diff --git a/media/libjxl/src/.bazelignore b/media/libjxl/src/.bazelignore new file mode 100644 index 0000000000..912eacc1b5 --- /dev/null +++ b/media/libjxl/src/.bazelignore @@ -0,0 +1 @@ +third_party diff --git a/media/libjxl/src/.clang-tidy b/media/libjxl/src/.clang-tidy index abccf4ed47..11c6d2bcdd 100644 --- a/media/libjxl/src/.clang-tidy +++ b/media/libjxl/src/.clang-tidy @@ -1,6 +1,4 @@ # Disabled checks: -# - google-readability-todo: We don't use the google TODO format. -# # - modernize-deprecated-headers: We don't use std:: versions of the standard # types and functions like size_t or printf, so we should include # instead . @@ -27,17 +25,34 @@ Checks: >- modernize-*, performance-*, readability-*, - -google-readability-todo, + -bugprone-branch-clone, + -bugprone-easily-swappable-parameters, + -bugprone-implicit-widening-of-multiplication-result, + -bugprone-infinite-loop, + -bugprone-narrowing-conversions, + -bugprone-unused-local-non-trivial-variable, + -modernize-avoid-c-arrays, + -modernize-concat-nested-namespaces, -modernize-deprecated-headers, -modernize-return-braced-init-list, + -modernize-type-traits, -modernize-use-auto, -modernize-use-default-member-init, + -modernize-use-nodiscard, -modernize-use-trailing-return-type, -modernize-use-using, + -performance-enum-size, + -readability-avoid-nested-conditional-operator, -readability-else-after-return, -readability-function-cognitive-complexity, + -readability-identifier-length, + -readability-magic-numbers, + -readability-redundant-access-specifiers, + -readability-simplify-boolean-expr, -readability-static-accessed-through-instance, + -readability-suspicious-call-argument, -readability-uppercase-literal-suffix, + -readability-use-anyofallof, WarningsAsErrors: >- @@ -60,11 +75,13 @@ WarningsAsErrors: >- HeaderFilterRegex: '^.*/(lib|tools)/.*\.h$' CheckOptions: - - key: readability-braces-around-statements.ShortStatementLines - value: '2' - - key: google-readability-braces-around-statements.ShortStatementLines - value: '2' - - key: readability-implicit-bool-conversion.AllowPointerConditions - value: '1' - - key: readability-implicit-bool-conversion.AllowIntegerConditions - value: '1' + - key: readability-braces-around-statements.ShortStatementLines + value: '2' + - key: google-readability-braces-around-statements.ShortStatementLines + value: '2' + - key: readability-implicit-bool-conversion.AllowPointerConditions + value: '1' + - key: readability-implicit-bool-conversion.AllowIntegerConditions + value: '1' + - key: bugprone-signed-char-misuse.CharTypdefsToIgnore + value: 'int8_t' diff --git a/media/libjxl/src/.gitignore b/media/libjxl/src/.gitignore index 58fea2d954..cb4acb8f82 100644 --- a/media/libjxl/src/.gitignore +++ b/media/libjxl/src/.gitignore @@ -1,7 +1,10 @@ # Build output directories /build /build* -/docker/*.log +/bazel* + +# clangd +.cache # The downloaded corpora files for benchmark. /third_party/corpora diff --git a/media/libjxl/src/.gitmodules b/media/libjxl/src/.gitmodules index bd008a6129..99b500fa68 100644 --- a/media/libjxl/src/.gitmodules +++ b/media/libjxl/src/.gitmodules @@ -25,3 +25,6 @@ [submodule "third_party/testdata"] path = testdata url = https://github.com/libjxl/testdata +[submodule "third_party/libjpeg-turbo"] + path = third_party/libjpeg-turbo + url = https://github.com/libjpeg-turbo/libjpeg-turbo.git diff --git a/media/libjxl/src/.pre-commit-config.yaml b/media/libjxl/src/.pre-commit-config.yaml new file mode 100644 index 0000000000..96b5d7fb52 --- /dev/null +++ b/media/libjxl/src/.pre-commit-config.yaml @@ -0,0 +1,30 @@ +repos: +- repo: https://github.com/gherynos/pre-commit-java + rev: v0.2.4 + hooks: + - id: Checkstyle +- repo: https://github.com/gitleaks/gitleaks + rev: v8.16.3 + hooks: + - id: gitleaks +- repo: https://github.com/jumanjihouse/pre-commit-hooks + rev: 3.0.0 + hooks: + - id: shellcheck +- repo: https://github.com/pocc/pre-commit-hooks + rev: v1.3.5 + hooks: + - id: cpplint +- repo: https://github.com/pre-commit/mirrors-eslint + rev: v8.38.0 + hooks: + - id: eslint +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/pylint-dev/pylint + rev: v2.17.2 + hooks: + - id: pylint diff --git a/media/libjxl/src/.readthedocs.yaml b/media/libjxl/src/.readthedocs.yaml index 6d714ba1aa..ee25fed481 100644 --- a/media/libjxl/src/.readthedocs.yaml +++ b/media/libjxl/src/.readthedocs.yaml @@ -11,7 +11,14 @@ version: 2 sphinx: configuration: doc/sphinx/conf.py +build: + os: ubuntu-22.04 + tools: + python: "3" + apt_packages: + - doxygen + - graphviz + python: - version: "3.7" install: - requirements: doc/sphinx/requirements.txt diff --git a/media/libjxl/src/AUTHORS b/media/libjxl/src/AUTHORS index c8522b8fc3..afd884bc79 100644 --- a/media/libjxl/src/AUTHORS +++ b/media/libjxl/src/AUTHORS @@ -1,38 +1,75 @@ -# List of the project authors for copyright purposes. When contributing to the -# project add your name or your organization's name to this list. See -# CONTRIBUTING.md for details. +# List of the project authors. +# When contributing you can add your name to this list. +# For a complete list of contributions made after the move +# from gitlab to github, see +# https://github.com/libjxl/libjxl/graphs/contributors. +# See CONTRIBUTING.md for details. # -# For organizations: -# Organization -# -# For individuals: -# Name # # Please keep each list sorted. If you wish to change your email address please # send a pull request. # Organizations: -Cloudinary Ltd. <*@cloudinary.com> -Google LLC <*@google.com> + +# - Cloudinary Ltd.: +Jon Sneyers + +# - Google: +Evgenii Kliuchnikov +Iulia Comșa +Jan Wassenberg +Jyrki Alakuijala +Lode Vandevenne +Luca Versari +Marcin Kowalczyk +Martin Bruse +Moritz Firsching +Sami Boukortt +Sebastian Gomez +Thomas Fischbacher +Zoltan Szabadka # Individuals: +a-shvedov +Aditya Patadia +Ahmad Amsyar Asyadiq Bin Syaiful Bahri <27284123+Ahmad-Amsyar@users.noreply.github.com> Alex Xu (Hello71) Alexander Sago +Alifian Caesar Khalid +Alistair Barrow Andrius Lukas Narbutas Aous Naman Artem Selishchev +Aryan Pingle Biswapriyo Nath CanadianBaconBoi +Damiano Albani +Damon Townsend 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 +Gerhard Huber +gi-man +Gilles Devillers (GilDev) Heiko Becker -Jon Sneyers +Ivan Kokorev +Jim Robinson +John Platts +Jonathan Brown (Jonnyawsom3) +Joshua Root Kai Hollberg +Kerry Su Kleis Auke Wolthuizen L. E. Segovia +ledoge Leo Izen Lovell Fuller Maarten DB @@ -41,14 +78,26 @@ Martin Strunz Mathieu Malaterre Mikk Leini Misaki Kasumi +Moonchild Straver +Nicholas Hayes <0xC0000054@users.noreply.github.com> +Nigel Tao +oupson Petr Diblík Pieter Wuille roland-rollo Samuel Leong Sandro +sandstrom +Sergey Fedorov Stephan T. Lavavej +StepSecurity Bot +Sylvestre Ledru Thomas Bonfort +Timo Rothenpieler +tmkk Vincent Torri +Wonwoo Choi xiota Yonatan Nebenzhal Ziemowit Zabawa +源文雨 <41315874+fumiama@users.noreply.github.com> diff --git a/media/libjxl/src/CHANGELOG.md b/media/libjxl/src/CHANGELOG.md index cf68400807..66bec27197 100644 --- a/media/libjxl/src/CHANGELOG.md +++ b/media/libjxl/src/CHANGELOG.md @@ -5,7 +5,149 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## [0.11.1] - 2024-26-11 + +### Fixed + - Huffman lookup table size fix (#3871 - + [CVE-2024-11403](https://www.cve.org/cverecord?id=CVE-2024-11403)) + - Check height limit in modular trees. (#3943 - + [CVE-2024-11498](https://www.cve.org/cverecord?id=CVE-2024-11498)) + +## [0.11.0] - 2024-09-13 + +### Added + - Gain Map API (#3552 and #3628): `JxlGainMapBundle` struct and API functions + to read and write gain map bundles`JxlGainMapWriteBundle` and + `JxlGainMapReadBundle` as well as handling compressed ICC profiles: + `JxlICCProfileEncode` and `JxlICCProfileDecode`. + - decoder API: added `JXL_DEC_BOX_COMPLETE` event to signal that the output + buffer for the current box has received all contents. Previously, this was + to be determined from the fact that the decoder had moved on either to + `JXL_DEC_SUCCESS` or to another subsequent `JXL_DEC_BOX`. This change is + made backward-compatible by the fact that the new event must be explicitly + subscribed to, and that `JXL_DEC_SUCCESS` / `JXL_DEC_BOX` still occur + afterwards and still imply that the previous box must be complete. + +### Changed / clarified + - avoiding abort in release build (#3631 and #3639) + +## [0.10.2] - 2024-03-08 + +### Fixed + - bugs in (lossless) encoding (#3367, #3359 and #3386) + - re-enable installation of MIME file (#3375) + - bugs in streaming mode (#3379 and #3380) + +## [0.10.1] - 2024-02-28 + +### Fixed + - reduce allocations (#3336 and #3339), + fixing a significant speed regression present since 0.9.0 + - bug in streaming encoding (#3331) + +## [0.10.0] - 2024-02-21 + +### Added + - decoder API: added `JxlDecoderGetBoxSizeContents` for getting the size of the + content of a box without the headers. + - encoder API: implemented new api functions for streaming encoding. + +### Changed / clarified + - decoder/encoder API: return failure when surface allocation fail + - encoder API / cjxl: updated modular effort levels to faster settings; the + effort range is now 1-10, with 11 available in advanced mode. + +## [0.9.2] - 2024-02-07 + +### Fixed + - bugs in the gdk-pixbuf plugin + - some build issues + +## [0.9.1] - 2024-01-08 + +### Fixed + - multiple build issues + +## [0.9.0] - 2023-12-22 + +### Added + - encoder API: add `JxlEncoderSetExtraChannelDistance` to adjust the quality + of extra channels (like alpha) separately. + - encoder API: new api functions for streaming encoding: + - `JxlEncoderSetOutputProcessor` + - `JxlEncoderFlushInput` + - `JxlEncoderOutputProcessor` struct + - `JxlEncoderSetOutputCallback` + - `JxlChunkedFrameInputSource` struct + - `JxlEncoderAddChunkedFrame` + - encoder API: new options for more fine-grained control over metadata + preservation when using `JxlEncoderAddJPEGFrame`: + - `JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF` + - `JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP` + - `JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF` + - encoder API: new function `JxlEncoderSetUpsamplingMode` to change the upsampling + method, e.g. to use nearest-neighbor upsampling for pixel art + - decoder API: implemented `JxlDecoderSetOutputColorProfile` and + `JxlDecoderSetCms` to enable decoding to desired colorspace. + - cjxl can now be used to explicitly add/update/strip Exif/XMP/JUMBF metadata using + the decoder-hints syntax, e.g. `cjxl input.ppm -x exif=input.exif output.jxl` + - djxl can now be used to extract Exif/XMP/JUMBF metadata + - encoder API: new function `JxlEncoderDistanceFromQuality` for convenience to + calculate a `distance` given a `quality` + +### Removed + - API: the Butteraugli API (`jxl/butteraugli.h`) was removed. + - encoder and decoder API: all deprecated functions were removed: + `JxlDecoderDefaultPixelFormat`, `JxlEncoderOptionsSetLossless`, + `JxlEncoderOptionsSetEffort`, `JxlEncoderOptionsSetDecodingSpeed`, + `JxlEncoderOptionsSetDistance`, `JxlEncoderOptionsCreate`, as well as + the deprecated enumerator values `JXL_DEC_EXTENSIONS`, `JXL_ENC_NOT_SUPPORTED`, + `JXL_TYPE_BOOLEAN`, `JXL_TYPE_UINT32`, and deprecated type `JxlEncoderOptions`. + - decoder API: the signature of `JxlDecoderGetColorAsEncodedProfile`, + `JxlDecoderGetICCProfileSize`, and `JxlDecoderGetColorAsICCProfile` + changed: a deprecated unused argument was removed. + +### Changed / clarified + - changed the name of the cjxl flag `photon_noise` to `photon_noise_iso` + - fixed how large boxes are decoded (#2958) + - fixed encoding files with unreadable patches (#3042, #3046) + +## [0.8.2] - 2023-06-14 + +### Changed + - Security: Fix an integer underflow bug in patch decoding (#2551- CVE-2023-35790). + +## [0.8.1] - 2023-02-03 + +### Changed + - Allow fast-lossless for 16-bit float input (#2093) + - Fix bug in palette (#2120) + - Security: Fix OOB read in exif.h (#2101 - [CVE-2023-0645](https://www.cve.org/cverecord?id=CVE-2023-0645)) + +## [0.8.0] - 2023-01-18 + +### Added + - decoder API: new function `JxlDecoderSetImageBitDepth` to set the bit depth + of the output buffer. + - decoder API proposal: add `JxlDecoderSetOutputColorProfile` and + `JxlDecoderSetCms` to enable decoding to desired colorspace; NB: not + implemented yet. + - encoder API: new function `JxlEncoderSetFrameBitDepth` to set the bit depth + of the input buffer. + - encoder API: add an effort 10 option for lossless compression; using this + setting requires calling `JxlEncoderAllowExpertOptions`. + - encoder API: new `JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES` enum value to + allow explicit control of metadata compression + +### Removed + - common API: removed `JxlIntrinsicSizeHeader` + - decoder API: removed deprecated `JXL_DEC_NEED_DC_OUT_BUFFER` and + `JXL_DEC_DC_IMAGE` events, `JxlDecoderDCOutBufferSize` and + `JxlDecoderSetDCOutBuffer` functions + +### Changed / clarified + - encoder API: `JxlEncoderProcessOutput` requires at least 32 bytes of output + space to proceed and guarantees that at least one byte will be written ## [0.7] - 2022-07-21 diff --git a/media/libjxl/src/CMakeLists.txt b/media/libjxl/src/CMakeLists.txt index 533815d232..2e5a1650e9 100644 --- a/media/libjxl/src/CMakeLists.txt +++ b/media/libjxl/src/CMakeLists.txt @@ -3,44 +3,28 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -# Ubuntu bionic ships with cmake 3.10. -cmake_minimum_required(VERSION 3.10) +# Ubuntu focal ships with cmake 3.16. +cmake_minimum_required(VERSION 3.16...3.27) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -# Honor VISIBILITY_INLINES_HIDDEN on all types of targets. -if(POLICY CMP0063) - cmake_policy(SET CMP0063 NEW) -endif() -# Pass CMAKE_EXE_LINKER_FLAGS to CC and CXX compilers when testing if they work. -if(POLICY CMP0065) - cmake_policy(SET CMP0065 NEW) -endif() - -# Set PIE flags for POSITION_INDEPENDENT_CODE targets, added in 3.14. -if(POLICY CMP0083) - cmake_policy(SET CMP0083 NEW) -endif() - project(LIBJXL LANGUAGES C CXX) -include(CheckCXXSourceCompiles) -check_cxx_source_compiles( - "int main() { - #if !defined(__EMSCRIPTEN__) - static_assert(false, \"__EMSCRIPTEN__ is not defined\"); - #endif - return 0; - }" - JPEGXL_EMSCRIPTEN -) +# TODO(sboukortt): remove once oss-fuzz passes -DBUILD_SHARED_LIBS=OFF +if(JPEGXL_ENABLE_FUZZERS) + message(STATUS "Fuzzer build detected, building static libs") + set(BUILD_SHARED_LIBS OFF) +endif() message(STATUS "CMAKE_SYSTEM_PROCESSOR is ${CMAKE_SYSTEM_PROCESSOR}") include(CheckCXXCompilerFlag) check_cxx_compiler_flag("-fsanitize=fuzzer-no-link" CXX_FUZZERS_SUPPORTED) -check_cxx_compiler_flag("-Xclang -mconstructor-aliases" CXX_CONSTRUCTOR_ALIASES_SUPPORTED) check_cxx_compiler_flag("-fmacro-prefix-map=OLD=NEW" CXX_MACRO_PREFIX_MAP) check_cxx_compiler_flag("-fno-rtti" CXX_NO_RTTI_SUPPORTED) +# Add "DebugOpt" CMake build type. Unlike builtin DEBUG it is optimized. +string(REGEX REPLACE "-DNDEBUG " "" CMAKE_CXX_FLAGS_DEBUGOPT "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DDEBUG" ) +string(REGEX REPLACE "-DNDEBUG " "" CMAKE_C_FLAGS_DEBUGOPT "${CMAKE_C_FLAGS_RELWITHDEBINFO} -DDEBUG" ) + # Enabled PIE binaries by default if supported. include(CheckPIESupported OPTIONAL RESULT_VARIABLE CHECK_PIE_SUPPORTED) if(CHECK_PIE_SUPPORTED) @@ -50,6 +34,19 @@ if(CHECK_PIE_SUPPORTED) endif() endif() +if(PROVISION_DEPENDENCIES) + # Run script to provision dependencies. + find_program (BASH_PROGRAM bash) + if(BASH_PROGRAM) + execute_process( + COMMAND ${BASH_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/deps.sh + RESULT_VARIABLE PROVISION_DEPENDENCIES_RESULT) + endif() + if(NOT PROVISION_DEPENDENCIES_RESULT EQUAL "0") + message(FATAL_ERROR "${CMAKE_CURRENT_SOURCE_DIR}/deps.sh failed with ${PROVISION_DEPENDENCIES_RESULT}") + endif() +endif() + ### Project build options: if(CXX_FUZZERS_SUPPORTED) # Enabled by default except on arm64, Windows and Apple builds. @@ -82,14 +79,29 @@ check_cxx_source_compiles( JXL_HWY_DISABLED_TARGETS_FORCED ) -set(WARNINGS_AS_ERRORS_DEFAULT false) - -if((SANITIZER STREQUAL "msan") OR JPEGXL_EMSCRIPTEN) +if((SANITIZER STREQUAL "msan") OR EMSCRIPTEN) set(BUNDLE_LIBPNG_DEFAULT YES) else() set(BUNDLE_LIBPNG_DEFAULT NO) endif() + +if(EXISTS "${PROJECT_SOURCE_DIR}/third_party/libjpeg-turbo/jconfig.h.in") + set(ENABLE_JPEGLI_DEFAULT YES) +else() + set(ENABLE_JPEGLI_DEFAULT NO) + message(STATUS "libjpeg-turbo submodule is absent; not enabling jpegli") +endif() + +include(TestBigEndian) +test_big_endian(ARCH_IS_BIG_ENDIAN) +if(ARCH_IS_BIG_ENDIAN) + set(ENABLE_SKCMS_DEFAULT NO) + message(STATUS "Big-endian architecture detected; defaulting to lcms2 instead of skcms") +else() + set(ENABLE_SKCMS_DEFAULT YES) +endif() + # Standard cmake naming for building shared libraries. get_property(SHARED_LIBS_SUPPORTED GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS) option(BUILD_SHARED_LIBS "Build shared libraries instead of static ones" ${SHARED_LIBS_SUPPORTED}) @@ -100,6 +112,16 @@ set(JPEGXL_ENABLE_DEVTOOLS false CACHE BOOL "Build JPEGXL developer tools.") set(JPEGXL_ENABLE_TOOLS true CACHE BOOL "Build JPEGXL user tools: cjxl and djxl.") +set(JPEGXL_ENABLE_JPEGLI ${ENABLE_JPEGLI_DEFAULT} CACHE BOOL + "Build jpegli library.") +set(JPEGXL_ENABLE_JPEGLI_LIBJPEG true CACHE BOOL + "Build libjpeg.so shared library based on jpegli.") +set(JPEGXL_INSTALL_JPEGLI_LIBJPEG false CACHE BOOL + "Install jpegli version of libjpeg.so system-wide.") +set(JPEGLI_LIBJPEG_LIBRARY_VERSION "62.3.0" CACHE STRING + "Library version of the libjpeg.so shared library that we build.") +set(JPEGLI_LIBJPEG_LIBRARY_SOVERSION "62" CACHE STRING + "Library so-version of the libjpeg.so shared library that we build.") set(JPEGXL_ENABLE_DOXYGEN true CACHE BOOL "Generate C API documentation using Doxygen.") set(JPEGXL_ENABLE_MANPAGES true CACHE BOOL @@ -116,10 +138,8 @@ set(JPEGXL_ENABLE_SJPEG true CACHE BOOL "Build JPEGXL with support for encoding with sjpeg.") set(JPEGXL_ENABLE_OPENEXR true CACHE BOOL "Build JPEGXL with support for OpenEXR if available.") -set(JPEGXL_ENABLE_SKCMS true CACHE BOOL +set(JPEGXL_ENABLE_SKCMS ${ENABLE_SKCMS_DEFAULT} CACHE BOOL "Build with skcms instead of lcms2.") -set(JPEGXL_BUNDLE_SKCMS true CACHE BOOL - "When building with skcms, bundle it into libjxl.a.") set(JPEGXL_ENABLE_VIEWERS false CACHE BOOL "Build JPEGXL viewer tools for evaluation.") set(JPEGXL_ENABLE_TCMALLOC ${ENABLE_TCMALLOC_DEFAULT} CACHE BOOL @@ -128,29 +148,40 @@ set(JPEGXL_ENABLE_PLUGINS false CACHE BOOL "Build third-party plugins to support JPEG XL in other applications.") set(JPEGXL_ENABLE_COVERAGE false CACHE BOOL "Enable code coverage tracking for libjxl. This also enables debug and disables optimizations.") -set(JPEGXL_ENABLE_PROFILER false CACHE BOOL - "Builds in support for profiling (printed by tools if extra flags given)") set(JPEGXL_ENABLE_SIZELESS_VECTORS false CACHE BOOL "Builds in support for SVE/RVV vectorization") set(JPEGXL_ENABLE_TRANSCODE_JPEG true CACHE BOOL "Builds in support for decoding transcoded JXL files back to JPEG,\ disabling it makes the decoder reject JXL_DEC_JPEG_RECONSTRUCTION events,\ (default enabled)") +set(JPEGXL_ENABLE_BOXES true CACHE BOOL + "Builds in support for decoding boxes in JXL files,\ + disabling it makes the decoder reject JXL_DEC_BOX events,\ + (default enabled)") set(JPEGXL_STATIC false CACHE BOOL "Build tools as static binaries.") -set(JPEGXL_WARNINGS_AS_ERRORS ${WARNINGS_AS_ERRORS_DEFAULT} CACHE BOOL +set(JPEGXL_WARNINGS_AS_ERRORS false CACHE BOOL "Treat warnings as errors during compilation.") set(JPEGXL_DEP_LICENSE_DIR "" CACHE STRING "Directory where to search for system dependencies \"copyright\" files.") set(JPEGXL_FORCE_NEON false CACHE BOOL "Set flags to enable NEON in arm if not enabled by your toolchain.") - +set(JPEGXL_TEST_TOOLS false CACHE BOOL + "Run scripts that test the encoding / decoding tools.") +set(JPEGXL_ENABLE_AVX512 false CACHE BOOL + "Build with AVX512 support (faster on CPUs that support it, but larger binary size).") +set(JPEGXL_ENABLE_AVX512_SPR false CACHE BOOL + "Build with AVX-512FP16 support (faster on CPUs that support it, but larger binary size).") +set(JPEGXL_ENABLE_AVX512_ZEN4 false CACHE BOOL +"Build with Zen4-optimized AVX512 support (faster on CPUs that support it, but larger binary size).") +set(JPEGXL_ENABLE_WASM_THREADS true CACHE BOOL + "Builds WASM modules with threads support") # Force system dependencies. set(JPEGXL_FORCE_SYSTEM_BROTLI false CACHE BOOL "Force using system installed brotli instead of third_party/brotli source.") set(JPEGXL_FORCE_SYSTEM_GTEST false CACHE BOOL - "Force using system installed googletest (gtest/gmock) instead of third_party/googletest source.") + "Force using system installed googletest (gtest) instead of third_party/googletest source.") set(JPEGXL_FORCE_SYSTEM_LCMS2 false CACHE BOOL "Force using system installed lcms2 instead of third_party/lcms source.") set(JPEGXL_FORCE_SYSTEM_HWY false CACHE BOOL @@ -162,37 +193,49 @@ if (NOT CMAKE_C_COMPILER_ID STREQUAL CMAKE_CXX_COMPILER_ID) message(FATAL_ERROR "Different C/C++ compilers set: " "${CMAKE_C_COMPILER_ID} vs ${CMAKE_CXX_COMPILER_ID}") endif() -if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - # Android NDK's toolchain.cmake fakes the clang version in - # CMAKE_CXX_COMPILER_VERSION with an incorrect number, so ignore this. - if (NOT CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION MATCHES "clang" - AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5) - message(FATAL_ERROR - "Minimum Clang version required is Clang 5, please update.") - endif() -elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU") - if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7) - message(FATAL_ERROR - "Minimum GCC version required is 7, please update.") - endif() -endif() message(STATUS "Compiled IDs C:${CMAKE_C_COMPILER_ID}, C++:${CMAKE_CXX_COMPILER_ID}") +set(JXL_HWY_INCLUDE_DIRS "$,hwy::hwy,hwy>,INTERFACE_INCLUDE_DIRECTORIES>>") +# Always disable SSSE3 since it is rare to have SSSE3 but not SSE4 +set(HWY_DISABLED_TARGETS "HWY_SSSE3") +if (NOT JPEGXL_ENABLE_AVX512) + message(STATUS "Disabled AVX512 (set JPEGXL_ENABLE_AVX512 to enable it)") + set(HWY_DISABLED_TARGETS "${HWY_DISABLED_TARGETS}|HWY_AVX3") + add_definitions(-DFJXL_ENABLE_AVX512=0) +endif() +if (NOT JPEGXL_ENABLE_AVX512_SPR) + message(STATUS "Disabled AVX512_SPR (set JPEGXL_ENABLE_AVX512_SPR to enable it)") + set(HWY_DISABLED_TARGETS "${HWY_DISABLED_TARGETS}|HWY_AVX3_SPR") +endif() +if (NOT JPEGXL_ENABLE_AVX512_ZEN4) + message(STATUS "Disabled AVX512_ZEN4 (set JPEGXL_ENABLE_AVX512_ZEN4 to enable it)") + set(HWY_DISABLED_TARGETS "${HWY_DISABLED_TARGETS}|HWY_AVX3_ZEN4") +endif() + + + # CMAKE_EXPORT_COMPILE_COMMANDS is used to generate the compilation database # used by clang-tidy. set(CMAKE_EXPORT_COMPILE_COMMANDS ON) if(JPEGXL_STATIC) set(BUILD_SHARED_LIBS 0) + + # https://learn.microsoft.com/en-us/cpp/build/reference/md-mt-ld-use-run-time-library?view=msvc-170 + # https://cmake.org/cmake/help/latest/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html + set(CMAKE_MSVC_RUNTIME_LIBRARY "$<$>:MultiThreaded>" CACHE STRING "") + # Clang developers say that in case to use "static" we have to build stdlib # ourselves; for real use case we don't care about stdlib, as it is "granted", # so just linking all other libraries is fine. - if (NOT MSVC AND NOT APPLE) + if (NOT MSVC) + string(APPEND CMAKE_EXE_LINKER_FLAGS " -static") + endif() + if ((NOT WIN32 AND NOT APPLE) OR CYGWIN OR MINGW) set(CMAKE_FIND_LIBRARY_SUFFIXES .a) - set(CMAKE_EXE_LINKER_FLAGS - "${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -static-libstdc++") + string(APPEND CMAKE_EXE_LINKER_FLAGS " -static-libgcc -static-libstdc++") endif() endif() # JPEGXL_STATIC @@ -202,7 +245,16 @@ find_package(Threads REQUIRED) # These settings are important to drive check_cxx_source_compiles # See CMP0067 (min cmake version is 3.10 anyway) -set(CMAKE_CXX_STANDARD 11) + +if ("cxx_std_17" IN_LIST CMAKE_CXX_COMPILE_FEATURES) + set(CMAKE_CXX_STANDARD 17) +else() + if ("cxx_std_14" IN_LIST CMAKE_CXX_COMPILE_FEATURES) + set(CMAKE_CXX_STANDARD 14) + else() + set(CMAKE_CXX_STANDARD 11) + endif() +endif() set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED YES) @@ -243,7 +295,7 @@ if(JPEGXL_STATIC) endif() endif() # JPEGXL_STATIC -if (JPEGXL_EMSCRIPTEN) +if (EMSCRIPTEN AND JPEGXL_ENABLE_WASM_THREADS) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") @@ -257,73 +309,75 @@ if (CXX_NO_RTTI_SUPPORTED) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") endif() +# Internal flags for coverage builds: +set(JPEGXL_COVERAGE_FLAGS) +set(JPEGXL_COVERAGE_LINK_FLAGS) + if (MSVC) -# TODO(janwas): add flags -else () - -# Global compiler flags for all targets here and in subdirectories. -add_definitions( - # Avoid changing the binary based on the current time and date. - -D__DATE__="redacted" - -D__TIMESTAMP__="redacted" - -D__TIME__="redacted" -) - -# Avoid log spam from fopen etc. -if(MSVC) + # TODO(janwas): add flags add_definitions(-D_CRT_SECURE_NO_WARNINGS) -endif() +else () + # Global compiler flags for all targets here and in subdirectories. + add_definitions( + # Avoid changing the binary based on the current time and date. + -D__DATE__="redacted" + -D__TIMESTAMP__="redacted" + -D__TIME__="redacted" + ) -# TODO(eustas): JXL currently compiles, but does not pass tests... -if (NOT JXL_HWY_DISABLED_TARGETS_FORCED AND NOT JPEGXL_ENABLE_SIZELESS_VECTORS) - add_definitions(-DHWY_DISABLED_TARGETS=\(HWY_SVE|HWY_SVE2|HWY_SVE_256|HWY_SVE2_128|HWY_RVV\)) - message("Warning: HWY_SVE, HWY_SVE2, HWY_SVE_256, HWY_SVE2_128 and HWY_RVV CPU targets are disabled") -endif() + # TODO(eustas): JXL currently compiles, but does not pass tests... + if (NOT JXL_HWY_DISABLED_TARGETS_FORCED) + if (NOT JPEGXL_ENABLE_SIZELESS_VECTORS) + set(HWY_DISABLED_TARGETS "${HWY_DISABLED_TARGETS}|HWY_SVE|HWY_SVE2|HWY_SVE_256|HWY_SVE2_128|HWY_RVV") + endif() + add_compile_options($<$>:-DHWY_DISABLED_TARGETS=\(${HWY_DISABLED_TARGETS}\)>) + endif() -# In CMake before 3.12 it is problematic to pass repeated flags like -Xclang. -# For this reason we place them in CMAKE_CXX_FLAGS instead. -# See https://gitlab.kitware.com/cmake/cmake/issues/15826 + # Machine flags. + add_compile_options(-funwind-tables) + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options("SHELL:-Xclang -mrelax-all") + endif() + if (CXX_CONSTRUCTOR_ALIASES_SUPPORTED) + add_compile_options("SHELL:-Xclang -mconstructor-aliases") + endif() -# Machine flags. -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -funwind-tables") -if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -mrelax-all") -endif() -if (CXX_CONSTRUCTOR_ALIASES_SUPPORTED) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -mconstructor-aliases") -endif() + if(WIN32) + # Not supported by clang-cl, but frame pointers are default on Windows + else() + add_compile_options(-fno-omit-frame-pointer) + endif() -if(WIN32) -# Not supported by clang-cl, but frame pointers are default on Windows -else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer") -endif() + # CPU flags - remove once we have NEON dynamic dispatch -# CPU flags - remove once we have NEON dynamic dispatch + # TODO(janwas): this also matches M1, but only ARMv7 is intended/needed. + if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm") + if(JPEGXL_FORCE_NEON) + # GCC requires these flags, otherwise __ARM_NEON is undefined. + add_compile_options(-mfpu=neon-vfpv4 -mfloat-abi=hard) + endif() + endif() -# TODO(janwas): this also matches M1, but only ARMv7 is intended/needed. -if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm") -if(JPEGXL_FORCE_NEON) -# GCC requires these flags, otherwise __ARM_NEON is undefined. -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ - -mfpu=neon-vfpv4 -mfloat-abi=hard") -endif() -endif() + add_compile_options( + # Ignore this to allow redefining __DATE__ and others. + -Wno-builtin-macro-redefined -# Force build with optimizations in release mode. -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2") + # Global warning settings. + -Wall + ) -add_compile_options( - # Ignore this to allow redefining __DATE__ and others. - -Wno-builtin-macro-redefined + if (JPEGXL_WARNINGS_AS_ERRORS) + add_compile_options(-Werror) + endif () - # Global warning settings. - -Wall -) - -if (JPEGXL_WARNINGS_AS_ERRORS) -add_compile_options(-Werror) -endif () + if(JPEGXL_ENABLE_COVERAGE) + set(JPEGXL_COVERAGE_FLAGS + -g -O0 -fprofile-arcs -ftest-coverage + ) + set(JPEGXL_COVERAGE_LINK_FLAGS + --coverage + ) + endif() # JPEGXL_ENABLE_COVERAGE endif () # !MSVC include(GNUInstallDirs) @@ -348,125 +402,146 @@ endif() add_subdirectory(lib) if(BUILD_TESTING) -# Script to run tests over the source code in bash. -find_program (BASH_PROGRAM bash) -if(BASH_PROGRAM) - add_test( - NAME bash_test - COMMAND ${BASH_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/bash_test.sh) -endif() + # Script to run tests over the source code in bash. + find_program (BASH_PROGRAM bash) + if(BASH_PROGRAM) + add_test( + NAME bash_test + COMMAND ${BASH_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/bash_test.sh) + endif() endif() # BUILD_TESTING # Documentation generated by Doxygen if(JPEGXL_ENABLE_DOXYGEN) -find_package(Doxygen) -if(DOXYGEN_FOUND) -set(DOXYGEN_GENERATE_HTML "YES") -set(DOXYGEN_GENERATE_XML "YES") -set(DOXYGEN_STRIP_FROM_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lib/include") -set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "README.md") -if(JPEGXL_WARNINGS_AS_ERRORS) -set(DOXYGEN_WARN_AS_ERROR "YES") -endif() -set(DOXYGEN_QUIET "YES") -doxygen_add_docs(doc - "${CMAKE_CURRENT_SOURCE_DIR}/lib/include" - "${CMAKE_CURRENT_SOURCE_DIR}/doc/api.txt" - WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - COMMENT "Generating C API documentation") + find_package(Doxygen) + if(DOXYGEN_FOUND) + set(DOXYGEN_GENERATE_HTML "YES") + set(DOXYGEN_GENERATE_XML "YES") + set(DOXYGEN_STRIP_FROM_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lib/include") + if(JPEGXL_WARNINGS_AS_ERRORS) + set(DOXYGEN_WARN_AS_ERROR "YES") + endif() + set(DOXYGEN_QUIET "YES") + doxygen_add_docs(doc + "${CMAKE_CURRENT_SOURCE_DIR}/lib/include" + "${CMAKE_CURRENT_SOURCE_DIR}/doc/api.txt" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + COMMENT "Generating C API documentation") -# Add sphinx doc build step for readthedocs.io (requires doxygen too). -find_program(SPHINX_BUILD_PROGRAM sphinx-build) -if(SPHINX_BUILD_PROGRAM) - add_custom_command( - OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rtd/nonexistent" - COMMENT "Generating readthedocs.io output on ${CMAKE_CURRENT_BINARY_DIR}/rtd" - COMMAND ${SPHINX_BUILD_PROGRAM} -q -W -b html -j auto - ${CMAKE_SOURCE_DIR}/doc/sphinx - ${CMAKE_CURRENT_BINARY_DIR}/rtd - DEPENDS doc - ) - # This command runs the documentation generation every time since the output - # target file doesn't exist. - add_custom_target(rtd-html - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/rtd/nonexistent - ) -else() # SPHINX_BUILD_PROGRAM\ - message(WARNING "sphinx-build not found, skipping rtd documentation") -endif() # SPHINX_BUILD_PROGRAM + # Add sphinx doc build step for readthedocs.io (requires doxygen too). + find_program(SPHINX_BUILD_PROGRAM sphinx-build) + if(SPHINX_BUILD_PROGRAM) + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rtd/nonexistent" + COMMENT "Generating readthedocs.io output on ${CMAKE_CURRENT_BINARY_DIR}/rtd" + COMMAND ${SPHINX_BUILD_PROGRAM} -q -W -b html -j auto + ${CMAKE_SOURCE_DIR}/doc/sphinx + ${CMAKE_CURRENT_BINARY_DIR}/rtd + DEPENDS doc + ) + # This command runs the documentation generation every time since the output + # target file doesn't exist. + add_custom_target(rtd-html + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/rtd/nonexistent + ) + else() # SPHINX_BUILD_PROGRAM\ + message(WARNING "sphinx-build not found, skipping rtd documentation") + endif() # SPHINX_BUILD_PROGRAM -else() -# Create a "doc" target for compatibility since "doc" is not otherwise added to -# the build when doxygen is not installed. -add_custom_target(doc false - COMMENT "Error: Can't generate doc since Doxygen not installed.") -endif() # DOXYGEN_FOUND + else() + # Create a "doc" target for compatibility since "doc" is not otherwise added to + # the build when doxygen is not installed. + add_custom_target(doc false + COMMENT "Error: Can't generate doc since Doxygen not installed.") + endif() # DOXYGEN_FOUND endif() # JPEGXL_ENABLE_DOXYGEN if(JPEGXL_ENABLE_MANPAGES) -find_program(ASCIIDOC a2x) -if(ASCIIDOC) -file(STRINGS "${ASCIIDOC}" ASCIIDOC_SHEBANG LIMIT_COUNT 1) -if(ASCIIDOC_SHEBANG MATCHES "/sh|/bash") - set(ASCIIDOC_PY_FOUND ON) - # Run the program directly and set ASCIIDOC as empty. - set(ASCIIDOC_PY "${ASCIIDOC}") - set(ASCIIDOC "") -elseif(ASCIIDOC_SHEBANG MATCHES "python2") - find_package(Python2 COMPONENTS Interpreter) - set(ASCIIDOC_PY_FOUND "${Python2_Interpreter_FOUND}") - set(ASCIIDOC_PY Python2::Interpreter) -elseif(ASCIIDOC_SHEBANG MATCHES "python3") - find_package(Python3 COMPONENTS Interpreter) - set(ASCIIDOC_PY_FOUND "${Python3_Interpreter_FOUND}") - set(ASCIIDOC_PY Python3::Interpreter) -else() - find_package(Python COMPONENTS Interpreter QUIET) - if(NOT Python_Interpreter_FOUND) - find_program(ASCIIDOC_PY python) - if(ASCIIDOC_PY) + find_program(ASCIIDOC a2x) + if(ASCIIDOC) + file(STRINGS "${ASCIIDOC}" ASCIIDOC_SHEBANG LIMIT_COUNT 1) + if(ASCIIDOC_SHEBANG MATCHES "sh$" OR ASCIIDOC_SHEBANG MATCHES "libexec/bin/python$" OR MINGW) set(ASCIIDOC_PY_FOUND ON) + # Run the program directly and set ASCIIDOC as empty. + set(ASCIIDOC_PY "${ASCIIDOC}") + set(ASCIIDOC "") + elseif(ASCIIDOC_SHEBANG MATCHES "python2") + find_package(Python2 COMPONENTS Interpreter) + set(ASCIIDOC_PY_FOUND "${Python2_Interpreter_FOUND}") + set(ASCIIDOC_PY Python2::Interpreter) + elseif(ASCIIDOC_SHEBANG MATCHES "python3") + find_package(Python3 COMPONENTS Interpreter) + set(ASCIIDOC_PY_FOUND "${Python3_Interpreter_FOUND}") + set(ASCIIDOC_PY Python3::Interpreter) + else() + find_package(Python COMPONENTS Interpreter QUIET) + if(NOT Python_Interpreter_FOUND) + find_program(ASCIIDOC_PY python) + if(ASCIIDOC_PY) + set(ASCIIDOC_PY_FOUND ON) + endif() + else() + set(ASCIIDOC_PY_FOUND "${Python_Interpreter_FOUND}") + set(ASCIIDOC_PY Python::Interpreter) + endif() endif() - else() - set(ASCIIDOC_PY_FOUND "${Python_Interpreter_FOUND}") - set(ASCIIDOC_PY Python::Interpreter) - endif() -endif() -if (ASCIIDOC_PY_FOUND) - set(MANPAGE_FILES "") - set(MANPAGES "") - foreach(PAGE IN ITEMS cjxl djxl) - # Invoking the Python interpreter ourselves instead of running the a2x binary - # directly is necessary on MSYS2, otherwise it is run through cmd.exe which - # does not recognize it. - add_custom_command( - OUTPUT "${PAGE}.1" - COMMAND "${ASCIIDOC_PY}" - ARGS ${ASCIIDOC} - --format manpage --destination-dir="${CMAKE_CURRENT_BINARY_DIR}" - "${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt" - MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt") - list(APPEND MANPAGE_FILES "${CMAKE_CURRENT_BINARY_DIR}/${PAGE}.1") - list(APPEND MANPAGES "${PAGE}.1") - endforeach() - add_custom_target(manpages ALL DEPENDS ${MANPAGES}) - install(FILES ${MANPAGE_FILES} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) -endif() # ASCIIDOC_PY_FOUND -else() - message(WARNING "asciidoc was not found, the man pages will not be installed.") -endif() # ASCIIDOC + if (ASCIIDOC_PY_FOUND) + set(MANPAGE_FILES "") + set(MANPAGES "") + foreach(PAGE IN ITEMS cjxl djxl) + # Invoking the Python interpreter ourselves instead of running the a2x binary + # directly is necessary on MSYS2, otherwise it is run through cmd.exe which + # does not recognize it. + add_custom_command( + OUTPUT "${PAGE}.1" + COMMAND "${ASCIIDOC_PY}" + ARGS ${ASCIIDOC} + --format manpage --destination-dir="${CMAKE_CURRENT_BINARY_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt" + MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt") + list(APPEND MANPAGE_FILES "${CMAKE_CURRENT_BINARY_DIR}/${PAGE}.1") + list(APPEND MANPAGES "${PAGE}.1") + endforeach() + add_custom_target(manpages ALL DEPENDS ${MANPAGES}) + install(FILES ${MANPAGE_FILES} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) + endif() # ASCIIDOC_PY_FOUND + else() + message(WARNING "asciidoc was not found, the man pages will not be installed.") + endif() # ASCIIDOC endif() # JPEGXL_ENABLE_MANPAGES # Example usage code. if (JPEGXL_ENABLE_EXAMPLES) -include(examples/examples.cmake) + include(examples/examples.cmake) endif () # Plugins for third-party software if (JPEGXL_ENABLE_PLUGINS) -add_subdirectory(plugins) + add_subdirectory(plugins) endif () # Binary tools add_subdirectory(tools) + + +macro(list_test_targets out dir) + get_property(dir_targets DIRECTORY ${dir} PROPERTY BUILDSYSTEM_TARGETS) + foreach(target ${dir_targets}) + if (target MATCHES ".*_test") + list(APPEND ${out} ${target}) + endif() + endforeach() + get_property(subdirectories DIRECTORY ${dir} PROPERTY SUBDIRECTORIES) + foreach(subdir ${subdirectories}) + list_test_targets(${out} ${subdir}) + endforeach() +endmacro() + +set(all_tests_list) +list_test_targets(all_tests_list ${CMAKE_CURRENT_SOURCE_DIR}) + +if(all_tests_list) + add_custom_target(all_tests) + add_dependencies(all_tests ${all_tests_list}) +endif() diff --git a/media/libjxl/src/CONTRIBUTING.md b/media/libjxl/src/CONTRIBUTING.md index cb6459797c..1a255ba40b 100644 --- a/media/libjxl/src/CONTRIBUTING.md +++ b/media/libjxl/src/CONTRIBUTING.md @@ -75,7 +75,9 @@ information on using pull requests. functionality. Reviewers may ask you to split a Pull Request and it is easier to create a smaller change from the beginning. - * Describe your commits. Add a meaningful description to your commit message, explain what you are changing if it is not trivially obvious, but more importantly explain *why* you are making those changes. For example "Fix + * Describe your commits. Add a meaningful description to your commit message, + explain what you are changing if it is not trivially obvious, but more + importantly explain *why* you are making those changes. For example "Fix build" is not a good commit message, describe what build and if it makes sense why is this fixing it or why was it failing without this. It is very likely that people far in the future without any context you have right now will be @@ -113,8 +115,7 @@ information on using pull requests. * Sign the CLA (only needed once per user, see above). - * AUTHORS: If this is your first contribution, add your name or your - company name to the [AUTHORS](AUTHORS) file for copyright tracking purposes. + * AUTHORS: You can add your name to the [AUTHORS](AUTHORS) file. * Style guide. Check `./ci.sh lint`. diff --git a/media/libjxl/src/MODULE.bazel b/media/libjxl/src/MODULE.bazel new file mode 100644 index 0000000000..cc2397538d --- /dev/null +++ b/media/libjxl/src/MODULE.bazel @@ -0,0 +1,12 @@ +# 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. + +bazel_dep(name = "bazel_skylib", version = "1.7.1") +bazel_dep(name = "giflib", version = "5.2.1") +bazel_dep(name = "googletest", version = "1.14.0") +bazel_dep(name = "libjpeg_turbo", version = "2.1.91") +bazel_dep(name = "libpng", version = "1.6.40") +bazel_dep(name = "libwebp", version = "1.3.2") +bazel_dep(name = "openexr", version = "3.2.1") diff --git a/media/libjxl/src/MODULE.bazel.lock b/media/libjxl/src/MODULE.bazel.lock new file mode 100644 index 0000000000..7ea00d853c --- /dev/null +++ b/media/libjxl/src/MODULE.bazel.lock @@ -0,0 +1,132 @@ +{ + "lockFileVersion": 11, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/source.json": "06cc0842d241da0c5edc755edb3c7d0d008d304330e57ecf2d6449fb0b633a82", + "https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel": "50341a62efbc483e8a2a6aec30994a58749bd7b885e18dd96aa8c33031e558ef", + "https://bcr.bazel.build/modules/apple_support/1.5.0/source.json": "eb98a7627c0bc486b57f598ad8da50f6625d974c8f723e9ea71bd39f709c9862", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/source.json": "c9320aa53cd1c441d24bd6b716da087ad7e4ff0d9742a9884587596edfe53015", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", + "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/giflib/5.2.1/MODULE.bazel": "810dbc4275425c89ffe648dd78c537fe2eb1d2a9704d10e950b295263af03366", + "https://bcr.bazel.build/modules/giflib/5.2.1/source.json": "94215af981976c329eaec0083727b155ea89607e61debea50ed508e7963ef9a6", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/googletest/1.14.0/source.json": "2478949479000fdd7de9a3d0107ba2c85bb5f961c3ecb1aa448f52549ce310b5", + "https://bcr.bazel.build/modules/imath/3.1.9/MODULE.bazel": "26fe47ee8137a4c605667fb0d26a5c12b8fb2e758824a376789b287b2f9d424d", + "https://bcr.bazel.build/modules/imath/3.1.9/source.json": "22b7d9e617d4d26626f5ac8fba3cd2bd7a87f7501c99fa847f8d9e2980416e8f", + "https://bcr.bazel.build/modules/libdeflate/1.19/MODULE.bazel": "b7396a2edfd5ce6669509fbdd10db5e8731d60954063699c546c3126c8156824", + "https://bcr.bazel.build/modules/libdeflate/1.19/source.json": "d4604a526efba9b5347309de49673bbe152da465f7c80c7f7ffe6800d8b504d1", + "https://bcr.bazel.build/modules/libjpeg_turbo/2.1.91/MODULE.bazel": "bcc23b7c4866af2d7777ee49db435603ca1e35b90ea0689f8051900fa8c73c6b", + "https://bcr.bazel.build/modules/libjpeg_turbo/2.1.91/source.json": "42ea85708058e2408f229075e1cbeaad13fa2719918ff9c505be5e22b57ef17b", + "https://bcr.bazel.build/modules/libpng/1.6.40/MODULE.bazel": "cc1952a9b5efd4df3dfdb9f9ba2b1c8d88b4fd9b0e474185cb81d90a31c7c453", + "https://bcr.bazel.build/modules/libpng/1.6.40/source.json": "2fe294bf161c2d3f1e04e7cecb6eb2e6c0c198698b23cabc1c4e6ff77d82a86a", + "https://bcr.bazel.build/modules/libwebp/1.3.2/MODULE.bazel": "c60edf34a913daebac9bd2cbe17b84048e4a7a5d3571f70be93c1b1227a69659", + "https://bcr.bazel.build/modules/libwebp/1.3.2/source.json": "e7b8d3047ad9758fda22fcf46bd8b57414b0eb5e7903f4ce888683d778633cf7", + "https://bcr.bazel.build/modules/openexr/3.2.1/MODULE.bazel": "5665fa95490825760943601d618e2d70eb45378ea3f2961c5ec18f23ae8a2106", + "https://bcr.bazel.build/modules/openexr/3.2.1/source.json": "afc17dda6614ff723cc1def634fa4f33534d3d29514b089fa4aa5eb47ba1c65b", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/platforms/0.0.9/source.json": "cd74d854bf16a9e002fb2ca7b1a421f4403cda29f824a765acd3a8c56f8d43e6", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/21.7/source.json": "bbe500720421e582ff2d18b0802464205138c06056f443184de39fbb8187b09b", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/source.json": "1f1ba6fea244b616de4a554a0f4983c91a9301640c8fe0dd1d410254115c8430", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/7.6.1/source.json": "8f3f3076554e1558e8e468b2232991c510ecbcbed9e6f8c06ac31c93bcf38362", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/source.json": "a075731e1b46bc8425098512d038d416e966ab19684a10a34f4741295642fc35", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/0.0.7/source.json": "355cc5737a0f294e560d52b1b7a6492d4fff2caf0bef1a315df5a298fca2d34a", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/source.json": "c2557066e0c0342223ba592510ad3d812d4963b9024831f7f66fd0584dd8c66c", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/source.json": "d57902c052424dfda0e71646cb12668d39c4620ee0544294d9d941e7d12bc3a9", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7", + "https://bcr.bazel.build/modules/rules_python/0.22.1/source.json": "57226905e783bae7c37c2dd662be078728e48fa28ee4324a7eabcafb5a43d014", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.1/source.json": "a96f95e02123320aa015b956f29c00cb818fa891ef823d55148e1a362caacf29", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/source.json": "f1ef7d3f9e0e26d4b23d1c39b5f5de71f584dd7d1b4ef83d9bbba6ec7a6a6459", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", + "https://bcr.bazel.build/modules/zlib/1.3/MODULE.bazel": "6a9c02f19a24dcedb05572b2381446e27c272cd383aed11d41d99da9e3167a72", + "https://bcr.bazel.build/modules/zlib/1.3/source.json": "b6b43d0737af846022636e6e255fd4a96fee0d34f08f3830e6e0bac51465c37c" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@apple_support~//crosstool:setup.bzl%apple_cc_configure_extension": { + "general": { + "bzlTransitiveDigest": "PjIds3feoYE8SGbbIq2SFTZy3zmxeO2tQevJZNDo7iY=", + "usagesDigest": "aLmqbvowmHkkBPve05yyDNGN7oh7QE9kBADr3QIZTZs=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_config_apple_cc": { + "bzlFile": "@@apple_support~//crosstool:setup.bzl", + "ruleClassName": "_apple_cc_autoconf", + "attributes": {} + }, + "local_config_apple_cc_toolchains": { + "bzlFile": "@@apple_support~//crosstool:setup.bzl", + "ruleClassName": "_apple_cc_autoconf_toolchains", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "apple_support~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@platforms//host:extension.bzl%host_platform": { + "general": { + "bzlTransitiveDigest": "xelQcPZH8+tmuOHVjL9vDxMnnQNMlwj0SlvgoqBkm4U=", + "usagesDigest": "meSzxn3DUCcYEhq4HQwExWkWtU4EjriRBQLsZN+Q0SU=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "host_platform": { + "bzlFile": "@@platforms//host:extension.bzl", + "ruleClassName": "host_platform_repo", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [] + } + } + } +} diff --git a/media/libjxl/src/README.Haiku.md b/media/libjxl/src/README.Haiku.md deleted file mode 100644 index 20111c570c..0000000000 --- a/media/libjxl/src/README.Haiku.md +++ /dev/null @@ -1,20 +0,0 @@ -## Disclaimer - -Haiku builds are not officially supported, i.e. the build might not work at all, -some tests may fail and some sub-projects are excluded from build. - -This manual outlines Haiku-specific setup. For general building and testing -instructions see "[README](README.md)" and -"[Building and Testing changes](doc/building_and_testing.md)". - -## Dependencies - -```shell -pkgman install llvm9_clang ninja cmake doxygen libjpeg_turbo_devel giflib_devel -``` - -## Building - -```shell -TEST_STACK_LIMIT=none CMAKE_FLAGS="-I/boot/system/develop/tools/lib/gcc/x86_64-unknown-haiku/8.3.0/include/c++ -I/boot/system/develop/tools/lib/gcc/x86_64-unknown-haiku/8.3.0/include/c++/x86_64-unknown-haiku" CMAKE_SHARED_LINKER_FLAGS="-shared -Xlinker -soname=libjpegxl.so -lpthread" ./ci.sh opt -``` diff --git a/media/libjxl/src/README.OSX.md b/media/libjxl/src/README.OSX.md deleted file mode 100644 index 8c6dc5a397..0000000000 --- a/media/libjxl/src/README.OSX.md +++ /dev/null @@ -1,41 +0,0 @@ -## Disclaimer - -OSX builds have "best effort" support, i.e. build might not work at all, some -tests may fail and some sub-projects are excluded from build. - -This manual outlines OSX specific setup. For general building and testing -instructions see "[README](README.md)" and -"[Building and Testing changes](doc/building_and_testing.md)". - -[Homebrew](https://brew.sh/) is a popular package manager. JPEG XL library and -binaries could be installed using it: - -```bash -brew install jpeg-xl -``` - -## Dependencies - -Make sure that `brew doctor` does not report serious problems and up-to-date -version of XCode is installed. - -Installing (actually, building) `clang` might take a couple hours. - -```bash -brew install llvm -``` - -```bash -brew install coreutils cmake giflib jpeg-turbo libpng ninja zlib -``` - -Before building the project check that `which clang` is -`/usr/local/opt/llvm/bin/clang`, not the one provided by XCode. If not, update -`PATH` environment variable. - -Also, setting `CMAKE_PREFIX_PATH` might be necessary for correct include paths -resolving, e.g.: - -```bash -export CMAKE_PREFIX_PATH=`brew --prefix giflib`:`brew --prefix jpeg-turbo`:`brew --prefix libpng`:`brew --prefix zlib` -``` \ No newline at end of file diff --git a/media/libjxl/src/README.md b/media/libjxl/src/README.md index b0f2e3b50e..877eabac8f 100644 --- a/media/libjxl/src/README.md +++ b/media/libjxl/src/README.md @@ -12,6 +12,10 @@ https://github.com/libjxl/libjxl/actions/workflows/fuzz.yml) https://github.com/libjxl/libjxl/actions/workflows/release.yaml) [![Doc](https://readthedocs.org/projects/libjxl/badge/?version=latest)]( https://libjxl.readthedocs.io/en/latest/?badge=latest) +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7845/badge)]( +https://www.bestpractices.dev/projects/7845) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/libjxl/libjxl/badge)]( +https://securityscorecards.dev/viewer/?uri=github.com/libjxl/libjxl) [![codecov](https://codecov.io/gh/libjxl/libjxl/branch/main/graph/badge.svg)]( https://codecov.io/gh/libjxl/libjxl) @@ -21,109 +25,62 @@ This repository contains a reference implementation of JPEG XL (encoder and decoder), called `libjxl`. This software library is [used by many applications that support JPEG XL](doc/software_support.md). -JPEG XL is in the final stages of standardization and its codestream and file format -are frozen. +JPEG XL was standardized in 2022 as [ISO/IEC 18181](https://jpeg.org/jpegxl/workplan.html). +The [core codestream](doc/format_overview.md#codestream-features) is specified in 18181-1, +the [file format](doc/format_overview.md#file-format-features) in 18181-2. +[Decoder conformance](https://github.com/libjxl/conformance) is defined in 18181-3, +and 18181-4 is the [reference software](https://github.com/libjxl/libjxl). The library API, command line options, and tools in this repository are subject -to change, however files encoded with `cjxl` conform to the JPEG XL format -specification and can be decoded with current and future `djxl` decoders or -`libjxl` decoding library. +to change, however files encoded with `cjxl` conform to the JPEG XL specification +and can be decoded with current and future `djxl` decoders or the `libjxl` decoding library. -## Quick start guide +## Installation -For more details and other workflows see the "Advanced guide" below. +In most Linux distributions, installing `libjxl` is just a matter of using the package management system. +For example in Debian-based distributions: `apt install libjxl-tools` will install `cjxl` and `djxl` +and other tools like `benchmark_xl` are available in the package `libjxl-devtools`. +On MacOS, you can use [Homebrew](https://brew.sh/): `brew install jpeg-xl`. -### Checking out the code +[![libjxl packaging status](https://repology.org/badge/vertical-allrepos/libjxl.svg?exclude_unsupported=1&columns=3&exclude_sources=modules,site&header=libjxl%20packaging%20status)](https://repology.org/project/libjxl/versions) -```bash -git clone https://github.com/libjxl/libjxl.git --recursive --shallow-submodules -``` +From the [releases page](https://github.com/libjxl/libjxl/releases/) the following can be downloaded: + - Windows binaries + - Debian and Ubuntu .deb packages -This repository uses git submodules to handle some third party dependencies -under `third_party`, that's why is important to pass `--recursive`. If you -didn't check out with `--recursive`, or any submodule has changed, run: +Of course you can also [build libjxl from sources](BUILDING.md). -```bash -git submodule update --init --recursive --depth 1 --recommend-shallow -``` -The `--shallow-submodules` and `--depth 1 --recommend-shallow` options create -shallow clones which only downloads the commits requested, and is all that is -needed to build `libjxl`. Should full clones be necessary, you could always run: - -```bash -git submodule foreach git fetch --unshallow -git submodule update --init --recursive -``` - -which pulls the rest of the commits in the submodules. - -Important: If you downloaded a zip file or tarball from the web interface you -won't get the needed submodules and the code will not compile. You can download -these external dependencies from source running `./deps.sh`. The git workflow -described above is recommended instead. - -### Installing dependencies - -Required dependencies for compiling the code, in a Debian/Ubuntu based -distribution run: - -```bash -sudo apt install cmake pkg-config libbrotli-dev -``` - -Optional dependencies for supporting other formats in the `cjxl`/`djxl` tools, -in a Debian/Ubuntu based distribution run: - -```bash -sudo apt install libgif-dev libjpeg-dev libopenexr-dev libpng-dev libwebp-dev -``` - -We recommend using a recent Clang compiler (version 7 or newer), for that -install clang and set `CC` and `CXX` variables. - -```bash -sudo apt install clang -export CC=clang CXX=clang++ -``` - -### Building - -```bash -cd libjxl -mkdir build -cd build -cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF .. -cmake --build . -- -j$(nproc) -``` - -The encoder/decoder tools will be available in the `build/tools` directory. - -### Installing - -```bash -sudo cmake --install . -``` - -### Basic encoder/decoder +## Usage To encode a source image to JPEG XL with default settings: ```bash -build/tools/cjxl input.png output.jxl +cjxl input.png output.jxl ``` -For more settings run `build/tools/cjxl --help` or for a full list of options -run `build/tools/cjxl -v -v --help`. +The desired visual fidelity can be selected using the `--distance` parameter +(in units of just-noticeable difference, where 0 is lossless and the most useful lossy range is 0.5 .. 3.0), +or using `--quality` (on a scale from 0 to 100, roughly matching libjpeg). +The [encode effort](doc/encode_effort.md) can be selected using the `--effort` parameter. + +For more settings run `cjxl --help` or for a full list of options +run `cjxl -v -v --help`. To decode a JPEG XL file run: ```bash -build/tools/djxl input.jxl output.png +djxl input.jxl output.png ``` -When possible `cjxl`/`djxl` are able to read/write the following -image formats: .exr, .gif, .jpeg/.jpg, .pfm, .pgm/.ppm, .pgx, .png. +When possible, `cjxl`/`djxl` are able to read/write the following image formats: +OpenEXR (`.exr`), GIF (`.gif`), JPEG (`.jpg`/`.jpeg`), NetPBM (`.pam`/`.pgm`/`.ppm`), +Portable FloatMap (`.pfm`), PGX Test Format (`.pgx`), Portable Network Graphics (`.png`), +Animated PNG (`.png`/`.apng`), and JPEG XL itself (`.jxl`). + +Specifically for JPEG files, the default `cjxl` behavior is to apply lossless +recompression and the default `djxl` behavior is to reconstruct the original +JPEG file (when the extension of the output file is `.jpg`). ### Benchmarking @@ -135,26 +92,12 @@ benchmarking purposes. For more comprehensive benchmarking options, see the [benchmarking guide](doc/benchmarking.md). -## Advanced guide +### Library API -### Building with Docker +Besides the `libjxl` library [API documentation](https://libjxl.readthedocs.io/en/latest/), +there are [example applications](examples/) and [plugins](plugins/) that can be used as a reference or +starting point for developers who wish to integrate `libjxl` in their project. -We build a common environment based on Debian/Ubuntu using Docker. Other -systems may have different combinations of versions and dependencies that -have not been tested and may not work. For those cases we recommend using the -Docker container as explained in the -[step by step guide](doc/developing_in_docker.md). - -### Building JPEG XL for developers - -For experienced developers, we provide build instructions for several other environments: - -* [Building on Debian](doc/developing_in_debian.md) -* Building on Windows with [vcpkg](doc/developing_in_windows_vcpkg.md) (Visual Studio 2019) -* Building on Windows with [MSYS2](doc/developing_in_windows_msys.md) -* [Cross Compiling for Windows with Crossroad](doc/developing_with_crossroad.md) - -If you encounter any difficulties, please use Docker instead. ## License @@ -187,6 +130,7 @@ format: Cloudinary and Google. * [Test coverage on Codecov.io](https://app.codecov.io/gh/libjxl/libjxl) - for developers * [libjxl documentation on readthedocs.io](https://libjxl.readthedocs.io/) +* The development of jpegli, the improved JPEG encoder and decoder, will continue at https://github.com/google/jpegli ### Contact diff --git a/media/libjxl/src/WORKSPACE b/media/libjxl/src/WORKSPACE new file mode 100644 index 0000000000..d8cd9a7495 --- /dev/null +++ b/media/libjxl/src/WORKSPACE @@ -0,0 +1,37 @@ +# Copyright (c) the JPEG XL Project Authors. All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +workspace(name = "libjxl") + +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository", "new_git_repository") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +local_repository( + name = "highway", + path = "third_party/highway", +) + +local_repository( + name = "brotli", + path = "third_party/brotli", +) + +new_local_repository( + name = "skcms", + build_file_content = """ +cc_library( + name = "skcms", + srcs = [ + "skcms.cc", + "src/skcms_internals.h", + "src/skcms_Transform.h", + "src/Transform_inl.h", + ], + hdrs = ["skcms.h"], + visibility = ["//visibility:public"], +) + """, + path = "third_party/skcms", +) diff --git a/media/libjxl/src/bash_test.sh b/media/libjxl/src/bash_test.sh index 675026ab2e..9472c85970 100644 --- a/media/libjxl/src/bash_test.sh +++ b/media/libjxl/src/bash_test.sh @@ -7,7 +7,8 @@ # Tests implemented in bash. These typically will run checks about the source # code rather than the compiled one. -MYDIR=$(dirname $(realpath "$0")) +SELF=$(realpath "$0") +MYDIR=$(dirname "${SELF}") set -u @@ -18,14 +19,11 @@ test_includes() { if [ ! -e "$f" ]; then continue fi - # Check that the public files (in lib/include/ directory) don't use the full - # path to the public header since users of the library will include the - # library as: #include "jxl/foobar.h". - if [[ "${f#lib/include/}" != "${f}" ]]; then - if grep -i -H -n -E '#include\s*[<"]lib/include/jxl' "$f" >&2; then - echo "Don't add \"include/\" to the include path of public headers." >&2 - ret=1 - fi + # Check that the full paths to the public headers are not used, since users + # of the library will include the library as: #include "jxl/foobar.h". + if grep -i -H -n -E '#include\s*[<"]lib/include/jxl' "$f" >&2; then + echo "Don't add \"include/\" to the include path of public headers." >&2 + ret=1 fi if [[ "${f#third_party/}" == "$f" ]]; then @@ -103,9 +101,9 @@ test_printf_size_t() { ret=1 fi - if grep -n -E 'gmock\.h' \ - $(git ls-files | grep -E '(\.c|\.cc|\.cpp|\.h)$' | grep -v -F /test_utils.h); then - echo "Don't include gmock directly, instead include 'test_utils.h'. " >&2 + if grep -n -E '[^_]gtest\.h' \ + $(git ls-files | grep -E '(\.c|\.cc|\.cpp|\.h)$' | grep -v -F /testing.h); then + echo "Don't include gtest directly, instead include 'testing.h'. " >&2 ret=1 fi @@ -122,7 +120,7 @@ test_printf_size_t() { fi done - for f in $(git ls-files | grep -E "\.h$" | grep -v -E '(printf_macros\.h|test_utils\.h)' | + for f in $(git ls-files | grep -E "\.h$" | grep -v -E '(printf_macros\.h|testing\.h)' | xargs grep -n 'PRI[udx]S'); do # Having PRIuS / PRIdS in a header file means that printf_macros.h may # be included before a system header, in particular before gtest headers. diff --git a/media/libjxl/src/ci.sh b/media/libjxl/src/ci.sh index 45d5218d23..a2ad829cb7 100644 --- a/media/libjxl/src/ci.sh +++ b/media/libjxl/src/ci.sh @@ -5,23 +5,29 @@ # license that can be found in the LICENSE file. # Continuous integration helper module. This module is meant to be called from -# the .gitlab-ci.yml file during the continuous integration build, as well as -# from the command line for developers. +# workflows during the continuous integration build, as well as from the +# command line for developers. set -eu OS=`uname -s` -MYDIR=$(dirname $(realpath "$0")) +SELF=$(realpath "$0") +MYDIR=$(dirname "${SELF}") ### Environment parameters: TEST_STACK_LIMIT="${TEST_STACK_LIMIT:-256}" +BENCHMARK_NUM_THREADS="${BENCHMARK_NUM_THREADS:-0}" +BUILD_CONFIG=${BUILD_CONFIG:-} CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE:-RelWithDebInfo} CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH:-} CMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER:-} CMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER:-} CMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM:-} +SKIP_BUILD="${SKIP_BUILD:-0}" SKIP_TEST="${SKIP_TEST:-0}" +FASTER_MSAN_BUILD="${FASTER_MSAN_BUILD:-0}" +TARGETS="${TARGETS:-all doc}" TEST_SELECTOR="${TEST_SELECTOR:-}" BUILD_TARGET="${BUILD_TARGET:-}" ENABLE_WASM_SIMD="${ENABLE_WASM_SIMD:-0}" @@ -32,6 +38,8 @@ else fi # Whether we should post a message in the MR when the build fails. POST_MESSAGE_ON_ERROR="${POST_MESSAGE_ON_ERROR:-1}" +# By default, do a lightweight debian HWY package build. +HWY_PKG_OPTIONS="${HWY_PKG_OPTIONS:---set-envvar=HWY_EXTRA_CONFIG=-DBUILD_TESTING=OFF -DHWY_ENABLE_EXAMPLES=OFF -DHWY_ENABLE_CONTRIB=OFF}" # Set default compilers to clang if not already set export CC=${CC:-clang} @@ -69,13 +77,24 @@ if [[ "${ENABLE_WASM_SIMD}" -ne "0" ]]; then CMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS} -msimd128" fi +if [[ "${ENABLE_WASM_SIMD}" -eq "2" ]]; then + CMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -DHWY_WANT_WASM2" + CMAKE_C_FLAGS="${CMAKE_C_FLAGS} -DHWY_WANT_WASM2" +fi + +if [[ -z "${BUILD_CONFIG}" ]]; then + TOOLS_DIR="${BUILD_DIR}/tools" +else + TOOLS_DIR="${BUILD_DIR}/tools/${BUILD_CONFIG}" +fi + if [[ ! -z "${HWY_BASELINE_TARGETS}" ]]; then CMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -DHWY_BASELINE_TARGETS=${HWY_BASELINE_TARGETS}" fi # Version inferred from the CI variables. -CI_COMMIT_SHA=${CI_COMMIT_SHA:-${GITHUB_SHA:-}} -JPEGXL_VERSION=${JPEGXL_VERSION:-${CI_COMMIT_SHA:0:8}} +CI_COMMIT_SHA=${GITHUB_SHA:-} +JPEGXL_VERSION=${JPEGXL_VERSION:-} # Benchmark parameters STORE_IMAGES=${STORE_IMAGES:-1} @@ -118,17 +137,34 @@ if [[ "${BUILD_TARGET%%-*}" != "arm" ]]; then ) fi -CLANG_TIDY_BIN=$(which clang-tidy-6.0 clang-tidy-7 clang-tidy-8 clang-tidy | head -n 1) +CLANG_TIDY_BIN_CANDIDATES=( + clang-tidy + clang-tidy-6.0 + clang-tidy-7 + clang-tidy-8 + clang-tidy-9 + clang-tidy-10 + clang-tidy-11 + clang-tidy-12 + clang-tidy-13 + clang-tidy-14 + clang-tidy-15 + clang-tidy-16 + clang-tidy-17 + clang-tidy-18 +) + +CLANG_TIDY_BIN=${CLANG_TIDY_BIN:-$(which ${CLANG_TIDY_BIN_CANDIDATES[@]} 2>/dev/null | tail -n 1)} # Default to "cat" if "colordiff" is not installed or if stdout is not a tty. if [[ -t 1 ]]; then - COLORDIFF_BIN=$(which colordiff cat | head -n 1) + COLORDIFF_BIN=$(which colordiff cat 2>/dev/null | head -n 1) else COLORDIFF_BIN="cat" fi -FIND_BIN=$(which gfind find | head -n 1) +FIND_BIN=$(which gfind find 2>/dev/null | head -n 1) # "false" will disable wine64 when not installed. This won't allow # cross-compiling. -WINE_BIN=$(which wine64 false | head -n 1) +WINE_BIN=$(which wine64 false 2>/dev/null | head -n 1) CLANG_VERSION="${CLANG_VERSION:-}" # Detect the clang version suffix and store it in CLANG_VERSION. For example, @@ -139,6 +175,7 @@ detect_clang_version() { fi local clang_version=$("${CC:-clang}" --version | head -n1) clang_version=${clang_version#"Debian "} + clang_version=${clang_version#"Ubuntu "} local llvm_tag case "${clang_version}" in "clang version 6."*) @@ -171,27 +208,6 @@ on_exit() { local retcode="$1" # Always cleanup the CLEANUP_FILES. cleanup - - # Post a message in the MR when requested with POST_MESSAGE_ON_ERROR but only - # if the run failed and we are not running from a MR pipeline. - if [[ ${retcode} -ne 0 && -n "${CI_BUILD_NAME:-}" && - -n "${POST_MESSAGE_ON_ERROR}" && -z "${CI_MERGE_REQUEST_ID:-}" && - "${CI_BUILD_REF_NAME}" = "master" ]]; then - load_mr_vars_from_commit - { set +xeu; } 2>/dev/null - local message="**Run ${CI_BUILD_NAME} @ ${CI_COMMIT_SHORT_SHA} failed.** - -Check the output of the job at ${CI_JOB_URL:-} to see if this was your problem. -If it was, please rollback this change or fix the problem ASAP, broken builds -slow down development. Check if the error already existed in the previous build -as well. - -Pipeline: ${CI_PIPELINE_URL} - -Previous build commit: ${CI_COMMIT_BEFORE_SHA} -" - cmd_post_mr_comment "${message}" - fi } trap 'retcode=$?; { set +x; } 2>/dev/null; on_exit ${retcode}' INT TERM EXIT @@ -203,7 +219,7 @@ trap 'retcode=$?; { set +x; } 2>/dev/null; on_exit ${retcode}' INT TERM EXIT # 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. @@ -216,30 +232,23 @@ merge_request_commits() { # changes on the Pull Request if needed. This fetches 10 more commits which # should be enough given that PR normally should have 1 commit. git -C "${MYDIR}" fetch -q origin "${GITHUB_SHA}" --depth 10 - MR_HEAD_SHA="$(git rev-parse "FETCH_HEAD^2" 2>/dev/null || + if [ "${GITHUB_EVENT_NAME}" = "pull_request" ]; then + MR_HEAD_SHA="$(git rev-parse "FETCH_HEAD^2" 2>/dev/null || echo "${GITHUB_SHA}")" + else + MR_HEAD_SHA="${GITHUB_SHA}" + fi else - # CI_BUILD_REF is the reference currently being build in the CI workflow. - MR_HEAD_SHA=$(git -C "${MYDIR}" rev-parse -q "${CI_BUILD_REF:-HEAD}") + MR_HEAD_SHA=$(git -C "${MYDIR}" rev-parse -q "HEAD") fi - if [[ -n "${CI_MERGE_REQUEST_IID:-}" ]]; then - # Merge request pipeline in CI. In this case the upstream is called "origin" - # but it refers to the forked project that's the source of the merge - # request. We need to get the target of the merge request, for which we need - # to query that repository using our CI_JOB_TOKEN. - echo "machine gitlab.com login gitlab-ci-token password ${CI_JOB_TOKEN}" \ - >> "${HOME}/.netrc" - git -C "${MYDIR}" fetch "${CI_MERGE_REQUEST_PROJECT_URL}" \ - "${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}" - MR_ANCESTOR_SHA=$(git -C "${MYDIR}" rev-parse -q FETCH_HEAD) - elif [[ -n "${GITHUB_BASE_REF:-}" ]]; then + if [[ -n "${GITHUB_BASE_REF:-}" ]]; then # Pull request workflow in GitHub Actions. GitHub checkout action uses # "origin" as the remote for the git checkout. git -C "${MYDIR}" fetch -q origin "${GITHUB_BASE_REF}" MR_ANCESTOR_SHA=$(git -C "${MYDIR}" rev-parse -q FETCH_HEAD) else - # We are in a local branch, not a merge request. + # We are in a local branch, not a pull request workflow. MR_ANCESTOR_SHA=$(git -C "${MYDIR}" rev-parse -q HEAD@{upstream} || true) fi @@ -258,40 +267,6 @@ merge_request_commits() { set -x } -# Load the MR iid from the landed commit message when running not from a -# merge request workflow. This is useful to post back results at the merge -# request when running pipelines from master. -load_mr_vars_from_commit() { - { set +x; } 2>/dev/null - if [[ -z "${CI_MERGE_REQUEST_IID:-}" ]]; then - local mr_iid=$(git rev-list --format=%B --max-count=1 HEAD | - grep -F "${CI_PROJECT_URL}" | grep -F "/merge_requests" | head -n 1) - # mr_iid contains a string like this if it matched: - # Part-of: - if [[ -n "${mr_iid}" ]]; then - mr_iid=$(echo "${mr_iid}" | - sed -E 's,^.*merge_requests/([0-9]+)>.*$,\1,') - CI_MERGE_REQUEST_IID="${mr_iid}" - CI_MERGE_REQUEST_PROJECT_ID=${CI_PROJECT_ID} - fi - fi - set -x -} - -# Posts a comment to the current merge request. -cmd_post_mr_comment() { - { set +x; } 2>/dev/null - local comment="$1" - if [[ -n "${BOT_TOKEN:-}" && -n "${CI_MERGE_REQUEST_IID:-}" ]]; then - local url="${CI_API_V4_URL}/projects/${CI_MERGE_REQUEST_PROJECT_ID}/merge_requests/${CI_MERGE_REQUEST_IID}/notes" - curl -X POST -g \ - -H "PRIVATE-TOKEN: ${BOT_TOKEN}" \ - --data-urlencode "body=${comment}" \ - --output /dev/null \ - "${url}" - fi - set -x -} # Set up and export the environment variables needed by the child processes. export_env() { @@ -452,14 +427,17 @@ cmake_configure() { } cmake_build_and_test() { + if [[ "${SKIP_BUILD}" -eq "1" ]]; then + return 0 + fi # gtest_discover_tests() runs the test binaries to discover the list of tests # at build time, which fails under qemu. - ASAN_OPTIONS=detect_leaks=0 cmake --build "${BUILD_DIR}" -- all doc + ASAN_OPTIONS=detect_leaks=0 cmake --build "${BUILD_DIR}" -- $TARGETS # Pack test binaries if requested. if [[ "${PACK_TEST:-}" == "1" ]]; then (cd "${BUILD_DIR}" ${FIND_BIN} -name '*.cmake' -a '!' -path '*CMakeFiles*' - # gtest / gmock / gtest_main shared libs + # gtest / gtest_main shared libs ${FIND_BIN} lib/ -name 'libg*.so*' ${FIND_BIN} -type d -name tests -a '!' -path '*CMakeFiles*' ) | tar -C "${BUILD_DIR}" -cf "${BUILD_DIR}/tests.tar.xz" -T - \ @@ -507,7 +485,7 @@ strip_dead_code() { ### Externally visible commands cmd_debug() { - CMAKE_BUILD_TYPE="Debug" + CMAKE_BUILD_TYPE="DebugOpt" cmake_configure "$@" cmake_build_and_test } @@ -521,7 +499,7 @@ cmd_release() { cmd_opt() { CMAKE_BUILD_TYPE="RelWithDebInfo" - CMAKE_CXX_FLAGS+=" -DJXL_DEBUG_WARNING -DJXL_DEBUG_ON_ERROR" + CMAKE_CXX_FLAGS+=" -DJXL_IS_DEBUG_BUILD" cmake_configure "$@" cmake_build_and_test } @@ -547,6 +525,7 @@ cmd_coverage_report() { # Only print coverage information for the libjxl directories. The rest # is not part of the code under test. --filter '.*jxl/.*' + --exclude '.*_gbench.cc' --exclude '.*_test.cc' --exclude '.*_testonly..*' --exclude '.*_debug.*' @@ -576,7 +555,7 @@ cmd_test() { (cd "${BUILD_DIR}" export UBSAN_OPTIONS=print_stacktrace=1 [[ "${TEST_STACK_LIMIT}" == "none" ]] || ulimit -s "${TEST_STACK_LIMIT}" - ctest -j $(nproc --all || echo 1) --output-on-failure "$@") + ctest -j $(nproc --all || echo 1) ${TEST_SELECTOR} --output-on-failure "$@") } cmd_gbench() { @@ -600,6 +579,7 @@ cmd_msanfuzz() { # Install msan if needed before changing the flags. detect_clang_version local msan_prefix="${HOME}/.msan/${CLANG_VERSION}" + # TODO(eustas): why libc++abi.a is bad? if [[ ! -d "${msan_prefix}" || -e "${msan_prefix}/lib/libc++abi.a" ]]; then # Install msan libraries for this version if needed or if an older version # with libc++abi was installed. @@ -613,9 +593,9 @@ cmd_msanfuzz() { cmd_asan() { SANITIZER="asan" - CMAKE_C_FLAGS+=" -DJXL_ENABLE_ASSERT=1 -g -DADDRESS_SANITIZER \ + CMAKE_C_FLAGS+=" -g -DADDRESS_SANITIZER \ -fsanitize=address ${UBSAN_FLAGS[@]}" - CMAKE_CXX_FLAGS+=" -DJXL_ENABLE_ASSERT=1 -g -DADDRESS_SANITIZER \ + CMAKE_CXX_FLAGS+=" -g -DADDRESS_SANITIZER \ -fsanitize=address ${UBSAN_FLAGS[@]}" strip_dead_code cmake_configure "$@" -DJPEGXL_ENABLE_TCMALLOC=OFF @@ -625,16 +605,13 @@ cmd_asan() { cmd_tsan() { SANITIZER="tsan" local tsan_args=( - -DJXL_ENABLE_ASSERT=1 -g -DTHREAD_SANITIZER - ${UBSAN_FLAGS[@]} -fsanitize=thread ) CMAKE_C_FLAGS+=" ${tsan_args[@]}" CMAKE_CXX_FLAGS+=" ${tsan_args[@]}" - CMAKE_BUILD_TYPE="RelWithDebInfo" cmake_configure "$@" -DJPEGXL_ENABLE_TCMALLOC=OFF cmake_build_and_test } @@ -652,15 +629,20 @@ cmd_msan() { local msan_c_flags=( -fsanitize=memory -fno-omit-frame-pointer - -fsanitize-memory-track-origins - -DJXL_ENABLE_ASSERT=1 -g -DMEMORY_SANITIZER # Force gtest to not use the cxxbai. -DGTEST_HAS_CXXABI_H_=0 ) + if [[ "${FASTER_MSAN_BUILD}" -ne "1" ]]; then + msan_c_flags=( + "${msan_c_flags[@]}" + -fsanitize-memory-track-origins + ) + fi + local msan_cxx_flags=( "${msan_c_flags[@]}" @@ -680,16 +662,22 @@ cmd_msan() { -Wl,-rpath -Wl,"${msan_prefix}"/lib/ ) - CMAKE_C_FLAGS+=" ${msan_c_flags[@]} ${UBSAN_FLAGS[@]}" - CMAKE_CXX_FLAGS+=" ${msan_cxx_flags[@]} ${UBSAN_FLAGS[@]}" + CMAKE_C_FLAGS+=" ${msan_c_flags[@]}" + CMAKE_CXX_FLAGS+=" ${msan_cxx_flags[@]}" CMAKE_EXE_LINKER_FLAGS+=" ${msan_linker_flags[@]}" CMAKE_MODULE_LINKER_FLAGS+=" ${msan_linker_flags[@]}" CMAKE_SHARED_LINKER_FLAGS+=" ${msan_linker_flags[@]}" strip_dead_code + + # MSAN share of stack size is non-negligible. + TEST_STACK_LIMIT="none" + + # TODO(eustas): investigate why fuzzers do not link when MSAN libc++ is used cmake_configure "$@" \ -DCMAKE_CROSSCOMPILING=1 -DRUN_HAVE_STD_REGEX=0 -DRUN_HAVE_POSIX_REGEX=0 \ -DJPEGXL_ENABLE_TCMALLOC=OFF -DJPEGXL_WARNINGS_AS_ERRORS=OFF \ - -DCMAKE_REQUIRED_LINK_OPTIONS="${msan_linker_flags[@]}" + -DCMAKE_REQUIRED_LINK_OPTIONS="${msan_linker_flags[@]}" \ + -DJPEGXL_ENABLE_FUZZERS=OFF cmake_build_and_test } @@ -698,6 +686,8 @@ cmd_msan() { cmd_msan_install() { local tmpdir=$(mktemp -d) CLEANUP_FILES+=("${tmpdir}") + local msan_root="${HOME}/.msan" + mkdir -p "${msan_root}" # Detect the llvm to install: export CC="${CC:-clang}" export CXX="${CXX:-clang++}" @@ -705,50 +695,70 @@ cmd_msan_install() { # Allow overriding the LLVM checkout. local llvm_root="${LLVM_ROOT:-}" if [ -z "${llvm_root}" ]; then - local llvm_tag="llvmorg-${CLANG_VERSION}.0.0" - case "${CLANG_VERSION}" in - "6.0") - llvm_tag="llvmorg-6.0.1" - ;; - "7") - llvm_tag="llvmorg-7.0.1" - ;; - esac - local llvm_targz="${tmpdir}/${llvm_tag}.tar.gz" - curl -L --show-error -o "${llvm_targz}" \ - "https://github.com/llvm/llvm-project/archive/${llvm_tag}.tar.gz" + declare -A llvm_tag_by_version=( + ["6.0"]="6.0.1" + ["7"]="7.1.0" + ["8"]="8.0.1" + ["9"]="9.0.2" + ["10"]="10.0.1" + ["11"]="11.1.0" + ["12"]="12.0.1" + ["13"]="13.0.1" + ["14"]="14.0.6" + ["15"]="15.0.7" + ["16"]="16.0.6" + ["17"]="17.0.6" + ["18"]="18.1.6" + ) + local llvm_tag="${CLANG_VERSION}.0.0" + if [[ -n "${llvm_tag_by_version["${CLANG_VERSION}"]}" ]]; then + llvm_tag=${llvm_tag_by_version["${CLANG_VERSION}"]} + fi + llvm_tag="llvmorg-${llvm_tag}" + local llvm_targz="${msan_root}/${llvm_tag}.tar.gz" + if [ ! -f "${llvm_targz}" ]; then + curl -L --show-error -o "${llvm_targz}" \ + "https://github.com/llvm/llvm-project/archive/${llvm_tag}.tar.gz" + fi tar -C "${tmpdir}" -zxf "${llvm_targz}" llvm_root="${tmpdir}/llvm-project-${llvm_tag}" fi - local msan_prefix="${HOME}/.msan/${CLANG_VERSION}" + local msan_prefix="${msan_root}/${CLANG_VERSION}" rm -rf "${msan_prefix}" - declare -A CMAKE_EXTRAS - CMAKE_EXTRAS[libcxx]="\ - -DLIBCXX_CXX_ABI=libstdc++ \ - -DLIBCXX_INSTALL_EXPERIMENTAL_LIBRARY=ON" + local TARGET_OPTS="" + if [[ -n "${BUILD_TARGET}" ]]; then + TARGET_OPTS=" \ + -DCMAKE_C_COMPILER_TARGET=\"${BUILD_TARGET}\" \ + -DCMAKE_CXX_COMPILER_TARGET=\"${BUILD_TARGET}\" \ + -DCMAKE_SYSTEM_PROCESSOR=\"${BUILD_TARGET%%-*}\" \ + " + fi - for project in libcxx; do - local proj_build="${tmpdir}/build-${project}" - local proj_dir="${llvm_root}/${project}" - mkdir -p "${proj_build}" - cmake -B"${proj_build}" -H"${proj_dir}" \ - -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DLLVM_USE_SANITIZER=Memory \ - -DLLVM_PATH="${llvm_root}/llvm" \ - -DLLVM_CONFIG_PATH="$(which llvm-config llvm-config-7 llvm-config-6.0 | \ - head -n1)" \ - -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS}" \ - -DCMAKE_C_FLAGS="${CMAKE_C_FLAGS}" \ - -DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" \ - -DCMAKE_SHARED_LINKER_FLAGS="${CMAKE_SHARED_LINKER_FLAGS}" \ - -DCMAKE_INSTALL_PREFIX="${msan_prefix}" \ - ${CMAKE_EXTRAS[${project}]} - cmake --build "${proj_build}" - ninja -C "${proj_build}" install - done + local build_dir="${tmpdir}/build-llvm" + mkdir -p "${build_dir}" + cd ${llvm_root} + cmake -B"${build_dir}" \ + -G Ninja \ + -S runtimes \ + -DCMAKE_BUILD_TYPE=Release \ + -DLLVM_USE_SANITIZER=Memory \ + -DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi;libunwind;compiler-rt" \ + -DLIBCXXABI_ENABLE_SHARED=ON \ + -DLIBCXXABI_ENABLE_STATIC=OFF \ + -DLIBCXX_ENABLE_SHARED=ON \ + -DLIBCXX_ENABLE_STATIC=OFF \ + -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS}" \ + -DCMAKE_C_FLAGS="${CMAKE_C_FLAGS}" \ + -DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" \ + -DCMAKE_SHARED_LINKER_FLAGS="${CMAKE_SHARED_LINKER_FLAGS}" \ + -DCMAKE_INSTALL_PREFIX="${msan_prefix}" \ + -DLLVM_PATH="${llvm_root}/llvm" \ + -DLLVM_CONFIG_PATH="$(which llvm-config-${CLANG_VERSION} llvm-config | head -n1)" \ + ${TARGET_OPTS} + cmake --build "${build_dir}" + ninja -C "${build_dir}" install } # Internal build step shared between all cmd_ossfuzz_* commands. @@ -788,7 +798,7 @@ _cmd_ossfuzz() { -e MSAN_LIBS_PATH="/work/msan" \ -e JPEGXL_EXTRA_ARGS="${jpegxl_extra_args}" \ -v "${MYDIR}":/src/libjxl \ - -v "${MYDIR}/tools/ossfuzz-build.sh":/src/build.sh \ + -v "${MYDIR}/tools/scripts/ossfuzz-build.sh":/src/build.sh \ -v "${real_build_dir}":/work \ gcr.io/oss-fuzz/libjxl } @@ -822,9 +832,13 @@ cmd_ossfuzz_ninja() { cmd_fast_benchmark() { local small_corpus_tar="${BENCHMARK_CORPORA}/jyrki-full.tar" + local small_corpus_url="https://storage.googleapis.com/artifacts.jpegxl.appspot.com/corpora/jyrki-full.tar" mkdir -p "${BENCHMARK_CORPORA}" - curl --show-error -o "${small_corpus_tar}" -z "${small_corpus_tar}" \ - "https://storage.googleapis.com/artifacts.jpegxl.appspot.com/corpora/jyrki-full.tar" + if [ -f "${small_corpus_tar}" ]; then + curl --show-error -o "${small_corpus_tar}" -z "${small_corpus_tar}" "${small_corpus_url}" + else + curl --show-error -o "${small_corpus_tar}" "${small_corpus_url}" + fi local tmpdir=$(mktemp -d) CLEANUP_FILES+=("${tmpdir}") @@ -862,7 +876,7 @@ cmd_benchmark() { png_filename="${filename%.ppm}.png" png_filename=$(echo "${png_filename}" | tr '/' '_') sem --bg --id "${sem_id}" -j"${nprocs}" -- \ - "${BUILD_DIR}/tools/decode_and_encode" \ + "${TOOLS_DIR}/decode_and_encode" \ "${tmpdir}/${filename}" "${mode}" "${tmpdir}/${png_filename}" images+=( "${png_filename}" ) done < <(cd "${tmpdir}"; ${FIND_BIN} . -name '*.ppm' -type f) @@ -875,6 +889,8 @@ cmd_benchmark() { get_mem_available() { if [[ "${OS}" == "Darwin" ]]; then echo $(vm_stat | grep -F 'Pages free:' | awk '{print $3 * 4}') + elif [[ "${OS}" == MINGW* ]]; then + echo $(vmstat | tail -n 1 | awk '{print $4 * 4}') else echo $(grep -F MemAvailable: /proc/meminfo | awk '{print $2}') fi @@ -887,45 +903,46 @@ run_benchmark() { local output_dir="${BUILD_DIR}/benchmark_results" mkdir -p "${output_dir}" - # The memory available at the beginning of the benchmark run in kB. The number - # of threads depends on the available memory, and the passed memory per - # thread. We also add a 2 GiB of constant memory. - local mem_available="$(get_mem_available)" - # Check that we actually have a MemAvailable value. - [[ -n "${mem_available}" ]] - local num_threads=$(( (${mem_available} - 1048576) / ${mem_per_thread} )) - if [[ ${num_threads} -le 0 ]]; then - num_threads=1 + if [[ "${OS}" == MINGW* ]]; then + src_img_dir=`cygpath -w "${src_img_dir}"` + fi + + local num_threads=1 + if [[ ${BENCHMARK_NUM_THREADS} -gt 0 ]]; then + num_threads=${BENCHMARK_NUM_THREADS} + else + # The memory available at the beginning of the benchmark run in kB. The number + # of threads depends on the available memory, and the passed memory per + # thread. We also add a 2 GiB of constant memory. + local mem_available="$(get_mem_available)" + # Check that we actually have a MemAvailable value. + [[ -n "${mem_available}" ]] + num_threads=$(( (${mem_available} - 1048576) / ${mem_per_thread} )) + if [[ ${num_threads} -le 0 ]]; then + num_threads=1 + fi fi local benchmark_args=( --input "${src_img_dir}/*.png" --codec=jpeg:yuv420:q85,webp:q80,jxl:d1:6,jxl:d1:6:downsampling=8,jxl:d5:6,jxl:d5:6:downsampling=8,jxl:m:d0:2,jxl:m:d0:3,jxl:m:d2:2 --output_dir "${output_dir}" - --noprofiler --show_progress + --show_progress --num_threads="${num_threads}" + --decode_reps=11 + --encode_reps=11 ) if [[ "${STORE_IMAGES}" == "1" ]]; then benchmark_args+=(--save_decompressed --save_compressed) fi ( [[ "${TEST_STACK_LIMIT}" == "none" ]] || ulimit -s "${TEST_STACK_LIMIT}" - "${BUILD_DIR}/tools/benchmark_xl" "${benchmark_args[@]}" | \ + "${TOOLS_DIR}/benchmark_xl" "${benchmark_args[@]}" | \ tee "${output_dir}/results.txt" - # Check error code for benckmark_xl command. This will exit if not. + # Check error code for benchmark_xl command. This will exit if not. return ${PIPESTATUS[0]} ) - - if [[ -n "${CI_BUILD_NAME:-}" ]]; then - { set +x; } 2>/dev/null - local message="Results for ${CI_BUILD_NAME} @ ${CI_COMMIT_SHORT_SHA} (job ${CI_JOB_URL:-}): - -$(cat "${output_dir}/results.txt") -" - cmd_post_mr_comment "${message}" - set -x - fi } # Helper function to wait for the CPU temperature to cool down on ARM. @@ -1066,7 +1083,7 @@ cmd_arm_benchmark() { local src_img for src_img in "${jpg_images[@]}" "${images[@]}"; do local src_img_hash=$(sha1sum "${src_img}" | cut -f 1 -d ' ') - local enc_binaries=("${BUILD_DIR}/tools/cjxl") + local enc_binaries=("${TOOLS_DIR}/cjxl") local src_ext="${src_img##*.}" for enc_binary in "${enc_binaries[@]}"; do local enc_binary_base=$(basename "${enc_binary}") @@ -1115,7 +1132,7 @@ cmd_arm_benchmark() { local dec_output wait_for_temp - dec_output=$("${BUILD_DIR}/tools/djxl" "${enc_file}" \ + dec_output=$("${TOOLS_DIR}/djxl" "${enc_file}" \ --num_reps=5 --num_threads="${num_threads}" 2>&1 | tee /dev/stderr | grep -E "M[BP]/s \[") local img_size=$(echo "${dec_output}" | cut -f 1 -d ',') @@ -1131,7 +1148,7 @@ cmd_arm_benchmark() { if [[ "${src_ext}" == "jpg" ]]; then wait_for_temp local dec_file="${BUILD_DIR}/arm_benchmark/${enc_file_hash}.jpg" - dec_output=$("${BUILD_DIR}/tools/djxl" "${enc_file}" \ + dec_output=$("${TOOLS_DIR}/djxl" "${enc_file}" \ "${dec_file}" --num_reps=5 --num_threads="${num_threads}" 2>&1 | \ tee /dev/stderr | grep -E "M[BP]/s \[") local jpeg_dec_mps_speed=$(_speed_from_output "${dec_output}") @@ -1153,18 +1170,6 @@ cmd_arm_benchmark() { cmd_cpuset "${RUNNER_CPU_ALL:-}" cat "${runs_file}" - if [[ -n "${CI_BUILD_NAME:-}" ]]; then - load_mr_vars_from_commit - { set +x; } 2>/dev/null - local message="Results for ${CI_BUILD_NAME} @ ${CI_COMMIT_SHORT_SHA} (job ${CI_JOB_URL:-}): - -\`\`\` -$(column -t -s " " "${runs_file}") -\`\`\` -" - cmd_post_mr_comment "${message}" - set -x - fi } # Generate a corpus and run the fuzzer on that corpus. @@ -1173,33 +1178,58 @@ cmd_fuzz() { local fuzzer_crash_dir=$(realpath "${BUILD_DIR}/fuzzer_crash") mkdir -p "${corpus_dir}" "${fuzzer_crash_dir}" # Generate step. - "${BUILD_DIR}/tools/fuzzer_corpus" "${corpus_dir}" + "${TOOLS_DIR}/fuzzer_corpus" "${corpus_dir}" # Run step: local nprocs=$(nproc --all || echo 1) ( - cd "${BUILD_DIR}" - "tools/djxl_fuzzer" "${fuzzer_crash_dir}" "${corpus_dir}" \ + cd "${TOOLS_DIR}" + djxl_fuzzer "${fuzzer_crash_dir}" "${corpus_dir}" \ -max_total_time="${FUZZER_MAX_TIME}" -jobs=${nprocs} \ -artifact_prefix="${fuzzer_crash_dir}/" ) } -# Runs the linter (clang-format) on the pending CLs. +# Runs the linters (clang-format, build_cleaner, buildirier) on the pending CLs. cmd_lint() { merge_request_commits { set +x; } 2>/dev/null - local versions=(${1:-6.0 7 8 9 10 11}) + local versions=(${1:-16 15 14 13 12 11 10 9 8 7 6.0}) local clang_format_bins=("${versions[@]/#/clang-format-}" clang-format) local tmpdir=$(mktemp -d) CLEANUP_FILES+=("${tmpdir}") local ret=0 local build_patch="${tmpdir}/build_cleaner.patch" - if ! "${MYDIR}/tools/build_cleaner.py" >"${build_patch}"; then + if ! "${MYDIR}/tools/scripts/build_cleaner.py" >"${build_patch}"; then ret=1 echo "build_cleaner.py findings:" >&2 "${COLORDIFF_BIN}" <"${build_patch}" - echo "Run \`tools/build_cleaner.py --update\` to apply them" >&2 + echo "Run \`tools/scripts/build_cleaner.py --update\` to apply them" >&2 + fi + + # It is ok, if buildifier is not installed. + if which buildifier >/dev/null; then + local buildifier_patch="${tmpdir}/buildifier.patch" + local bazel_files=`git -C "${MYDIR}" ls-files | grep -E "/BUILD$|WORKSPACE|.bzl$"` + set -x + buildifier -d ${bazel_files} >"${buildifier_patch}"|| true + { set +x; } 2>/dev/null + if [ -s "${buildifier_patch}" ]; then + ret=1 + echo 'buildifier have found some problems in Bazel build files:' >&2 + "${COLORDIFF_BIN}" <"${buildifier_patch}" + echo 'To fix them run (from the base directory):' >&2 + echo ' buildifier `git ls-files | grep -E "/BUILD$|WORKSPACE|.bzl$"`' >&2 + fi + fi + + # It is ok, if spell-checker is not installed. + if which typos >/dev/null; then + local src_ext="bazel|bzl|c|cc|cmake|gni|h|html|in|java|js|m|md|nix|py|rst|sh|ts|txt|yaml|yml" + local sources=`git -C "${MYDIR}" ls-files | grep -E "\.(${src_ext})$"` + typos -c "${MYDIR}/tools/scripts/typos.toml" ${sources} + else + echo "Consider installing https://github.com/crate-ci/typos for spell-checking" fi local installed=() @@ -1218,7 +1248,7 @@ cmd_lint() { git -C "${MYDIR}" "${clang_format}" --binary "${clang_format}" \ --style=file --diff "${MR_ANCESTOR_SHA}" -- >"${tmppatch}" || true { set +x; } 2>/dev/null - if grep -E '^--- ' "${tmppatch}">/dev/null; then + if grep -E '^--- ' "${tmppatch}" | grep -v 'a/third_party' >/dev/null; then if [[ -n "${LINT_OUTPUT:-}" ]]; then cp "${tmppatch}" "${LINT_OUTPUT}" fi @@ -1326,6 +1356,7 @@ cmd_debian_stats() { build_debian_pkg() { local srcdir="$1" local srcpkg="$2" + local options="${3:-}" local debsdir="${BUILD_DIR}/debs" local builddir="${debsdir}/${srcpkg}" @@ -1341,7 +1372,7 @@ build_debian_pkg() { done ( cd "${builddir}" - debuild -b -uc -us + debuild "${options}" -b -uc -us ) } @@ -1353,7 +1384,7 @@ cmd_debian_build() { build_debian_pkg "${MYDIR}" "jpeg-xl" ;; highway) - build_debian_pkg "${MYDIR}/third_party/highway" "highway" + build_debian_pkg "${MYDIR}/third_party/highway" "highway" "${HWY_PKG_OPTIONS}" ;; *) echo "ERROR: Must pass a valid source package name to build." >&2 @@ -1374,7 +1405,7 @@ cmd_bump_version() { local newver="${1:-}" if ! which dch >/dev/null; then - echo "Run:\n sudo apt install debhelper" + echo "Missing dch\nTo install it run:\n sudo apt install devscripts" exit 1 fi @@ -1402,9 +1433,13 @@ cmd_bump_version() { -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}\"/" \ + -i .github/workflows/conformance.yml # Update lib.gni - tools/build_cleaner.py --update + tools/scripts/build_cleaner.py --update # Mark the previous version as "unstable". DEBCHANGE_RELEASE_HEURISTIC=log dch -M --distribution unstable --release '' @@ -1418,11 +1453,11 @@ cmd_authors() { merge_request_commits local emails local names - readarray -t emails < <(git log --format='%ae' "${MR_HEAD_SHA}...${MR_ANCESTOR_SHA}") - readarray -t names < <(git log --format='%an' "${MR_HEAD_SHA}...${MR_ANCESTOR_SHA}") + readarray -t emails < <(git log --format='%ae' "${MR_ANCESTOR_SHA}..${MR_HEAD_SHA}") + readarray -t names < <(git log --format='%an' "${MR_ANCESTOR_SHA}..${MR_HEAD_SHA}") for i in "${!names[@]}"; do echo "Checking name '${names[$i]}' with email '${emails[$i]}' ..." - "${MYDIR}"/tools/check_author.py "${emails[$i]}" "${names[$i]}" + "${MYDIR}"/tools/scripts/check_author.py "${emails[$i]}" "${names[$i]}" done } @@ -1450,7 +1485,7 @@ Where cmd is one of: benchmark Run the benchmark over the default corpus. fast_benchmark Run the benchmark over the small corpus. - coverage Buils and run tests with coverage support. Runs coverage_report as + coverage Build and run tests with coverage support. Runs coverage_report as well. coverage_report Generate HTML, XML and text coverage report after a coverage run. @@ -1483,6 +1518,7 @@ You can pass some optional environment variables as well: - FUZZER_MAX_TIME: "fuzz" command fuzzer running timeout in seconds. - LINT_OUTPUT: Path to the output patch from the "lint" command. - SKIP_CPUSET=1: Skip modifying the cpuset in the arm_benchmark. + - SKIP_BUILD=1: Skip the build stage, cmake configure only. - SKIP_TEST=1: Skip the test stage. - STORE_IMAGES=0: Makes the benchmark discard the computed images. - TEST_STACK_LIMIT: Stack size limit (ulimit -s) during tests, in KiB. diff --git a/media/libjxl/src/cmake/FindBrotli.cmake b/media/libjxl/src/cmake/FindBrotli.cmake index 5c6cb09870..9fb78e47d8 100644 --- a/media/libjxl/src/cmake/FindBrotli.cmake +++ b/media/libjxl/src/cmake/FindBrotli.cmake @@ -31,11 +31,6 @@ foreach(brlib IN ITEMS ${brlibs}) set_property(TARGET ${brlib} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${BROTLI_INCLUDE_DIR}) target_link_libraries(${brlib} INTERFACE ${${BRPREFIX}_LIBRARY}) set_property(TARGET ${brlib} PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_${BRPREFIX}_CFLAGS_OTHER}) - - add_library(${brlib}-static INTERFACE IMPORTED GLOBAL) - set_property(TARGET ${brlib}-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${BROTLI_INCLUDE_DIR}) - target_link_libraries(${brlib}-static INTERFACE ${${BRPREFIX}_LIBRARY}) - set_property(TARGET ${brlib}-static PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_${BRPREFIX}_CFLAGS_OTHER}) else() add_library(${brlib} INTERFACE IMPORTED GLOBAL) target_include_directories(${brlib} @@ -46,11 +41,6 @@ foreach(brlib IN ITEMS ${brlibs}) INTERFACE ${PC_${BRPREFIX}_LDFLAGS_OTHER}) target_compile_options(${brlib} INTERFACE ${PC_${BRPREFIX}_CFLAGS_OTHER}) - - # TODO(deymo): Remove the -static library versions, this target is - # currently needed by brunsli.cmake. When importing it this way, the - # brotli*-static target is just an alias. - add_library(${brlib}-static ALIAS ${brlib}) endif() endif() endforeach() diff --git a/media/libjxl/src/cmake/FindHWY.cmake b/media/libjxl/src/cmake/FindHWY.cmake index c1deb9b851..eab306a8b7 100644 --- a/media/libjxl/src/cmake/FindHWY.cmake +++ b/media/libjxl/src/cmake/FindHWY.cmake @@ -10,7 +10,7 @@ if (PkgConfig_FOUND) endif () find_path(HWY_INCLUDE_DIR - NAMES hwy/highway.h + NAMES hwy/base.h hwy/highway.h HINTS ${PC_HWY_INCLUDEDIR} ${PC_HWY_INCLUDE_DIRS} ) @@ -19,21 +19,31 @@ find_library(HWY_LIBRARY HINTS ${PC_HWY_LIBDIR} ${PC_HWY_LIBRARY_DIRS} ) +# If version not found using pkg-config, try extracting it from header files 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) + set(HWY_VERSION "") + set(HWY_POSSIBLE_HEADERS "${HWY_INCLUDE_DIR}/hwy/base.h" "${HWY_INCLUDE_DIR}/hwy/highway.h") + foreach(HWY_HEADER_FILE IN LISTS HWY_POSSIBLE_HEADERS) + if (EXISTS "${HWY_HEADER_FILE}") + file(READ "${HWY_HEADER_FILE}" HWY_VERSION_CONTENT) - string(REGEX MATCH "#define HWY_MAJOR +([0-9]+)" _dummy "${HWY_VERSION_CONTENT}") - set(HWY_VERSION_MAJOR "${CMAKE_MATCH_1}") + 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}") - set(HWY_VERSION_MINOR "${CMAKE_MATCH_1}") + 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}") - set(HWY_VERSION_PATCH "${CMAKE_MATCH_1}") - - set(HWY_VERSION "${HWY_VERSION_MAJOR}.${HWY_VERSION_MINOR}.${HWY_VERSION_PATCH}") - endif () + string(REGEX MATCH "#define +HWY_PATCH +([0-9]+)" _sink "${HWY_VERSION_CONTENT}") + set(HWY_VERSION_PATCH "${CMAKE_MATCH_1}") + if (NOT HWY_VERSION_MAJOR STREQUAL "" AND NOT HWY_VERSION_MINOR STREQUAL "" AND NOT HWY_VERSION_PATCH STREQUAL "") + set(HWY_VERSION "${HWY_VERSION_MAJOR}.${HWY_VERSION_MINOR}.${HWY_VERSION_PATCH}") + break() + endif() + endif () + endforeach () + if (NOT HWY_VERSION) + message(WARNING "Highway version not found.") + endif() endif () include(FindPackageHandleStandardArgs) diff --git a/media/libjxl/src/debian/changelog b/media/libjxl/src/debian/changelog index a63607e5e5..503d8f2165 100644 --- a/media/libjxl/src/debian/changelog +++ b/media/libjxl/src/debian/changelog @@ -1,4 +1,46 @@ -jpeg-xl (0.7) UNRELEASED; urgency=medium +jpeg-xl (0.11.1) UNRELEASED; urgency=medium + + * Bump JPEG XL version to 0.11.1. + + -- JPEG XL Maintainers Tue, 26 Nov 2024 12:09:23 +0100 + +jpeg-xl (0.11.0) unstable; urgency=medium + + * Bump JPEG XL version to 0.11.0. + + -- JPEG XL Maintainers Tue, 06 Aug 2024 14:35:34 +0200 + +jpeg-xl (0.10.2) unstable; urgency=medium + + * Bump JPEG XL version to 0.10.2. + + -- JPEG XL Maintainers Thu, 07 Mar 2024 13:50:05 +0100 + +jpeg-xl (0.10.1) unstable; urgency=medium + + * Bump JPEG XL version to 0.10.1. + + -- JPEG XL Maintainers Wed, 28 Feb 2024 12:52:20 +0100 + +jpeg-xl (0.10.0) unstable; urgency=medium + + * Bump JPEG XL version to 0.10.0. + + -- JPEG XL Maintainers Wed, 21 Feb 2024 08:37:00 +0100 + +jpeg-xl (0.9.0) unstable; urgency=medium + + * Bump JPEG XL version to 0.9.0. + + -- 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. + + -- JPEG XL Maintainers Wed, 11 Jan 2023 16:12:34 +0000 + +jpeg-xl (0.7) unstable; urgency=medium * Bump JPEG XL version to 0.7. diff --git a/media/libjxl/src/debian/control b/media/libjxl/src/debian/control index 7a3c502e0e..e0169a3488 100644 --- a/media/libjxl/src/debian/control +++ b/media/libjxl/src/debian/control @@ -11,10 +11,9 @@ Build-Depends: libgdk-pixbuf-2.0-dev | libgdk-pixbuf2.0-dev, libgif-dev, libgimp2.0-dev, - libgmock-dev, libgoogle-perftools-dev, libgtest-dev, - libhwy-dev (>= 0.15.0), + libhwy-dev (>= 1.0.0), libjpeg-dev, libopenexr-dev, libpng-dev, diff --git a/media/libjxl/src/debian/copyright b/media/libjxl/src/debian/copyright index 20225a9209..7786a8775b 100644 --- a/media/libjxl/src/debian/copyright +++ b/media/libjxl/src/debian/copyright @@ -5,6 +5,11 @@ Files: * Copyright: 2020 the JPEG XL Project License: BSD-3-clause +Files: third_party/libjpeg-turbo/* +Copyright (C)2009-2023 D. R. Commander. All Rights Reserved. +Copyright (C)2015 Viktor Szathmáry. All Rights Reserved. +License: BSD-3-clause + Files: third_party/sjpeg/* Copyright: 2017 Google, Inc License: Apache-2.0 diff --git a/media/libjxl/src/debian/libjxl-dev.install b/media/libjxl/src/debian/libjxl-dev.install index b735ec2c26..ebe4ac4ae5 100644 --- a/media/libjxl/src/debian/libjxl-dev.install +++ b/media/libjxl/src/debian/libjxl-dev.install @@ -1,4 +1,3 @@ usr/include/jxl/*.h -usr/lib/*/*.a usr/lib/*/*.so usr/lib/*/pkgconfig/*.pc diff --git a/media/libjxl/src/debian/rules b/media/libjxl/src/debian/rules index efed75d509..6259dbfc61 100644 --- a/media/libjxl/src/debian/rules +++ b/media/libjxl/src/debian/rules @@ -8,10 +8,14 @@ include /usr/share/dpkg/pkg-info.mk override_dh_auto_configure: # TODO(deymo): Remove the DCMAKE_BUILD_TYPE once builds without NDEBUG # are as useful as Release builds. + # TODO(szabadka) Re-enable jpegli after tests are fixed on Ubuntu 20.04, + # and debian:buster dh_auto_configure -- \ -DJPEGXL_VERSION=$(DEB_VERSION) \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DJPEGXL_FORCE_SYSTEM_GTEST=ON \ -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DJPEGXL_FORCE_SYSTEM_HWY=ON \ - -DJPEGXL_ENABLE_PLUGINS=ON + -DJPEGXL_ENABLE_JPEGLI=OFF \ + -DJPEGXL_ENABLE_JPEGLI_LIBJPEG=OFF \ + -DJPEGXL_ENABLE_PLUGINS=ON diff --git a/media/libjxl/src/deps.sh b/media/libjxl/src/deps.sh index 9aaabba2e0..069c721de1 100644 --- a/media/libjxl/src/deps.sh +++ b/media/libjxl/src/deps.sh @@ -5,20 +5,24 @@ # license that can be found in the LICENSE file. # This file downloads the dependencies needed to build JPEG XL into third_party. -# These dependencies are normally pulled by gtest. +# These dependencies are normally pulled by git. set -eu -MYDIR=$(dirname $(realpath "$0")) +SELF=$(realpath "$0") +MYDIR=$(dirname "${SELF}") # Git revisions we use for the given submodules. Update these whenever you # update a git submodule. -THIRD_PARTY_BROTLI="35ef5c554d888bef217d449346067de05e269b30" -THIRD_PARTY_HIGHWAY="22e3d7276f4157d4a47586ba9fd91dd6303f441a" -THIRD_PARTY_SKCMS="64374756e03700d649f897dbd98c95e78c30c7da" -THIRD_PARTY_SJPEG="868ab558fad70fcbe8863ba4e85179eeb81cc840" -THIRD_PARTY_ZLIB="cacf7f1d4e3d44d871b605da3b647f07d718623f" -THIRD_PARTY_LIBPNG="a40189cf881e9f0db80511c382292a5604c3c3d1" +TESTDATA="873045a9c42ed60721756e26e2a6b32e17415205" +THIRD_PARTY_BROTLI="36533a866ed1ca4b75cf049f4521e4ec5fe24727" +THIRD_PARTY_GOOGLETEST="58d77fa8070e8cec2dc1ed015d66b454c8d78850" +THIRD_PARTY_HIGHWAY="457c891775a7397bdb0376bb1031e6e027af1c48" +THIRD_PARTY_SKCMS="42030a771244ba67f86b1c1c76a6493f873c5f91" +THIRD_PARTY_SJPEG="e5ab13008bb214deb66d5f3e17ca2f8dbff150bf" +THIRD_PARTY_ZLIB="51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf" # v1.3.1 +THIRD_PARTY_LIBPNG="f135775ad4e5d4408d2e12ffcc71bb36e6b48551" # v1.6.40 +THIRD_PARTY_LIBJPEG_TURBO="8ecba3647edb6dd940463fedf38ca33a8e2a73d1" # 2.1.5.1 # Download the target revision from GitHub. download_github() { @@ -26,7 +30,7 @@ download_github() { local project="$2" local varname=`echo "$path" | tr '[:lower:]' '[:upper:]'` - varname="${varname/\//_}" + varname="${varname//[\/-]/_}" local sha eval "sha=\${${varname}}" @@ -57,9 +61,16 @@ download_github() { mv "${local_fn}.tmp" "${local_fn}" } +is_git_repository() { + local dir="$1" + local toplevel=$(git rev-parse --show-toplevel) + + [[ "${dir}" == "${toplevel}" ]] +} + main() { - if git -C "${MYDIR}" rev-parse; then + if is_git_repository "${MYDIR}"; then cat >&2 <> $EMSDK/.emscripten -# If building using the jpegxl-builder docker container prefix the following commands with: -# CMAKE_FLAGS=-I/usr/wasm32/include -# ex. CMAKE_FLAGS=-I/usr/wasm32/include BUILD_TARGET=wasm32 emconfigure ./ci.sh release - -# Either build with regular WASM: +# Assuming you are in the root level of the cloned libjxl repo, +# either build with regular WASM: BUILD_TARGET=wasm32 emconfigure ./ci.sh release # or with SIMD WASM: BUILD_TARGET=wasm32 ENABLE_WASM_SIMD=1 emconfigure ./ci.sh release ``` + +## Example site + +Once you have build the wasm binary, you can give it a try by building a site +that decodes jxl images, see [wasm_demo](../tools/wasm_demo/README.md). diff --git a/media/libjxl/src/doc/color_management.md b/media/libjxl/src/doc/color_management.md index 56f4a2856c..88a7b60afa 100644 --- a/media/libjxl/src/doc/color_management.md +++ b/media/libjxl/src/doc/color_management.md @@ -4,7 +4,7 @@ ## Why diff --git a/media/libjxl/src/doc/debugging_workflows.md b/media/libjxl/src/doc/debugging_workflows.md new file mode 100644 index 0000000000..bac97d2ec8 --- /dev/null +++ b/media/libjxl/src/doc/debugging_workflows.md @@ -0,0 +1,71 @@ +### Reasoning + +Given the differences in compilers / environment it is not always clear why some +build / test fails in workflows. In this document we gather practices that +would help debugging workflows. + +### Debugging workflows on GitHub + +To connect to real workflow on GitHub one can use "tmate" plugin. To do that, +add the following snippet in workflow .yml: + +``` + - name: Setup tmate session + # Or other condition that pin-points a single strategy matrix item + if: failure() + uses: mxschmitt/action-tmate@a283f9441d2d96eb62436dc46d7014f5d357ac22 # v3.17 + timeout-minutes: 15 +``` + +When the plugin is executed it dumps to log a command to "ssh" to that instance. + +NB: since session is wrapped in tmux, scrolling might be very inconvenient. + +### Debugging build_test_cross.yml locally + +"cross" workflows are executed in container, so those are easy to reproduce +locally. Here is a snippet that reflects how setup / compilation are (currently) +done in the workflow: + +``` +docker run -it -v`pwd`:/libjxl debian:bookworm bash + +cd /libjxl + +export ARCH=i386 # arm64 armhf +export MAIN_LIST="amd64,${ARCH}" +export BUILD_DIR=build +export CC=clang-14 +export CXX=clang++-14 +export BUILD_TARGET=i686-linux-gnu # aarch64-linux-gnu arm-linux-gnueabihf + +rm -f /var/lib/man-db/auto-update +apt-get update -y +apt-get install -y ca-certificates debian-ports-archive-keyring python3 + +dpkg --add-architecture ${ARCH} +python3 ./tools/scripts/transform_sources_list.py "${MAIN_LIST}" +apt update + +apt-get install -y \ + clang-14 cmake doxygen g++-aarch64-linux-gnu graphviz libbrotli-dev:${ARCH} \ + libc6-dev-${ARCH}-cross libgdk-pixbuf2.0-dev:${ARCH} libgif-dev:${ARCH} \ + libgtk2.0-dev:${ARCH} libilmbase-dev:${ARCH} libjpeg-dev:${ARCH} \ + libopenexr-dev:${ARCH} libpng-dev:${ARCH} libstdc++-12-dev-${ARCH}-cross \ + libstdc++-12-dev:${ARCH} libwebp-dev:${ARCH} ninja-build pkg-config \ + qemu-user-static unzip xdg-utils xvfb + +#apt-get install -y binutils-${BUILD_TARGET} gcc-${BUILD_TARGET} +#apt-get install -y \ +# libgoogle-perftools-dev:${ARCH} libgoogle-perftools4:${ARCH} \ +# libtcmalloc-minimal4:${ARCH} libunwind-dev:${ARCH} +#export CMAKE_FLAGS="-march=armv8-a+sve" + +SKIP_TEST=1 ./ci.sh release \ + -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ + -DJPEGXL_ENABLE_JNI=OFF +# -DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/qemu-aarch64-static +# -DJPEGXL_ENABLE_OPENEXR=off +# -DJPEGXL_ENABLE_SIZELESS_VECTORS=on +# -DCMAKE_CXX_FLAGS=-DJXL_HIGH_PRECISION=0 +``` diff --git a/media/libjxl/src/doc/developing_in_debian.md b/media/libjxl/src/doc/developing_in_debian.md index a88b682ff3..a79af72d23 100644 --- a/media/libjxl/src/doc/developing_in_debian.md +++ b/media/libjxl/src/doc/developing_in_debian.md @@ -3,7 +3,6 @@ These instructions assume an up-to-date Debian/Ubuntu system. For other platforms, please instead use the following: -* [Developing in Docker](developing_in_docker.md). * [Cross Compiling for Windows with Crossroad](developing_with_crossroad.md). ## Minimum build dependencies @@ -13,7 +12,8 @@ dependencies that need to be installed on your system first: ```bash sudo apt install cmake clang doxygen g++ extra-cmake-modules \ - libgif-dev libjpeg-dev ninja-build libgoogle-perftools-dev + libgif-dev libjpeg-dev ninja-build libgoogle-perftools-dev \ + graphviz ``` Make sure your default `clang` compiler is at least version 6 by running @@ -34,8 +34,8 @@ Optionally, to compile some of the extra tool support and tests you can install the following packages: ```bash -sudo apt install qtbase5-dev libqt5x11extras5-dev libwebp-dev libgimp2.0-dev \ - libopenexr-dev libgtest-dev libgmock-dev libbenchmark-dev libbenchmark-tools +sudo apt install qt6-base-dev libwebp-dev libgimp2.0-dev libopenexr-dev \ + libgtest-dev libbenchmark-dev libbenchmark-tools ``` For the lint/coverage commands, you will also need additional packages: diff --git a/media/libjxl/src/doc/developing_in_docker.md b/media/libjxl/src/doc/developing_in_docker.md deleted file mode 100644 index f104a44b2f..0000000000 --- a/media/libjxl/src/doc/developing_in_docker.md +++ /dev/null @@ -1,114 +0,0 @@ -# Developing in Docker - -Docker allows software to be run in a packaged container, isolated from the -host system. This allows code to be run in a standard environment instead -of dealing with different build environments during development. It also -simplifies resolving external dependencies by including them in the automated -setup of the container environment. - -## Set up the container - -You can read installation instructions and download Docker for your -operating system at [Get Docker](https://docs.docker.com/get-docker/). - -The image used by our builders is an Ubuntu Bionic image with all the -required dependencies and build tools installed. You can pull this image -from `gcr.io/jpegxl/jpegxl-builder` using the following command: - -```bash -sudo docker pull gcr.io/jpegxl/jpegxl-builder -``` - -To use the Docker image you can run the following command: - -```bash -sudo docker run -it --rm \ - --user $(id -u):$(id -g) \ - -v $HOME/jpeg-xl:/jpeg-xl -w /jpeg-xl \ - gcr.io/jpegxl/jpegxl-builder bash -``` - -This creates and runs a container that will be deleted after you exit the -terminal (`--rm` flag). - -The `-v` flag is to map the directory containing your jpeg-xl checkout in your -host (assumed to be at `$HOME/jpeg-xl`) to a directory inside the container at -/jpeg-xl. Since the container is accessing the host folder directly, -changes made on the host will will be seen immediately in the container, -and vice versa. - -On OSX, the path must be one of those shared and whitelisted with Docker. $HOME -(which is a subdirectory of /Users/) is known to work with the factory-default -settings of Docker. - -On OSX, you may ignore the warning that Docker "cannot find name for group ID". -This warning may also appear on some Linux computers. - -On Windows, you can run the following from the jpeg-xl directory obtained from -Gitlab: - -```bash -docker run -u root:root -it --rm -v %cd%:/jpeg-xl -w /jpeg-xl \ - gcr.io/jpegxl/jpegxl-builder -``` - -## Basic building - -Inside the Docker container, you can compile everything and run unit tests. -We need to specify `clang-7` because the default `clang` compiler is -not installed on the image. - -```bash -CC=clang-7 CXX=clang++-7 ./ci.sh opt -``` - -This writes binaries to `/jpeg-xl/build/tools` and runs unit tests. -More information on [build modes and testing](building_and_testing.md) is -available. - -If a `build` directory already exists and was configured for a different -compiler, `cmake` will complain. This can be avoided by renaming or removing -the existing `build` directory or setting the `BUILD_DIR` environment variable. - -## Cross-compiling environments (optional) - -We have installed the required cross-compiling tools in the main Docker image -`jpegxl-builder`. This allows compiling for other architectures, such as arm. -Tests will be emulated under `qemu`. - -The Docker container has several `qemu-*-static` binaries (such as -`qemu-aarch64-static`) that emulate other architectures on x86_64. These -binaries are automatically used when running foreign architecture programs -in the container only if `binfmt` is installed and configured on the *host* -to use binaries from `/usr/bin` . This is the default location on Ubuntu/Debian. - -You need to install both `binfmt-support` and `qemu-user-static` on the host, -since `binfmt-support` configures only `binfmt` signatures of architectures -that are installed. If these are configured elsewhere on other distributions, -you can symlink them to `/usr/bin/qemu-*-static` inside the Docker container. - -To install binfmt support in your Ubuntu host run *outside* the container: - -```bash -sudo apt install binfmt-support qemu-user-static -``` - -Then to cross-compile and run unit tests execute the following commands: - -```bash -export BUILD_TARGET=aarch64-linux-gnu CC=clang-7 CXX=clang++-7 -./ci.sh release -``` - -The `BUILD_TARGET=aarch64-linux-gnu` environment variable tells the `ci.sh` -script to cross-compile for that target. This also changes the default -`BUILD_DIR` to `build-aarch64` since you never want to mix them with the `build` -of your host. You can also explicitly set a `BUILD_DIR` environment variable -that will be used instead. The list of supported `BUILD_TARGET` values for this -container is: - -* *the empty string* (for native x86_64 support) -* aarch64-linux-gnu -* arm-linux-gnueabihf -* i686-linux-gnu -* x86_64-w64-mingw32 (for Windows builds) diff --git a/media/libjxl/src/doc/developing_in_github.md b/media/libjxl/src/doc/developing_in_github.md index ecda64fc85..f075e36cd0 100644 --- a/media/libjxl/src/doc/developing_in_github.md +++ b/media/libjxl/src/doc/developing_in_github.md @@ -17,7 +17,7 @@ After that "Life of a Pull Request" describes the common everyday workflows. ### Configure your SSH access -The easiest way to configure access to your Github repository is to use SSH +The easiest way to configure access to your GitHub repository is to use SSH keys. For that you need an SSH private and public key, ideally a strong one. You can use different keys for different sites if you want. In this example, we will create one for using in GitHub only. @@ -93,7 +93,7 @@ fork. You can use any other name for your fork if you want. Use the following commands to set things up, replacing `{{USERNAME}}` with your GitHub username: ```bash -git clone git https://github.com/libjxl/libjxl --recursive +git clone https://github.com/libjxl/libjxl --recursive cd libjxl git remote set-url --push origin git@github.com:{{USERNAME}}/libjxl.git git remote add myfork git@github.com:{{USERNAME}}/libjxl.git @@ -231,7 +231,7 @@ git push origin mybranch This will push your local branch "mybranch" to a remote in your fork called "mybranch". The name can be anything, but keep in mind that it is public. A link -to the URL to create a merge request will be displayed. +to the URL to create a pull request will be displayed. ``` Enumerating objects: 627, done. @@ -309,7 +309,7 @@ In that case, you can use the force flag: git submodule update --init --recursive --force ``` -### Iterating changes in your merge request +### Iterating changes in your pull request To address reviewer changes you need to amend the local changes in your branch first. Make the changes you need in your commit locally by running `git commit @@ -325,7 +325,7 @@ ones), you need to force the operation to replace the old version. git push origin mybranch --force ``` -The merge request should now be updated with the new changes. +The pull request should now be updated with the new changes. ### Merging your changes @@ -342,7 +342,7 @@ verify that all the tests pass again after including the latest changes. ### Trying locally a pending Pull Request If you want to review in your computer a pending pull request proposed by -another user you can fetch the merge request commit with the following command, +another user you can fetch the pull request commit with the following command, replacing `NNNN` with the pull request number: ```bash diff --git a/media/libjxl/src/doc/developing_in_windows_vcpkg.md b/media/libjxl/src/doc/developing_in_windows_vcpkg.md index 332e4513eb..c7ef603e2b 100644 --- a/media/libjxl/src/doc/developing_in_windows_vcpkg.md +++ b/media/libjxl/src/doc/developing_in_windows_vcpkg.md @@ -4,7 +4,6 @@ These instructions assume an up-to-date Windows 10 (e.g. build 19041.928) with **Microsoft Visual Studio 2019** (e.g. Version 16.9.0 Preview 4.0) installed. If unavailable, please use another build environment: -* [Docker container](developing_in_docker.md) * [MSYS2 on Windows](developing_in_windows_msys.md) * [Crossroad on Linux](developing_with_crossroad.md) (cross compilation for Windows) @@ -59,11 +58,6 @@ vcpkg above. "value": "False", "type": "BOOL" }, - { - "name": "BUILD_GMOCK", - "value": "True", - "type": "BOOL" - }, { "name": "gtest_force_shared_crt", "value": "True", diff --git a/media/libjxl/src/doc/encode_effort.md b/media/libjxl/src/doc/encode_effort.md new file mode 100644 index 0000000000..221b2bf649 --- /dev/null +++ b/media/libjxl/src/doc/encode_effort.md @@ -0,0 +1,32 @@ +# Encode effort settings + +Various trade-offs between encode speed and compression performance can be selected in libjxl. In `cjxl`, this is done via the `--effort` (`-e`) option. +Higher effort means slower encoding; generally the higher the effort, the more coding tools are used, computationally more expensive heuristics are used, +and more exhaustive search is performed. +Generally efforts range between `1` and `9`, but there is also `e10` you pass the flag `--allow_expert_options` (in combination with "lossless", i.e. `-d 0`). It is considered an expert option because it can be extremely slow. + + +For lossy compression, higher effort results in better visual quality at a given filesize, and also better +encoder consistency, i.e. less image-dependent variation in the actual visual quality that is achieved. This means that for lossy compression, +higher effort does not necessarily mean smaller filesizes for every image — some images may be somewhat lower quality than desired when using +lower effort heuristics, and to improve consistency, higher effort heuristics may decide to use more bytes for them. + +For lossless compression, higher effort should result in smaller filesizes, although this is not guaranteed; +in particular, e2 can be better than e3 for non-photographic images, and e3 can be better than e4 for photographic images. + +The following table describes what the various effort settings do: + +|Effort | Modular (lossless) | VarDCT (lossy) | +|-------|--------------------|----------------| +| e1 | fast-lossless, fixed YCoCg RCT, fixed ClampedGradient predictor, simple palette detection, no MA tree (one context for everything), Huffman, simple rle-only lz77 | | +| e2 | global channel palette, fixed MA tree (context based on Gradient-error), ANS, otherwise same as e1 | | +| e3 | same as e2 but fixed Weighted predictor and fixed MA tree with context based on WP-error | only 8x8, basically XYB jpeg with ANS | +| e4 | try both ClampedGradient and Weighted predictor, learned MA tree, global palette | simple variable blocks heuristics, adaptive quantization, coefficient reordering | +| e5 | e4 + patches, local palette / local channel palette, different local RCTs | e4 + gabor-like transform, chroma from luma | +| e6 | e5 + more RCTs and MA tree properties | e5 + error diffusion, full variable blocks heuristics | +| e7 | e6 + more RCTs and MA tree properties | e6 + patches (including dots) | +| e8 | e7 + more RCTs, MA tree properties and Weighted predictor parameters | e7 + Butteraugli iterations for adaptive quantization | +| e9 | e8 + more RCTs, MA tree properties and Weighted predictor parameters, try all predictors | e8 + more Butteraugli iterations | +| e10 | e9 + previous-channel MA tree properties, different group dimensions, exhaustively try various e9 options | | + +For the entropy coding (context clustering, lz77 search, hybriduint configuration): slower/more exhaustive search as effort goes up. diff --git a/media/libjxl/src/doc/fuzzing.md b/media/libjxl/src/doc/fuzzing.md index af926596f2..2bd84030fc 100644 --- a/media/libjxl/src/doc/fuzzing.md +++ b/media/libjxl/src/doc/fuzzing.md @@ -103,7 +103,7 @@ clang package for that version outside the container and using `clang-NN` (for example `clang-11`) instead of `clang` in the following commands: ```bash -symbolizer=$($(realpath $(which clang)) -print-prog-name=llvm-symbolizer) +symbolizer=$($(realpath "$(which clang)") -print-prog-name=llvm-symbolizer) export MSAN_SYMBOLIZER_PATH="${symbolizer}" export UBSAN_SYMBOLIZER_PATH="${symbolizer}" export ASAN_SYMBOLIZER_PATH="${symbolizer}" @@ -135,16 +135,9 @@ perform an invalid operation (read/write out of bounds, perform an undefined behavior operation, etc). You can help the fuzzer find invalid situations by adding asserts: - * `JXL_ASSERT()` is enabled in Release mode by default. It can be disabled - with `-DJXL_ENABLE_ASSERT=0` but the intention is that it will run for all - the users in released code. If performance of the check is not an issue (like - checks done once per image, once per channel, once per group, etc) a - JXL_ASSERT is appropriate. A failed assert is preferable to an out of bounds - write. - - * `JXL_DASSERT()` is only enabled in Debug builds, which includes all the ASan, + * `JXL_DASSERT` is only enabled in Debug builds, which includes all the ASan, MSan and UBSan builds. Performance of these checks is not an issue if kept - within reasonable limits (automated msan/asan test should finish withing 1 + within reasonable limits (automated msan/asan test should finish within 1 hour for example). Fuzzing is more effective when the given input runs faster, so keep that in mind when adding a complex DASSERT that runs multiple times per output pixel. diff --git a/media/libjxl/src/doc/release.md b/media/libjxl/src/doc/release.md index 70f1278a42..3448640f67 100644 --- a/media/libjxl/src/doc/release.md +++ b/media/libjxl/src/doc/release.md @@ -29,7 +29,7 @@ number. Released tags don't each one have their own release branch, all releases from the same MAJOR.MINOR version will share the same branch. The first commit after the branch-off points between the main branch and the release branch should be tagged with the suffix `-snapshot` and the name of the next -MAJOR.MINOR version, in order to get meaningful ouput for `git --describe`. +MAJOR.MINOR version, in order to get meaningful output for `git describe`. The main purpose of the release branch is to stabilize the code before a release. This involves including fixes to existing bugs but **not** including @@ -93,6 +93,15 @@ branches](https://docs.github.com/en/github/administering-a-repository/defining- in our repository, however you can push to a protected branch when *creating* it but you can't directly push to it after it is created. To include more changes in the release branch see the "Cherry-picking fixes to a release" section below. +Remember to tag the first commit in the `main` branch after the branch of point +with the suffix `-snapshot`, so for instances, if you just made the +branch `v0.5.x`, run +``` +git checkout [hash of first commit after the branch off point] +git tag v0.5-snapshot +git push git@github.com:libjxl/libjxl.git v0.5-snapshot +``` + ## Creating a merge label @@ -128,13 +137,15 @@ To help update it, run this helper command (in a Debian-based system): This will update the version in the following files: * `lib/CMakeLists.txt` - * `lib/lib.gni`, automatically updated with `tools/build_cleaner.py --update`. + * `lib/lib.gni`, automatically updated with + `tools/scripts/build_cleaner.py --update`. * `debian/changelog` to create the Debian package release with the new version. Debian changelog shouldn't repeat the library changelog, instead it should include changes to the packaging scripts. - -If there were incompatible API/ABI changes, make sure to also adapt the -corresponding section in + * `.github/workflows/conformance.yml` + +If there were incompatible API/ABI changes, make sure to also adapt the +corresponding section in [CMakeLists.txt](https://github.com/libjxl/libjxl/blob/main/lib/CMakeLists.txt#L12). ## Cherry-pick fixes to a release @@ -180,6 +191,15 @@ commits you need to cherry-pick, ideally in the same order they were merged on the `main` branch. At the end you will have a local branch with multiple commits on top of the release branch. +To update the version number, for example from v0.8.0 to v0.8.1 run this helper +command (in a Debian-based system): + +```bash +./ci.sh bump_version 0.8.1 +``` + +as described above and commit the changes. + Finally, upload your changes to *your fork* like normal, except that when creating a pull request select the desired release branch as a target: @@ -263,5 +283,67 @@ instructions: the release itself that are not included in the CHANGELOG.md, although prefer to include those in the CHANGELOG.md file. You can switch to the Preview tab to see the results. + * Copy-paste the following note: + ```markdown + **Note:** This release is for evaluation purposes and may contain bugs, including security bugs, that may not be individually documented when fixed. See the [SECURITY.md](SECURITY.md) file for details. Always prefer to use the latest release. + +Please provide feedback and report bugs [here](https://github.com/libjxl/libjxl/issues). +``` + * Finally click "Publish release" and go celebrate with the team. 🎉 + + * The branch v0.7.x will be pushed to gitlab automatically, but make sure to + manually push the *tag* of the release also to + https://gitlab.com/wg1/jpeg-xl, by doing configuring `gitlab` to be the remote `git@gitlab.com:wg1/jpeg-xl.git` and then running: + +```bash +git push gitlab v0.7.1 +``` +and possibly also push the tag for the snapshot to gitlab, if it wasn't done yet: +```bash +git push gitlab v0.7-snapshot +``` + + + +### How to build downstream projects + +```bash +docker run -it debian:bookworm /bin/bash + +apt update +apt install -y clang cmake git libbrotli-dev nasm pkg-config ninja-build +export CC=clang +export CXX=clang++ + +mkdir -p /src +cd /src + +git clone --recurse-submodules --depth 1 -b v0.9.x \ + https://github.com/libjxl/libjxl.git +git clone --recurse-submodules --depth 1 \ + https://github.com/ImageMagick/ImageMagick.git +git clone --recurse-submodules --depth 1 \ + https://github.com/FFmpeg/FFmpeg.git + +cd /src/libjxl +cmake -B build -G Ninja . +cmake --build build -j`nproc` +cmake --install build --prefix="/usr" + +cd /src/ImageMagick +./configure --with-jxl=yes +# check for "JPEG XL --with-jxl=yes yes" +make -j `nproc` +./utilities/magick -version + +cd /src/FFmpeg +./configure --disable-all --disable-debug --enable-avcodec --enable-avfilter \ + --enable-avformat --enable-libjxl --enable-encoder=libjxl \ + --enable-decoder=libjxl --enable-ffmpeg +# check for libjxl decoder/encoder support +make -j `nproc` +ldd ./ffmpeg +./ffmpeg -version +``` diff --git a/media/libjxl/src/doc/software_support.md b/media/libjxl/src/doc/software_support.md index 9dddad7de7..047d37fed9 100644 --- a/media/libjxl/src/doc/software_support.md +++ b/media/libjxl/src/doc/software_support.md @@ -12,20 +12,34 @@ Please add missing software to this list. ## Browsers -- Chromium: behind a flag since version 91, [tracking bug](https://bugs.chromium.org/p/chromium/issues/detail?id=1178058) +- Chromium: behind a flag from version 91 to 109, [tracking bug](https://bugs.chromium.org/p/chromium/issues/detail?id=1178058) - Firefox: behind a flag since version 90, [tracking bug](https://bugzilla.mozilla.org/show_bug.cgi?id=1539075) -- Safari: not supported, [tracking bug](https://bugs.webkit.org/show_bug.cgi?id=208235) +- Safari: supported since version 17 [release notes](https://developer.apple.com/documentation/safari-release-notes/safari-17-release-notes), [tracking bug](https://bugs.webkit.org/show_bug.cgi?id=208235) - Edge: behind a flag since version 91, start with `.\msedge.exe --enable-features=JXL` - Opera: behind a flag since version 77. -- For all browsers and to track browsers progress see [Can I Use](https://caniuse.com/jpegxl). +- Basilisk: supported since version v2023.01.07, [release notes](https://www.basilisk-browser.org/releasenotes.shtml) +- Pale Moon: supported since version 31.4.0, [release notes](https://www.palemoon.org/releasenotes-archived.shtml#v31.4.0) +- Waterfox: [enabled by default](https://github.com/WaterfoxCo/Waterfox/pull/2936) + +For all browsers and to track browsers progress see [Can I Use](https://caniuse.com/jpegxl). ## Image libraries - [ImageMagick](https://imagemagick.org/): supported since 7.0.10-54 - [libvips](https://libvips.github.io/libvips/): supported since 8.11 -- [Imlib2](https://github.com/alistair7/imlib2-jxl) +- [Imlib2](https://docs.enlightenment.org/api/imlib2/html/) - [FFmpeg](https://github.com/FFmpeg/FFmpeg/search?q=jpeg-xl&type=commits) - [GDAL](https://gdal.org/drivers/raster/jpegxl.html): supported since 3.4.0 as a TIFF codec, and 3.6.0 as standalone format +- [GraphicsMagick](http://www.graphicsmagick.org/NEWS.html#march-26-2022): supported since 1.3.38 +- [SAIL](https://sail.software): supported since 0.9.0 +- [JPEG XL Coder](https://github.com/awxkee/jxl-coder): Supports version from Android 5.0 (API Level 21) +- [SDWebImageJPEGXLCoder](https://github.com/SDWebImage/SDWebImageJPEGXLCoder): supported since 0.1.0 + +## Metadata manipulation libraries + +- [ExifTool by Phil Harvey](https://exiftool.org/): supported since 12.25 +- [Exiv2](https://exiv2.org): supported since 0.27.4 +- [Ashampoo Kim](https://github.com/ashampoo/kim): supported since 0.10 ## OS-level support / UI frameworks / file browser plugins @@ -41,25 +55,43 @@ Please add missing software to this list. ## Image editors +- [Adobe Camera Raw (since version 15)](https://helpx.adobe.com/camera-raw/using/hdr-output.html) +- [Affinity (since V2)](https://affinity.serif.com/en-gb/whats-new/) +- [darktable (since 4.2)](https://github.com/darktable-org/darktable/releases/tag/release-4.2.0) - [GIMP (since 2.99.8)](https://www.gimp.org/news/2021/10/20/gimp-2-99-8-released/); plugin for older versions available in libjxl repo +- [Graphic Converter (since 11.5)](https://www.lemkesoft.de/en/products/graphicconverter/) - [Krita](https://invent.kde.org/graphics/krita/-/commit/13e5d2e5b9f0eac5c8064b7767f0b62264a0797b) +- [Paint.NET](https://www.getpaint.net/index.html); supported since 4.3.12 - requires a [plugin](https://github.com/0xC0000054/pdn-jpegxl) to be downloaded and installed. - Photoshop: no plugin available yet, no official support yet +- [XL Converter](https://github.com/JacobDev1/xl-converter) +- [Image Toolbox (supported since 2.6.0)](https://github.com/T8RIN/ImageToolbox) +- [JPEG XL Toolbox by SUIKA LTD](https://apps.apple.com/app/jpeg-xl-toolbox/id6470681357) +- [RawTherapee (since 5.11)](https://github.com/Beep6581/RawTherapee); Currently only opening of JXL files is supported. [Exporting is expected for the 6.0 release](https://github.com/Beep6581/RawTherapee/pull/7097) ## Image viewers -- [XnView](https://www.xnview.com/en/) +- [Aspect](https://aspect.bildhuus.com) - [ImageGlass](https://imageglass.org/) - [IrfanView](https://www.irfanview.com/); supported since 4.59 - requires a [plugin](https://www.irfanview.com/plugins.htm) to be downloaded and enabled. +- [jpegview](https://github.com/sylikc/jpegview/releases) +- [Swayimg](https://github.com/artemsen/swayimg) - [Tachiyomi](https://github.com/tachiyomiorg/tachiyomi/releases/tag/v0.12.1) +- [XnView](https://www.xnview.com/en/) - Any viewer based on Qt, KDE, GDK-pixbuf, EFL, ImageMagick, libvips or imlib2 (see above) - Qt viewers: gwenview, digiKam, KolourPaint, KPhotoAlbum, LXImage-Qt, qimgv, qView, nomacs, VookiImageViewer, PhotoQt - GTK viewers: Eye of Gnome (eog), gThumb, Geeqie - EFL viewers: entice, ephoto -- [Swayimg](https://github.com/artemsen/swayimg) + +## Duplicate image finders + +- [AntiDupl.NET](https://github.com/ermig1979/AntiDupl/releases) ## Online tools +- [Gumlet](https://www.gumlet.com/blog/jpeg-xl/) - [Squoosh](https://squoosh.app/) - [Cloudinary](https://cloudinary.com/blog/cloudinary_supports_jpeg_xl) - [MConverter](https://mconverter.eu/) - [jpegxl.io](https://jpegxl.io/) +- [EXIF Viewer](https://stefan-oltmann.de/exif-viewer) +- [Immich](https://immich.app/), a self-hosted image gallery service diff --git a/media/libjxl/src/doc/sphinx/api.rst b/media/libjxl/src/doc/sphinx/api.rst index 56fca09e25..1d79041f7d 100644 --- a/media/libjxl/src/doc/sphinx/api.rst +++ b/media/libjxl/src/doc/sphinx/api.rst @@ -1,5 +1,5 @@ -API reference -============= +libjxl API reference +==================== ``libjxl`` exposes a C API for encoding and decoding JPEG XL files with some C++ header-only helpers for C++ users. @@ -11,5 +11,7 @@ C++ header-only helpers for C++ users. api_decoder api_encoder api_common - api_butteraugli + api_metadata + api_color api_threads + api_cpp diff --git a/media/libjxl/src/doc/sphinx/api_butteraugli.rst b/media/libjxl/src/doc/sphinx/api_butteraugli.rst deleted file mode 100644 index 4aae44a991..0000000000 --- a/media/libjxl/src/doc/sphinx/api_butteraugli.rst +++ /dev/null @@ -1,6 +0,0 @@ -Butteraugli API - ``jxl/butteraugli.h`` -======================================= - -.. doxygengroup:: libjxl_butteraugli - :members: - :private-members: diff --git a/media/libjxl/src/doc/sphinx/api_color.rst b/media/libjxl/src/doc/sphinx/api_color.rst new file mode 100644 index 0000000000..6e40d48295 --- /dev/null +++ b/media/libjxl/src/doc/sphinx/api_color.rst @@ -0,0 +1,6 @@ +Color encoding and conversion +============================= + +.. doxygengroup:: libjxl_color + :members: + :private-members: diff --git a/media/libjxl/src/doc/sphinx/api_cpp.rst b/media/libjxl/src/doc/sphinx/api_cpp.rst new file mode 100644 index 0000000000..cc5e9856a5 --- /dev/null +++ b/media/libjxl/src/doc/sphinx/api_cpp.rst @@ -0,0 +1,6 @@ +C++ helpers +=========== + +.. doxygengroup:: libjxl_cpp + :members: + :private-members: diff --git a/media/libjxl/src/doc/sphinx/api_metadata.rst b/media/libjxl/src/doc/sphinx/api_metadata.rst new file mode 100644 index 0000000000..ddc5b03045 --- /dev/null +++ b/media/libjxl/src/doc/sphinx/api_metadata.rst @@ -0,0 +1,6 @@ +Image and frame metadata +======================== + +.. doxygengroup:: libjxl_metadata + :members: + :private-members: diff --git a/media/libjxl/src/doc/tables/adobe.md b/media/libjxl/src/doc/tables/adobe.md deleted file mode 100644 index f3beef75ae..0000000000 --- a/media/libjxl/src/doc/tables/adobe.md +++ /dev/null @@ -1,6 +0,0 @@ -#### Table M.8 – "Adobe" marker template - -``` -0xEE, 0x00, 0x0E, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x01 -``` - diff --git a/media/libjxl/src/doc/tables/all_tables.pdf b/media/libjxl/src/doc/tables/all_tables.pdf deleted file mode 100644 index 09a85170c1..0000000000 --- a/media/libjxl/src/doc/tables/all_tables.pdf +++ /dev/null @@ -1,1078 +0,0 @@ -Electronic Insert I.1 – DCT-II / DCT-III code generator - - ####################################################################### - # DCT-II / DCT-III generator - # - # Based on: - # "A low multiplicative complexity fast recursive DCT-2 algorithm" - # by Maxim Vashkevich and Alexander Petrovsky / arXiv / 20 Jul 2012 - ####################################################################### - - import math - import sys - N=8 - - ####################################################################### - # Base transforms / generators - ####################################################################### - - CNTR = 0 - def makeTmp(): - - global CNTR - result = "t{:02d}".format(CNTR) - CNTR = CNTR + 1 - return result - - def makeVar(i): - return "i{:02d}".format(i) - - def add(x, y): - tmp = makeTmp() - print(tmp + " = " + x + " + " + y + ";") - return tmp - - def sub(x, y): - tmp = makeTmp() - print(tmp + " = " + x + " - " + y + ";") - return tmp - - def mul(x, c): - tmp = makeTmp() - print(tmp + " = " + x + " * " + c + ";") - return tmp - - # 2.0 * math.cos((a + 0.0) / (b + 0.0) * math.pi) - def C2(a, b): - return "c_c2_" + str(a) + "_" + str(b) - -# 1.0 / C2(a, b) -def iC2(a, b): - - return "c_ic2_" + str(a) + "_" + str(b) - -####################################################################### -# Utilities -####################################################################### - -# Generate identity matrix. Usually this matrix is passed to -# DCT algorithm to generate "basis" vectors of the transform. -def makeVars(): - - return [makeVar(i) for i in range(N)] - -# Split list of variables info halves. -def split(x): - - m = len(x) - m2 = m // 2 - return (x[0 : m2], x[m2 : m]) - -# Make a list of variables in a reverse order. -def reverse(varz): - - m = len(varz) - result = [0] * m - for i in range(m): - - result[i] = varz[m - 1 - i] - return result - -# Apply permutation -def permute(x, p): - - return [x[p[i]] for i in range(len(p))] - -def transposePermutation(p): - n = len(p) - result = [0] * n - for i in range(n): - result[p[i]] = i - return result - -# See paper. Split even-odd elements. -def P(n): - - if n == 1: - return [0] - n2 = n // 2 - return [2 * i for i in range(n2)] + [2 * i + 1 for i in range(n2)] - -# See paper. Interleave first and second half. -def Pt(n): - - return transposePermutation(P(n)) - -####################################################################### -# Scheme -####################################################################### - -def B2(x): - n = len(x) - n2 = n // 2 - if n == 1: - raise "ooops" - (top, bottom) = split(x) - bottom = reverse(bottom) - t = [add(top[i], bottom[i]) for i in range(n2)] - b = [sub(top[i], bottom[i]) for i in range(n2)] - return t + b - -def iB2(x): - n = len(x) - n2 = n // 2 - if n == 1: - raise "ooops" - (top, bottom) = split(x) - t = [add(top[i], bottom[i]) for i in range(n2)] - b = [sub(top[i], bottom[i]) for i in range(n2)] - return t + reverse(b) - -def B4(x, rn): - n = len(x) - n2 = n // 2 - if n == 1: - raise "ooops" - (top, bottom) = split(x) - rbottom = reverse(bottom) - t = [sub(top[i], rbottom[i]) for i in range(n2)] - b = [mul(bottom[i], C2(rn, 2 * N)) for i in range(n2)] - top = [add(t[i], b[i]) for i in range(n2)] - bottom = [sub(t[i], b[i]) for i in range(n2)] - return top + bottom - def iB4(x, rn): - n = len(x) - n2 = n // 2 - if n == 1: - raise "ooops" - (top, bottom) = split(x) - t = [add(top[i], bottom[i]) for i in range(n2)] - b = [sub(top[i], bottom[i]) for i in range(n2)] - bottom = [mul(b[i], iC2(rn, 2 * N)) for i in range(n2)] - rbottom = reverse(bottom) - top = [add(t[i], rbottom[i]) for i in range(n2)] - return top + bottom - -def P4(n): - if n == 1: - return [0] - if n == 2: - return [0, 1] - n2 = n // 2 - result = [0] * n - tc = 0 - bc = 0 - i=0 - result[i] = tc; tc = tc + 1; i = i + 1 - turn = True - while i < n - 1: - if turn: - result[i] = n2 + bc; bc = bc + 1; i = i + 1 - result[i] = n2 + bc; bc = bc + 1; i = i + 1 - else: - result[i] = tc; tc = tc + 1; i = i + 1 - result[i] = tc; tc = tc + 1; i = i + 1 - turn = not turn - result[i] = tc; tc = tc + 1; i = i + 1 - return result - -def iP4(n): - return transposePermutation(P4(n)) - -def d2n(x): - n = len(x) - if n == 1: - return x - y = B2(x) - (top, bottom) = split(y) - return permute(d2n(top) + d4n(bottom, N // 2), Pt(n)) - -def id2n(x): - n = len(x) - if n == 1: - return x - (top, bottom) = split(permute(x, P(n))) - return iB2(id2n(top) + id4n(bottom, N // 2)) - -def d4n(x, rn): - n = len(x) - if n == 1: - return x - y = B4(x, rn) - (top, bottom) = split(y) - rn2 = rn // 2 - return permute(d4n(top, rn2) + d4n(bottom, N - rn2), P4(n)) - -def id4n(x, rn): - n = len(x) - if n == 1: - return x - (top, bottom) = split(permute(x, iP4(n))) - rn2 = rn // 2 - y = id4n(top, rn2) + id4n(bottom, N -rn2) - return iB4(y, rn) - -####################################################################### -# Main. -####################################################################### - -def help(): - print("Usage: %s [N [T]]" % sys.argv[0]) - print(" N should be the power of 2, default is 8") - print(" T is one of {2, 3}, default is 2") - sys.exit() - -def parseInt(s): - try: - return int(s) - except ValueError: - help() - -if __name__ == "__main__": - if len(sys.argv) < 1 or len(sys.argv) > 3: help() - if len(sys.argv) >= 2: - N = parseInt(sys.argv[1]) - if (N & (N - 1)) != 0: help() - - type = 0 - if len(sys.argv) >= 3: - - typeOption = sys.argv[2] - if len(typeOption) != 1: help() - type = "23".index(typeOption) - if type == -1: help() - if type == 0: - vars = d2n(makeVars()) - else: # type == 1 - vars = id2n(makeVars()) - print("Output vector: " + str(vars)) - -Table M.1 – is_zero_base table - - 228, 216, 216, 195, 192, 189, 182, 184, 179, 176, 171, 168, 166, 159, - 156, 151, 151, 150, 150, 146, 144, 138, 138, 137, 135, 131, 127, 126, - 124, 123, 124, 123, 122, 121, 118, 117, 114, 115, 116, 116, 115, 115, - 114, 111, 111, 111, 112, 111, 110, 110, 110, 111, 111, 114, 110, 111, - 112, 113, 116, 120, 126, 131, 147, 160 - -Table M.2 – num_nonzeros_base table - - 251, 252, 117, 249, 161, 136, 83, 238, 184, 126, 137, 129, 140, 119, - 70, 213, 160, 175, 174, 130, 166, 134, 122, 125, 131, 144, 136, 133, - - 139, 123, 79, 216, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128 - - 254, 252, 174, 232, 189, 155, 122, 177, 204, 173, 146, 149, 141, 133, - 103, 109, 167, 187, 168, 142, 154, 147, 125, 139, 144, 138, 138, 153, - 141, 133, 90, 121, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128 - - 251, 240, 197, 176, 184, 177, 114, 89, 194, 165, 153, 161, 158, 136, - 92, 95, 123, 171, 160, 140, 148, 136, 129, 139, 145, 136, 143, 134, - - 138, 124, 92, 154, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128 - 247, 220, 201, 110, 194, 176, 147, 59, 175, 171, 156, 157, 152, 146, -115, 114, 88, 151, 164, 141, 153, 135, 141, 131, 146, 139, 140, 145, -138, 137, 112, 184, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - -238, 179, 203, 63, 194, 173, 149, 71, 139, 169, 154, 159, 150, 146, -117, 143, 78, 122, 152, 137, 149, 138, 138, 133, 134, 142, 142, 142, -148, 128, 118, 199, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - -227, 127, 200, 44, 192, 170, 148, 100, 102, 161, 156, 153, 148, 149, -124, 160, 88, 101, 134, 132, 149, 145, 134, 134, 136, 141, 138, 142, -144, 137, 116, 208, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - -214, 86, 195, 44, 187, 163, 148, 126, 81, 147, 156, 152, 150, 144, -121, 172, 96, 95, 117, 122, 145, 152, 136, 133, 135, 135, 131, 142, -141, 135, 114, 217, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - -198, 56, 191, 54, 171, 162, 147, 144, 74, 128, 152, 149, 150, 142, -119, 177, 101, 100, 106, 111, 135, 154, 136, 137, 136, 132, 133, 142, -144, 130, 117, 222, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - -176, 40, 189, 73, 147, 159, 148, 152, 79, 106, 147, 149, 151, 139, -123, 188, 108, 110, 106, 97, 125, 151, 137, 138, 135, 135, 134, 136, -140, 131, 116, 221, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - -148, 33, 185, 88, 117, 158, 145, 163, 95, 91, 137, 146, 150, 140, -120, 197, 115, 116, 114, 92, 114, 144, 130, 133, 132, 133, 129, 140, -138, 130, 111, 224, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - 117, 31, 180, 104, 93, 150, 143, 166, 99, 85, 124, 139, 148, 142, -118, 201, 105, 120, 120, 90, 107, 135, 127, 130, 131, 131, 132, 140, -142, 133, 114, 229, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 87, 35, 170, 110, 78, 141, 144, 176, 106, 90, 112, 132, 143, 138, -119, 204, 111, 121, 125, 90, 105, 131, 124, 122, 129, 128, 129, 137, -138, 133, 114, 227, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 63, 42, 159, 123, 73, 127, 142, 191, 105, 91, 105, 123, 139, 137, -120, 209, 117, 110, 122, 98, 110, 125, 115, 123, 122, 126, 128, 134, -141, 129, 113, 229, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 45, 53, 146, 135, 71, 114, 138, 193, 100, 98, 98, 113, 133, 135, -118, 222, 113, 111, 139, 103, 107, 126, 111, 119, 121, 122, 127, 135, -141, 128, 114, 242, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 33, 60, 132, 138, 75, 100, 134, 203, 112, 99, 98, 105, 126, 131, -115, 229, 107, 93, 121, 106, 108, 122, 106, 109, 114, 116, 127, 133, -143, 128, 110, 242, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 24, 70, 118, 134, 76, 87, 130, 201, 110, 96, 99, 97, 119, 130, -111, 229, 97, 104, 125, 102, 112, 125, 101, 109, 113, 114, 125, 129, -142, 127, 112, 241, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 17, 65, 100, 121, 80, 75, 124, 174, 117, 100, 94, 93, 114, 128, -110, 216, 103, 94, 113, 122, 118, 126, 113, 108, 105, 108, 122, 128, -141, 125, 113, 238, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - 12, 70, 82, 132, 78, 65, 118, 155, 136, 103, 97, 89, 106, 124, -111, 215, 115, 123, 129, 99, 104, 127, 110, 108, 101, 109, 118, 126, -136, 123, 110, 233, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 8, 66, 61, 117, 91, 59, 108, 195, 101, 112, 99, 99, 99, 116, -106, 230, 127, 99, 144, 101, 118, 137, 117, 111, 106, 104, 116, 121, -134, 122, 110, 223, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 6, 78, 42, 146, 101, 54, 94, 201, 116, 102, 110, 94, 92, 108, -103, 214, 108, 111, 127, 102, 121, 132, 120, 121, 95, 98, 110, 121, -129, 117, 107, 235, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 5, 93, 29, 145, 102, 52, 77, 216, 108, 115, 108, 102, 89, 97, - 94, 229, 89, 103, 139, 120, 103, 151, 102, 100, 97, 96, 99, 111, -125, 116, 104, 242, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 4, 105, 21, 145, 100, 54, 64, 217, 100, 122, 128, 87, 88, 91, - 87, 230, 112, 80, 148, 95, 146, 123, 96, 140, 90, 91, 98, 106, -122, 111, 100, 249, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 4, 130, 14, 142, 104, 56, 51, 208, 116, 135, 100, 89, 82, 84, - 75, 239, 85, 85, 122, 125, 94, 144, 151, 136, 92, 97, 104, 109, -113, 110, 91, 246, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 3, 126, 9, 172, 105, 57, 39, 219, 95, 120, 118, 96, 93, 75, - 66, 241, 102, 134, 96, 156, 146, 162, 130, 112, 82, 89, 97, 101, -116, 103, 82, 254, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - 3, 149, 7, 182, 122, 54, 29, 224, 103, 100, 113, 96, 90, 74, - 55, 250, 127, 94, 118, 93, 135, 160, 113, 130, 95, 117, 106, 96, -111, 97, 77, 242, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 3, 150, 4, 170, 138, 59, 20, 229, 91, 150, 107, 98, 92, 68, - 48, 245, 113, 64, 114, 111, 134, 127, 102, 104, 85, 118, 103, 107, -102, 91, 72, 245, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 3, 171, 3, 165, 137, 62, 14, 211, 96, 127, 132, 121, 95, 62, - 37, 248, 102, 57, 144, 85, 127, 191, 102, 97, 127, 104, 91, 102, -107, 81, 64, 254, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 2, 166, 2, 196, 122, 65, 10, 243, 102, 93, 117, 92, 96, 63, - 29, 251, 169, 159, 149, 96, 91, 139, 157, 40, 100, 89, 120, 92, -109, 79, 58, 247, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 2, 176, 2, 189, 118, 48, 7, 219, 68, 43, 109, 96, 129, 75, - 19, 254, 2, 3, 185, 6, 102, 127, 127, 127, 1, 131, 83, 99, -107, 80, 45, 254, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 1, 205, 2, 208, 64, 89, 4, 223, 29, 169, 29, 123, 118, 76, - 11, 240, 202, 243, 65, 6, 12, 243, 96, 55, 102, 102, 114, 102, -107, 74, 31, 247, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - - 1, 216, 1, 214, 127, 94, 2, 234, 145, 3, 127, 106, 155, 80, - 4, 247, 4, 65, 86, 127, 127, 127, 127, 102, 127, 143, 143, 108, -113, 80, 16, 216, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 - 2, 199, 1, 222, 93, 94, 1, 232, 2, 65, 74, 139, 201, 48, - 2, 254, 169, 127, 52, 243, 251, 249, 102, 86, 202, 153, 65, 65, - 146, 69, 8, 238, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128 - -Table M.3 – Protocol Buffer descriptor of top-level structure of losslessly compressed JPEG stream - - message Header { - optional uint64 width = 1; - optional uint64 height = 2; - required uint64 version_and_component_count_code = 3; - optional uint64 subsampling_code = 4; - - } - - message Jpeg { - required bytes signature = 1; - required Header header = 2; - optional bytes meta_data = 3; - optional bytes jpeg1_internals = 4; - optional bytes quant_data = 5; - optional bytes histogram_data = 6; - optional bytes dc_data = 7; - optional bytes ac_data = 8; - optional bytes original_jpg = 9; - - } - -Table M.4 – APP0 template - - 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, - 0x01, 0x00, 0x01, 0x00, 0x00 - -Table M.6 – common ICC profile template - - 0xE2, 0x0C, 0x58, 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, - 0x4C, 0x45, 0x00, 0x01, 0x01, 0x00, 0x00, 0x0C, 0x48, 0x4C, 0x69, 0x6E, - 0x6F, 0x02, 0x10, 0x00, 0x00, 0x6D, 0x6E, 0x74, 0x72, 0x52, 0x47, 0x42, - 0x20, 0x58, 0x59, 0x5A, 0x20, 0x07, 0xCE, 0x00, 0x02, 0x00, 0x09, 0x00, - 0x06, 0x00, 0x31, 0x00, 0x00, 0x61, 0x63, 0x73, 0x70, 0x4D, 0x53, 0x46, - 0x54, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x43, 0x20, 0x73, 0x52, 0x47, - 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0xF6, 0xD6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xD3, - 0x2D, 0x48, 0x50, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x11, 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x01, -0x50, 0x00, 0x00, 0x00, 0x33, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x01, -0x84, 0x00, 0x00, 0x00, 0x6C, 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, -0xF0, 0x00, 0x00, 0x00, 0x14, 0x62, 0x6B, 0x70, 0x74, 0x00, 0x00, 0x02, -0x04, 0x00, 0x00, 0x00, 0x14, 0x72, 0x58, 0x59, 0x5A, 0x00, 0x00, 0x02, -0x18, 0x00, 0x00, 0x00, 0x14, 0x67, 0x58, 0x59, 0x5A, 0x00, 0x00, 0x02, -0x2C, 0x00, 0x00, 0x00, 0x14, 0x62, 0x58, 0x59, 0x5A, 0x00, 0x00, 0x02, -0x40, 0x00, 0x00, 0x00, 0x14, 0x64, 0x6D, 0x6E, 0x64, 0x00, 0x00, 0x02, -0x54, 0x00, 0x00, 0x00, 0x70, 0x64, 0x6D, 0x64, 0x64, 0x00, 0x00, 0x02, -0xC4, 0x00, 0x00, 0x00, 0x88, 0x76, 0x75, 0x65, 0x64, 0x00, 0x00, 0x03, -0x4C, 0x00, 0x00, 0x00, 0x86, 0x76, 0x69, 0x65, 0x77, 0x00, 0x00, 0x03, -0xD4, 0x00, 0x00, 0x00, 0x24, 0x6C, 0x75, 0x6D, 0x69, 0x00, 0x00, 0x03, -0xF8, 0x00, 0x00, 0x00, 0x14, 0x6D, 0x65, 0x61, 0x73, 0x00, 0x00, 0x04, -0x0C, 0x00, 0x00, 0x00, 0x24, 0x74, 0x65, 0x63, 0x68, 0x00, 0x00, 0x04, -0x30, 0x00, 0x00, 0x00, 0x0C, 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x04, -0x3C, 0x00, 0x00, 0x08, 0x0C, 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x04, -0x3C, 0x00, 0x00, 0x08, 0x0C, 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x04, -0x3C, 0x00, 0x00, 0x08, 0x0C, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, -0x00, 0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, -0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x38, 0x20, 0x48, 0x65, 0x77, 0x6C, -0x65, 0x74, 0x74, 0x2D, 0x50, 0x61, 0x63, 0x6B, 0x61, 0x72, 0x64, 0x20, -0x43, 0x6F, 0x6D, 0x70, 0x61, 0x6E, 0x79, 0x00, 0x00, 0x64, 0x65, 0x73, -0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x73, 0x52, 0x47, -0x42, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2D, 0x32, -0x2E, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x12, 0x73, 0x52, 0x47, 0x42, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, -0x39, 0x36, 0x36, 0x2D, 0x32, 0x2E, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5A, -0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3, 0x51, 0x00, 0x01, 0x00, -0x00, 0x00, 0x01, 0x16, 0xCC, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, -0xA2, 0x00, 0x00, 0x38, 0xF5, 0x00, 0x00, 0x03, 0x90, 0x58, 0x59, 0x5A, -0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x99, 0x00, 0x00, 0xB7, -0x85, 0x00, 0x00, 0x18, 0xDA, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x24, 0xA0, 0x00, 0x00, 0x0F, 0x84, 0x00, 0x00, 0xB6, -0xCF, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x16, 0x49, 0x45, 0x43, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, - 0x77, 0x77, 0x77, 0x2E, 0x69, 0x65, 0x63, 0x2E, 0x63, 0x68, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x49, 0x45, -0x43, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, -0x2E, 0x69, 0x65, 0x63, 0x2E, 0x63, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x2E, 0x49, 0x45, 0x43, 0x20, 0x36, 0x31, 0x39, -0x36, 0x36, 0x2D, 0x32, 0x2E, 0x31, 0x20, 0x44, 0x65, 0x66, 0x61, 0x75, -0x6C, 0x74, 0x20, 0x52, 0x47, 0x42, 0x20, 0x63, 0x6F, 0x6C, 0x6F, 0x75, -0x72, 0x20, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x2D, 0x20, 0x73, 0x52, -0x47, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x2E, 0x49, 0x45, 0x43, 0x20, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2D, -0x32, 0x2E, 0x31, 0x20, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, 0x20, -0x52, 0x47, 0x42, 0x20, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72, 0x20, 0x73, -0x70, 0x61, 0x63, 0x65, 0x20, 0x2D, 0x20, 0x73, 0x52, 0x47, 0x42, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x73, -0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x52, 0x65, 0x66, -0x65, 0x72, 0x65, 0x6E, 0x63, 0x65, 0x20, 0x56, 0x69, 0x65, 0x77, 0x69, -0x6E, 0x67, 0x20, 0x43, 0x6F, 0x6E, 0x64, 0x69, 0x74, 0x69, 0x6F, 0x6E, -0x20, 0x69, 0x6E, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, -0x2D, 0x32, 0x2E, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x2C, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6E, 0x63, -0x65, 0x20, 0x56, 0x69, 0x65, 0x77, 0x69, 0x6E, 0x67, 0x20, 0x43, 0x6F, -0x6E, 0x64, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x6E, 0x20, 0x49, -0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2D, 0x32, 0x2E, 0x31, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x76, 0x69, 0x65, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xA4, -0xFE, 0x00, 0x14, 0x5F, 0x2E, 0x00, 0x10, 0xCF, 0x14, 0x00, 0x03, 0xED, -0xCC, 0x00, 0x04, 0x13, 0x0B, 0x00, 0x03, 0x5C, 0x9E, 0x00, 0x00, 0x00, -0x01, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x09, -0x56, 0x00, 0x50, 0x00, 0x00, 0x00, 0x57, 0x1F, 0xE7, 0x6D, 0x65, 0x61, -0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x02, 0x8F, 0x00, 0x00, 0x00, 0x02, 0x73, 0x69, 0x67, -0x20, 0x00, 0x00, 0x00, 0x00, 0x43, 0x52, 0x54, 0x20, 0x63, 0x75, 0x72, -0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, -0x05, 0x00, 0x0A, 0x00, 0x0F, 0x00, 0x14, 0x00, 0x19, 0x00, 0x1E, 0x00, -0x23, 0x00, 0x28, 0x00, 0x2D, 0x00, 0x32, 0x00, 0x37, 0x00, 0x3B, 0x00, -0x40, 0x00, 0x45, 0x00, 0x4A, 0x00, 0x4F, 0x00, 0x54, 0x00, 0x59, 0x00, -0x5E, 0x00, 0x63, 0x00, 0x68, 0x00, 0x6D, 0x00, 0x72, 0x00, 0x77, 0x00, -0x7C, 0x00, 0x81, 0x00, 0x86, 0x00, 0x8B, 0x00, 0x90, 0x00, 0x95, 0x00, - 0x9A, 0x00, 0x9F, 0x00, 0xA4, 0x00, 0xA9, 0x00, 0xAE, 0x00, 0xB2, 0x00, -0xB7, 0x00, 0xBC, 0x00, 0xC1, 0x00, 0xC6, 0x00, 0xCB, 0x00, 0xD0, 0x00, -0xD5, 0x00, 0xDB, 0x00, 0xE0, 0x00, 0xE5, 0x00, 0xEB, 0x00, 0xF0, 0x00, -0xF6, 0x00, 0xFB, 0x01, 0x01, 0x01, 0x07, 0x01, 0x0D, 0x01, 0x13, 0x01, -0x19, 0x01, 0x1F, 0x01, 0x25, 0x01, 0x2B, 0x01, 0x32, 0x01, 0x38, 0x01, -0x3E, 0x01, 0x45, 0x01, 0x4C, 0x01, 0x52, 0x01, 0x59, 0x01, 0x60, 0x01, -0x67, 0x01, 0x6E, 0x01, 0x75, 0x01, 0x7C, 0x01, 0x83, 0x01, 0x8B, 0x01, -0x92, 0x01, 0x9A, 0x01, 0xA1, 0x01, 0xA9, 0x01, 0xB1, 0x01, 0xB9, 0x01, -0xC1, 0x01, 0xC9, 0x01, 0xD1, 0x01, 0xD9, 0x01, 0xE1, 0x01, 0xE9, 0x01, -0xF2, 0x01, 0xFA, 0x02, 0x03, 0x02, 0x0C, 0x02, 0x14, 0x02, 0x1D, 0x02, -0x26, 0x02, 0x2F, 0x02, 0x38, 0x02, 0x41, 0x02, 0x4B, 0x02, 0x54, 0x02, -0x5D, 0x02, 0x67, 0x02, 0x71, 0x02, 0x7A, 0x02, 0x84, 0x02, 0x8E, 0x02, -0x98, 0x02, 0xA2, 0x02, 0xAC, 0x02, 0xB6, 0x02, 0xC1, 0x02, 0xCB, 0x02, -0xD5, 0x02, 0xE0, 0x02, 0xEB, 0x02, 0xF5, 0x03, 0x00, 0x03, 0x0B, 0x03, -0x16, 0x03, 0x21, 0x03, 0x2D, 0x03, 0x38, 0x03, 0x43, 0x03, 0x4F, 0x03, -0x5A, 0x03, 0x66, 0x03, 0x72, 0x03, 0x7E, 0x03, 0x8A, 0x03, 0x96, 0x03, -0xA2, 0x03, 0xAE, 0x03, 0xBA, 0x03, 0xC7, 0x03, 0xD3, 0x03, 0xE0, 0x03, -0xEC, 0x03, 0xF9, 0x04, 0x06, 0x04, 0x13, 0x04, 0x20, 0x04, 0x2D, 0x04, -0x3B, 0x04, 0x48, 0x04, 0x55, 0x04, 0x63, 0x04, 0x71, 0x04, 0x7E, 0x04, -0x8C, 0x04, 0x9A, 0x04, 0xA8, 0x04, 0xB6, 0x04, 0xC4, 0x04, 0xD3, 0x04, -0xE1, 0x04, 0xF0, 0x04, 0xFE, 0x05, 0x0D, 0x05, 0x1C, 0x05, 0x2B, 0x05, -0x3A, 0x05, 0x49, 0x05, 0x58, 0x05, 0x67, 0x05, 0x77, 0x05, 0x86, 0x05, -0x96, 0x05, 0xA6, 0x05, 0xB5, 0x05, 0xC5, 0x05, 0xD5, 0x05, 0xE5, 0x05, -0xF6, 0x06, 0x06, 0x06, 0x16, 0x06, 0x27, 0x06, 0x37, 0x06, 0x48, 0x06, -0x59, 0x06, 0x6A, 0x06, 0x7B, 0x06, 0x8C, 0x06, 0x9D, 0x06, 0xAF, 0x06, -0xC0, 0x06, 0xD1, 0x06, 0xE3, 0x06, 0xF5, 0x07, 0x07, 0x07, 0x19, 0x07, -0x2B, 0x07, 0x3D, 0x07, 0x4F, 0x07, 0x61, 0x07, 0x74, 0x07, 0x86, 0x07, -0x99, 0x07, 0xAC, 0x07, 0xBF, 0x07, 0xD2, 0x07, 0xE5, 0x07, 0xF8, 0x08, -0x0B, 0x08, 0x1F, 0x08, 0x32, 0x08, 0x46, 0x08, 0x5A, 0x08, 0x6E, 0x08, -0x82, 0x08, 0x96, 0x08, 0xAA, 0x08, 0xBE, 0x08, 0xD2, 0x08, 0xE7, 0x08, -0xFB, 0x09, 0x10, 0x09, 0x25, 0x09, 0x3A, 0x09, 0x4F, 0x09, 0x64, 0x09, -0x79, 0x09, 0x8F, 0x09, 0xA4, 0x09, 0xBA, 0x09, 0xCF, 0x09, 0xE5, 0x09, -0xFB, 0x0A, 0x11, 0x0A, 0x27, 0x0A, 0x3D, 0x0A, 0x54, 0x0A, 0x6A, 0x0A, -0x81, 0x0A, 0x98, 0x0A, 0xAE, 0x0A, 0xC5, 0x0A, 0xDC, 0x0A, 0xF3, 0x0B, -0x0B, 0x0B, 0x22, 0x0B, 0x39, 0x0B, 0x51, 0x0B, 0x69, 0x0B, 0x80, 0x0B, -0x98, 0x0B, 0xB0, 0x0B, 0xC8, 0x0B, 0xE1, 0x0B, 0xF9, 0x0C, 0x12, 0x0C, -0x2A, 0x0C, 0x43, 0x0C, 0x5C, 0x0C, 0x75, 0x0C, 0x8E, 0x0C, 0xA7, 0x0C, -0xC0, 0x0C, 0xD9, 0x0C, 0xF3, 0x0D, 0x0D, 0x0D, 0x26, 0x0D, 0x40, 0x0D, -0x5A, 0x0D, 0x74, 0x0D, 0x8E, 0x0D, 0xA9, 0x0D, 0xC3, 0x0D, 0xDE, 0x0D, -0xF8, 0x0E, 0x13, 0x0E, 0x2E, 0x0E, 0x49, 0x0E, 0x64, 0x0E, 0x7F, 0x0E, -0x9B, 0x0E, 0xB6, 0x0E, 0xD2, 0x0E, 0xEE, 0x0F, 0x09, 0x0F, 0x25, 0x0F, -0x41, 0x0F, 0x5E, 0x0F, 0x7A, 0x0F, 0x96, 0x0F, 0xB3, 0x0F, 0xCF, 0x0F, -0xEC, 0x10, 0x09, 0x10, 0x26, 0x10, 0x43, 0x10, 0x61, 0x10, 0x7E, 0x10, -0x9B, 0x10, 0xB9, 0x10, 0xD7, 0x10, 0xF5, 0x11, 0x13, 0x11, 0x31, 0x11, -0x4F, 0x11, 0x6D, 0x11, 0x8C, 0x11, 0xAA, 0x11, 0xC9, 0x11, 0xE8, 0x12, - 0x07, 0x12, 0x26, 0x12, 0x45, 0x12, 0x64, 0x12, 0x84, 0x12, 0xA3, 0x12, -0xC3, 0x12, 0xE3, 0x13, 0x03, 0x13, 0x23, 0x13, 0x43, 0x13, 0x63, 0x13, -0x83, 0x13, 0xA4, 0x13, 0xC5, 0x13, 0xE5, 0x14, 0x06, 0x14, 0x27, 0x14, -0x49, 0x14, 0x6A, 0x14, 0x8B, 0x14, 0xAD, 0x14, 0xCE, 0x14, 0xF0, 0x15, -0x12, 0x15, 0x34, 0x15, 0x56, 0x15, 0x78, 0x15, 0x9B, 0x15, 0xBD, 0x15, -0xE0, 0x16, 0x03, 0x16, 0x26, 0x16, 0x49, 0x16, 0x6C, 0x16, 0x8F, 0x16, -0xB2, 0x16, 0xD6, 0x16, 0xFA, 0x17, 0x1D, 0x17, 0x41, 0x17, 0x65, 0x17, -0x89, 0x17, 0xAE, 0x17, 0xD2, 0x17, 0xF7, 0x18, 0x1B, 0x18, 0x40, 0x18, -0x65, 0x18, 0x8A, 0x18, 0xAF, 0x18, 0xD5, 0x18, 0xFA, 0x19, 0x20, 0x19, -0x45, 0x19, 0x6B, 0x19, 0x91, 0x19, 0xB7, 0x19, 0xDD, 0x1A, 0x04, 0x1A, -0x2A, 0x1A, 0x51, 0x1A, 0x77, 0x1A, 0x9E, 0x1A, 0xC5, 0x1A, 0xEC, 0x1B, -0x14, 0x1B, 0x3B, 0x1B, 0x63, 0x1B, 0x8A, 0x1B, 0xB2, 0x1B, 0xDA, 0x1C, -0x02, 0x1C, 0x2A, 0x1C, 0x52, 0x1C, 0x7B, 0x1C, 0xA3, 0x1C, 0xCC, 0x1C, -0xF5, 0x1D, 0x1E, 0x1D, 0x47, 0x1D, 0x70, 0x1D, 0x99, 0x1D, 0xC3, 0x1D, -0xEC, 0x1E, 0x16, 0x1E, 0x40, 0x1E, 0x6A, 0x1E, 0x94, 0x1E, 0xBE, 0x1E, -0xE9, 0x1F, 0x13, 0x1F, 0x3E, 0x1F, 0x69, 0x1F, 0x94, 0x1F, 0xBF, 0x1F, -0xEA, 0x20, 0x15, 0x20, 0x41, 0x20, 0x6C, 0x20, 0x98, 0x20, 0xC4, 0x20, -0xF0, 0x21, 0x1C, 0x21, 0x48, 0x21, 0x75, 0x21, 0xA1, 0x21, 0xCE, 0x21, -0xFB, 0x22, 0x27, 0x22, 0x55, 0x22, 0x82, 0x22, 0xAF, 0x22, 0xDD, 0x23, -0x0A, 0x23, 0x38, 0x23, 0x66, 0x23, 0x94, 0x23, 0xC2, 0x23, 0xF0, 0x24, -0x1F, 0x24, 0x4D, 0x24, 0x7C, 0x24, 0xAB, 0x24, 0xDA, 0x25, 0x09, 0x25, -0x38, 0x25, 0x68, 0x25, 0x97, 0x25, 0xC7, 0x25, 0xF7, 0x26, 0x27, 0x26, -0x57, 0x26, 0x87, 0x26, 0xB7, 0x26, 0xE8, 0x27, 0x18, 0x27, 0x49, 0x27, -0x7A, 0x27, 0xAB, 0x27, 0xDC, 0x28, 0x0D, 0x28, 0x3F, 0x28, 0x71, 0x28, -0xA2, 0x28, 0xD4, 0x29, 0x06, 0x29, 0x38, 0x29, 0x6B, 0x29, 0x9D, 0x29, -0xD0, 0x2A, 0x02, 0x2A, 0x35, 0x2A, 0x68, 0x2A, 0x9B, 0x2A, 0xCF, 0x2B, -0x02, 0x2B, 0x36, 0x2B, 0x69, 0x2B, 0x9D, 0x2B, 0xD1, 0x2C, 0x05, 0x2C, -0x39, 0x2C, 0x6E, 0x2C, 0xA2, 0x2C, 0xD7, 0x2D, 0x0C, 0x2D, 0x41, 0x2D, -0x76, 0x2D, 0xAB, 0x2D, 0xE1, 0x2E, 0x16, 0x2E, 0x4C, 0x2E, 0x82, 0x2E, -0xB7, 0x2E, 0xEE, 0x2F, 0x24, 0x2F, 0x5A, 0x2F, 0x91, 0x2F, 0xC7, 0x2F, -0xFE, 0x30, 0x35, 0x30, 0x6C, 0x30, 0xA4, 0x30, 0xDB, 0x31, 0x12, 0x31, -0x4A, 0x31, 0x82, 0x31, 0xBA, 0x31, 0xF2, 0x32, 0x2A, 0x32, 0x63, 0x32, -0x9B, 0x32, 0xD4, 0x33, 0x0D, 0x33, 0x46, 0x33, 0x7F, 0x33, 0xB8, 0x33, -0xF1, 0x34, 0x2B, 0x34, 0x65, 0x34, 0x9E, 0x34, 0xD8, 0x35, 0x13, 0x35, -0x4D, 0x35, 0x87, 0x35, 0xC2, 0x35, 0xFD, 0x36, 0x37, 0x36, 0x72, 0x36, -0xAE, 0x36, 0xE9, 0x37, 0x24, 0x37, 0x60, 0x37, 0x9C, 0x37, 0xD7, 0x38, -0x14, 0x38, 0x50, 0x38, 0x8C, 0x38, 0xC8, 0x39, 0x05, 0x39, 0x42, 0x39, -0x7F, 0x39, 0xBC, 0x39, 0xF9, 0x3A, 0x36, 0x3A, 0x74, 0x3A, 0xB2, 0x3A, -0xEF, 0x3B, 0x2D, 0x3B, 0x6B, 0x3B, 0xAA, 0x3B, 0xE8, 0x3C, 0x27, 0x3C, -0x65, 0x3C, 0xA4, 0x3C, 0xE3, 0x3D, 0x22, 0x3D, 0x61, 0x3D, 0xA1, 0x3D, -0xE0, 0x3E, 0x20, 0x3E, 0x60, 0x3E, 0xA0, 0x3E, 0xE0, 0x3F, 0x21, 0x3F, -0x61, 0x3F, 0xA2, 0x3F, 0xE2, 0x40, 0x23, 0x40, 0x64, 0x40, 0xA6, 0x40, -0xE7, 0x41, 0x29, 0x41, 0x6A, 0x41, 0xAC, 0x41, 0xEE, 0x42, 0x30, 0x42, -0x72, 0x42, 0xB5, 0x42, 0xF7, 0x43, 0x3A, 0x43, 0x7D, 0x43, 0xC0, 0x44, -0x03, 0x44, 0x47, 0x44, 0x8A, 0x44, 0xCE, 0x45, 0x12, 0x45, 0x55, 0x45, - 0x9A, 0x45, 0xDE, 0x46, 0x22, 0x46, 0x67, 0x46, 0xAB, 0x46, 0xF0, 0x47, -0x35, 0x47, 0x7B, 0x47, 0xC0, 0x48, 0x05, 0x48, 0x4B, 0x48, 0x91, 0x48, -0xD7, 0x49, 0x1D, 0x49, 0x63, 0x49, 0xA9, 0x49, 0xF0, 0x4A, 0x37, 0x4A, -0x7D, 0x4A, 0xC4, 0x4B, 0x0C, 0x4B, 0x53, 0x4B, 0x9A, 0x4B, 0xE2, 0x4C, -0x2A, 0x4C, 0x72, 0x4C, 0xBA, 0x4D, 0x02, 0x4D, 0x4A, 0x4D, 0x93, 0x4D, -0xDC, 0x4E, 0x25, 0x4E, 0x6E, 0x4E, 0xB7, 0x4F, 0x00, 0x4F, 0x49, 0x4F, -0x93, 0x4F, 0xDD, 0x50, 0x27, 0x50, 0x71, 0x50, 0xBB, 0x51, 0x06, 0x51, -0x50, 0x51, 0x9B, 0x51, 0xE6, 0x52, 0x31, 0x52, 0x7C, 0x52, 0xC7, 0x53, -0x13, 0x53, 0x5F, 0x53, 0xAA, 0x53, 0xF6, 0x54, 0x42, 0x54, 0x8F, 0x54, -0xDB, 0x55, 0x28, 0x55, 0x75, 0x55, 0xC2, 0x56, 0x0F, 0x56, 0x5C, 0x56, -0xA9, 0x56, 0xF7, 0x57, 0x44, 0x57, 0x92, 0x57, 0xE0, 0x58, 0x2F, 0x58, -0x7D, 0x58, 0xCB, 0x59, 0x1A, 0x59, 0x69, 0x59, 0xB8, 0x5A, 0x07, 0x5A, -0x56, 0x5A, 0xA6, 0x5A, 0xF5, 0x5B, 0x45, 0x5B, 0x95, 0x5B, 0xE5, 0x5C, -0x35, 0x5C, 0x86, 0x5C, 0xD6, 0x5D, 0x27, 0x5D, 0x78, 0x5D, 0xC9, 0x5E, -0x1A, 0x5E, 0x6C, 0x5E, 0xBD, 0x5F, 0x0F, 0x5F, 0x61, 0x5F, 0xB3, 0x60, -0x05, 0x60, 0x57, 0x60, 0xAA, 0x60, 0xFC, 0x61, 0x4F, 0x61, 0xA2, 0x61, -0xF5, 0x62, 0x49, 0x62, 0x9C, 0x62, 0xF0, 0x63, 0x43, 0x63, 0x97, 0x63, -0xEB, 0x64, 0x40, 0x64, 0x94, 0x64, 0xE9, 0x65, 0x3D, 0x65, 0x92, 0x65, -0xE7, 0x66, 0x3D, 0x66, 0x92, 0x66, 0xE8, 0x67, 0x3D, 0x67, 0x93, 0x67, -0xE9, 0x68, 0x3F, 0x68, 0x96, 0x68, 0xEC, 0x69, 0x43, 0x69, 0x9A, 0x69, -0xF1, 0x6A, 0x48, 0x6A, 0x9F, 0x6A, 0xF7, 0x6B, 0x4F, 0x6B, 0xA7, 0x6B, -0xFF, 0x6C, 0x57, 0x6C, 0xAF, 0x6D, 0x08, 0x6D, 0x60, 0x6D, 0xB9, 0x6E, -0x12, 0x6E, 0x6B, 0x6E, 0xC4, 0x6F, 0x1E, 0x6F, 0x78, 0x6F, 0xD1, 0x70, -0x2B, 0x70, 0x86, 0x70, 0xE0, 0x71, 0x3A, 0x71, 0x95, 0x71, 0xF0, 0x72, -0x4B, 0x72, 0xA6, 0x73, 0x01, 0x73, 0x5D, 0x73, 0xB8, 0x74, 0x14, 0x74, -0x70, 0x74, 0xCC, 0x75, 0x28, 0x75, 0x85, 0x75, 0xE1, 0x76, 0x3E, 0x76, -0x9B, 0x76, 0xF8, 0x77, 0x56, 0x77, 0xB3, 0x78, 0x11, 0x78, 0x6E, 0x78, -0xCC, 0x79, 0x2A, 0x79, 0x89, 0x79, 0xE7, 0x7A, 0x46, 0x7A, 0xA5, 0x7B, -0x04, 0x7B, 0x63, 0x7B, 0xC2, 0x7C, 0x21, 0x7C, 0x81, 0x7C, 0xE1, 0x7D, -0x41, 0x7D, 0xA1, 0x7E, 0x01, 0x7E, 0x62, 0x7E, 0xC2, 0x7F, 0x23, 0x7F, -0x84, 0x7F, 0xE5, 0x80, 0x47, 0x80, 0xA8, 0x81, 0x0A, 0x81, 0x6B, 0x81, -0xCD, 0x82, 0x30, 0x82, 0x92, 0x82, 0xF4, 0x83, 0x57, 0x83, 0xBA, 0x84, -0x1D, 0x84, 0x80, 0x84, 0xE3, 0x85, 0x47, 0x85, 0xAB, 0x86, 0x0E, 0x86, -0x72, 0x86, 0xD7, 0x87, 0x3B, 0x87, 0x9F, 0x88, 0x04, 0x88, 0x69, 0x88, -0xCE, 0x89, 0x33, 0x89, 0x99, 0x89, 0xFE, 0x8A, 0x64, 0x8A, 0xCA, 0x8B, -0x30, 0x8B, 0x96, 0x8B, 0xFC, 0x8C, 0x63, 0x8C, 0xCA, 0x8D, 0x31, 0x8D, -0x98, 0x8D, 0xFF, 0x8E, 0x66, 0x8E, 0xCE, 0x8F, 0x36, 0x8F, 0x9E, 0x90, -0x06, 0x90, 0x6E, 0x90, 0xD6, 0x91, 0x3F, 0x91, 0xA8, 0x92, 0x11, 0x92, -0x7A, 0x92, 0xE3, 0x93, 0x4D, 0x93, 0xB6, 0x94, 0x20, 0x94, 0x8A, 0x94, -0xF4, 0x95, 0x5F, 0x95, 0xC9, 0x96, 0x34, 0x96, 0x9F, 0x97, 0x0A, 0x97, -0x75, 0x97, 0xE0, 0x98, 0x4C, 0x98, 0xB8, 0x99, 0x24, 0x99, 0x90, 0x99, -0xFC, 0x9A, 0x68, 0x9A, 0xD5, 0x9B, 0x42, 0x9B, 0xAF, 0x9C, 0x1C, 0x9C, -0x89, 0x9C, 0xF7, 0x9D, 0x64, 0x9D, 0xD2, 0x9E, 0x40, 0x9E, 0xAE, 0x9F, -0x1D, 0x9F, 0x8B, 0x9F, 0xFA, 0xA0, 0x69, 0xA0, 0xD8, 0xA1, 0x47, 0xA1, -0xB6, 0xA2, 0x26, 0xA2, 0x96, 0xA3, 0x06, 0xA3, 0x76, 0xA3, 0xE6, 0xA4, - 0x56, 0xA4, 0xC7, 0xA5, 0x38, 0xA5, 0xA9, 0xA6, 0x1A, 0xA6, 0x8B, 0xA6, - 0xFD, 0xA7, 0x6E, 0xA7, 0xE0, 0xA8, 0x52, 0xA8, 0xC4, 0xA9, 0x37, 0xA9, - 0xA9, 0xAA, 0x1C, 0xAA, 0x8F, 0xAB, 0x02, 0xAB, 0x75, 0xAB, 0xE9, 0xAC, - 0x5C, 0xAC, 0xD0, 0xAD, 0x44, 0xAD, 0xB8, 0xAE, 0x2D, 0xAE, 0xA1, 0xAF, - 0x16, 0xAF, 0x8B, 0xB0, 0x00, 0xB0, 0x75, 0xB0, 0xEA, 0xB1, 0x60, 0xB1, - 0xD6, 0xB2, 0x4B, 0xB2, 0xC2, 0xB3, 0x38, 0xB3, 0xAE, 0xB4, 0x25, 0xB4, - 0x9C, 0xB5, 0x13, 0xB5, 0x8A, 0xB6, 0x01, 0xB6, 0x79, 0xB6, 0xF0, 0xB7, - 0x68, 0xB7, 0xE0, 0xB8, 0x59, 0xB8, 0xD1, 0xB9, 0x4A, 0xB9, 0xC2, 0xBA, - 0x3B, 0xBA, 0xB5, 0xBB, 0x2E, 0xBB, 0xA7, 0xBC, 0x21, 0xBC, 0x9B, 0xBD, - 0x15, 0xBD, 0x8F, 0xBE, 0x0A, 0xBE, 0x84, 0xBE, 0xFF, 0xBF, 0x7A, 0xBF, - 0xF5, 0xC0, 0x70, 0xC0, 0xEC, 0xC1, 0x67, 0xC1, 0xE3, 0xC2, 0x5F, 0xC2, - 0xDB, 0xC3, 0x58, 0xC3, 0xD4, 0xC4, 0x51, 0xC4, 0xCE, 0xC5, 0x4B, 0xC5, - 0xC8, 0xC6, 0x46, 0xC6, 0xC3, 0xC7, 0x41, 0xC7, 0xBF, 0xC8, 0x3D, 0xC8, - 0xBC, 0xC9, 0x3A, 0xC9, 0xB9, 0xCA, 0x38, 0xCA, 0xB7, 0xCB, 0x36, 0xCB, - 0xB6, 0xCC, 0x35, 0xCC, 0xB5, 0xCD, 0x35, 0xCD, 0xB5, 0xCE, 0x36, 0xCE, - 0xB6, 0xCF, 0x37, 0xCF, 0xB8, 0xD0, 0x39, 0xD0, 0xBA, 0xD1, 0x3C, 0xD1, - 0xBE, 0xD2, 0x3F, 0xD2, 0xC1, 0xD3, 0x44, 0xD3, 0xC6, 0xD4, 0x49, 0xD4, - 0xCB, 0xD5, 0x4E, 0xD5, 0xD1, 0xD6, 0x55, 0xD6, 0xD8, 0xD7, 0x5C, 0xD7, - 0xE0, 0xD8, 0x64, 0xD8, 0xE8, 0xD9, 0x6C, 0xD9, 0xF1, 0xDA, 0x76, 0xDA, - 0xFB, 0xDB, 0x80, 0xDC, 0x05, 0xDC, 0x8A, 0xDD, 0x10, 0xDD, 0x96, 0xDE, - 0x1C, 0xDE, 0xA2, 0xDF, 0x29, 0xDF, 0xAF, 0xE0, 0x36, 0xE0, 0xBD, 0xE1, - 0x44, 0xE1, 0xCC, 0xE2, 0x53, 0xE2, 0xDB, 0xE3, 0x63, 0xE3, 0xEB, 0xE4, - 0x73, 0xE4, 0xFC, 0xE5, 0x84, 0xE6, 0x0D, 0xE6, 0x96, 0xE7, 0x1F, 0xE7, - 0xA9, 0xE8, 0x32, 0xE8, 0xBC, 0xE9, 0x46, 0xE9, 0xD0, 0xEA, 0x5B, 0xEA, - 0xE5, 0xEB, 0x70, 0xEB, 0xFB, 0xEC, 0x86, 0xED, 0x11, 0xED, 0x9C, 0xEE, - 0x28, 0xEE, 0xB4, 0xEF, 0x40, 0xEF, 0xCC, 0xF0, 0x58, 0xF0, 0xE5, 0xF1, - 0x72, 0xF1, 0xFF, 0xF2, 0x8C, 0xF3, 0x19, 0xF3, 0xA7, 0xF4, 0x34, 0xF4, - 0xC2, 0xF5, 0x50, 0xF5, 0xDE, 0xF6, 0x6D, 0xF6, 0xFB, 0xF7, 0x8A, 0xF8, - 0x19, 0xF8, 0xA8, 0xF9, 0x38, 0xF9, 0xC7, 0xFA, 0x57, 0xFA, 0xE7, 0xFB, - 0x77, 0xFC, 0x07, 0xFC, 0x98, 0xFD, 0x29, 0xFD, 0xBA, 0xFE, 0x4B, 0xFE, - 0xDC, 0xFF, 0x6D, 0xFF, 0xFF - -Table M.7 – "Ducky" marker template - - 0xEC, 0x00, 0x11, 0x44, 0x75, 0x63, 0x6B, 0x79, 0x00, 0x01, 0x00, 0x04, - 0x00, 0x00, 0x00, 0x64, 0x00, 0x00 - -Table M.8 – "Adobe" marker template - - 0xEE, 0x00, 0x0E, 0x41, 0x64, 0x6F, 0x62, 0x65, 0x00, 0x64, 0x00, 0x00, - 0x00, 0x00, 0x01 - -Table M.9 – stock counts arrays - is_ac == 0, stock_index == 0: - - 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0 - -is_ac == 0, stock_index == 1: - - 0, 0, 1, 5, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0 - -is_ac == 1, stock_index == 0: - - 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 126 - -is_ac == 1, stock_index == 1: - - 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 120 - -Table M.10 – stock values arrays - -is_ac == 0, stock_index == 0: - - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 256 - -is_ac == 0, stock_index == 1: - - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 256 - -is_ac == 1, stock_index == 0: - - 1, 2, 3, 0, 4, 17, 5, 18, 33, 49, 65, 6, 19, 81, - 97, 7, 34, 113, 20, 50, 129, 145, 161, 8, 35, 66, 177, 193, - 21, 82, 209, 240, 36, 51, 98, 114, 130, 9, 10, 22, 23, 24, - 25, 26, 37, 38, 39, 40, 41, 42, 52, 53, 54, 55, 56, 57, - 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, - 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, - 118, 119, 120, 121, 122, 131, 132, 133, 134, 135, 136, 137, 138, 146, - 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, - 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, 194, 195, - 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216, - 217, 218, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 241, 242, - 243, 244, 245, 246, 247, 248, 249, 250, 256 - -is_ac == 1, stock_index == 1: - 0, 1, 2, 3, 17, 4, 5, 33, 49, 6, 18, 65, 81, 7, - 97, 113, 19, 34, 50, 129, 8, 20, 66, 145, 161, 177, 193, 9, - 35, 51, 82, 240, 21, 98, 114, 209, 10, 22, 36, 52, 225, 37, - 241, 23, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55, 56, - 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, - 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, - 117, 118, 119, 120, 121, 122, 130, 131, 132, 133, 134, 135, 136, 137, - 138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, - 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, - 194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, - 215, 216, 217, 218, 226, 227, 228, 229, 230, 231, 232, 233, 234, 242, - 243, 244, 245, 246, 247, 248, 249, 250, 256 - -Table M.11 – predefined symbol order - -is_ac == 0: - - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 - -is_ac == 1: - - 1, 0, 2, 3, 17, 4, 5, 33, 18, 49, 65, 6, 81, 19, - 97, 7, 34, 113, 50, 129, 20, 145, 161, 8, 35, 66, 177, 193, - 21, 82, 209, 240, 36, 51, 98, 114, 9, 130, 10, 22, 52, 225, - 23, 37, 241, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55, - 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, - 86, 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, - 116, 117, 118, 119, 120, 121, 122, 131, 132, 133, 134, 135, 136, 137, - 138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, - 166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, - 194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, - 215, 216, 217, 218, 226, 227, 228, 229, 230, 231, 232, 233, 234, 242, - 243, 244, 245, 246, 247, 248, 249, 250, 16, 32, 48, 64, 80, 96, - 112, 128, 144, 160, 176, 192, 208, 11, 12, 13, 14, 15, 27, 28, - 29, 30, 31, 43, 44, 45, 46, 47, 59, 60, 61, 62, 63, 75, - 76, 77, 78, 79, 91, 92, 93, 94, 95, 107, 108, 109, 110, 111, - 123, 124, 125, 126, 127, 139, 140, 141, 142, 143, 155, 156, 157, 158, - 159, 171, 172, 173, 174, 175, 187, 188, 189, 190, 191, 203, 204, 205, - 206, 207, 219, 220, 221, 222, 223, 224, 235, 236, 237, 238, 239, 251, - 252, 253, 254, 255 - -Table M.12 – stock quant tables - is_luma == true, stock_index == 0: - - 3, 2, 2, 3, 5, 8, 10, 12, 2, 2, 3, 4, 5, 12, 12, 11, 3, 3, - 3, 5, 8, 11, 14, 11, 3, 3, 4, 6, 10, 17, 16, 12, 4, 4, 7, 11, - 14, 22, 21, 15, 5, 7, 11, 13, 16, 21, 23, 18, 10, 13, 16, 17, 21, 24, - 24, 20, 14, 18, 19, 20, 22, 20, 21, 20 - -is_luma == true, stock_index == 1: - - 8, 6, 5, 8, 12, 20, 26, 31, 6, 6, 7, 10, 13, 29, 30, 28, 7, 7, - 8, 12, 20, 29, 35, 28, 7, 9, 11, 15, 26, 44, 40, 31, 9, 11, 19, 28, - 34, 55, 52, 39, 12, 18, 28, 32, 41, 52, 57, 46, 25, 32, 39, 44, 52, 61, - 60, 51, 36, 46, 48, 49, 56, 50, 52, 50 - -is_luma == true, stock_index == 2: - - 6, 4, 4, 6, 10, 16, 20, 24, 5, 5, 6, 8, 10, 23, 24, 22, 6, 5, - 6, 10, 16, 23, 28, 22, 6, 7, 9, 12, 20, 35, 32, 25, 7, 9, 15, 22, - 27, 44, 41, 31, 10, 14, 22, 26, 32, 42, 45, 37, 20, 26, 31, 35, 41, 48, - 48, 40, 29, 37, 38, 39, 45, 40, 41, 40 - -is_luma == true, stock_index == 3: - - 5, 3, 3, 5, 7, 12, 15, 18, 4, 4, 4, 6, 8, 17, 18, 17, 4, 4, - 5, 7, 12, 17, 21, 17, 4, 5, 7, 9, 15, 26, 24, 19, 5, 7, 11, 17, - 20, 33, 31, 23, 7, 11, 17, 19, 24, 31, 34, 28, 15, 19, 23, 26, 31, 36, - 36, 30, 22, 28, 29, 29, 34, 30, 31, 30 - -is_luma == true, stock_index == 4: - - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 - -is_luma == true, stock_index == 5: - - 2, 1, 1, 2, 2, 4, 5, 6, 1, 1, 1, 2, 3, 6, 6, 6, 1, 1, - 2, 2, 4, 6, 7, 6, 1, 2, 2, 3, 5, 9, 8, 6, 2, 2, 4, 6, - 7, 11, 10, 8, 2, 4, 6, 6, 8, 10, 11, 9, 5, 6, 8, 9, 10, 12, - 12, 10, 7, 9, 10, 10, 11, 10, 10, 10 - -is_luma == true, stock_index == 6: - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, - 1, 2, 2, 3, 1, 1, 1, 1, 2, 2, 3, 3, 1, 1, 1, 2, 2, 3, - 3, 3, 1, 1, 2, 2, 3, 3, 3, 3 - -is_luma == true, stock_index == 7: - - 10, 7, 6, 10, 14, 24, 31, 37, 7, 7, 8, 11, 16, 35, 36, 33, 8, 8, - 10, 14, 24, 34, 41, 34, 8, 10, 13, 17, 31, 52, 48, 37, 11, 13, 22, 34, - 41, 65, 62, 46, 14, 21, 33, 38, 49, 62, 68, 55, 29, 38, 47, 52, 62, 73, - 72, 61, 43, 55, 57, 59, 67, 60, 62, 59 - -is_luma == false, stock_index == 0: - - 9, 9, 9, 12, 11, 12, 24, 13, 13, 24, 50, 33, 28, 33, 50, 50, 50, 50, - 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, - 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, - 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 - -is_luma == false, stock_index == 1: - - 3, 4, 5, 9, 20, 20, 20, 20, 4, 4, 5, 13, 20, 20, 20, 20, 5, 5, - 11, 20, 20, 20, 20, 20, 9, 13, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 - -is_luma == false, stock_index == 2: - - 9, 9, 12, 24, 50, 50, 50, 50, 9, 11, 13, 33, 50, 50, 50, 50, 12, 13, - 28, 50, 50, 50, 50, 50, 24, 33, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, - 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, - 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 - -is_luma == false, stock_index == 3: - - 5, 5, 7, 14, 30, 30, 30, 30, 5, 6, 8, 20, 30, 30, 30, 30, 7, 8, - 17, 30, 30, 30, 30, 30, 14, 20, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, - 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, - 30, 30, 30, 30, 30, 30, 30, 30, 30, 30 - -is_luma == false, stock_index == 4: - 7, 7, 10, 19, 40, 40, 40, 40, 7, 8, 10, 26, 40, 40, 40, 40, 10, 10, - 22, 40, 40, 40, 40, 40, 19, 26, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, - 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, - 40, 40, 40, 40, 40, 40, 40, 40, 40, 40 - -is_luma == false, stock_index == 5: - - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 - -is_luma == false, stock_index == 6: - - 2, 2, 2, 5, 10, 10, 10, 10, 2, 2, 3, 7, 10, 10, 10, 10, 2, 3, - 6, 10, 10, 10, 10, 10, 5, 7, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, - 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, - 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 - -is_luma == false, stock_index == 7: - - 10, 11, 14, 28, 59, 59, 59, 59, 11, 13, 16, 40, 59, 59, 59, 59, 14, 16, - 34, 59, 59, 59, 59, 59, 28, 40, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, - 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, - 59, 59, 59, 59, 59, 59, 59, 59, 59, 59 - -Table M.13 – template quant tables -is_luma == true: - - 16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, - 55, 14, 13, 16, 24, 40, 57, 69, 56, 14, 17, 22, 29, 51, 87, - 80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, -104, 113, 92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, -112, 100, 103, 99 - -is_luma == false: - -17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 24, 26, -56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, -99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, -99, 99, 99, 99, 99, 99, 99, 99, 99, 99 - Table M.15 – freq_context - -scheme == 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, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - -scheme == 1: - - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 - -scheme == 2: - - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1 - -scheme == 3: - - 0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 2, 2, 2 - -scheme == 4: - - 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, - 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, - 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 - -scheme == 5: - - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16, - 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 24, 24, - 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, - 29, 29, 30, 30, 30, 30, 31, 31, 31, 31 - -scheme == 6: - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, - 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 - -Table M.16 – num_nonzero_context - -scheme == 0: - - 0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 - -scheme == 1: - - 0, 2, 2, 4, 4, 4, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10, - 10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, 12, 12, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - 14, 14, 14, 14, 14, 14, 14, 14, 14, 14 - -scheme == 2: - - 0, 4, 4, 8, 8, 8, 12, 12, 12, 12, 16, 16, 16, 16, 16, 16, 20, 20, - 20, 20, 20, 20, 20, 20, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 24, 24, 24, 24, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28 - -scheme == 3: - - 0, 8, 8, 16, 16, 16, 24, 24, 24, 24, 32, 32, 32, 32, 32, 32, 40, 40, - 40, 40, 40, 40, 40, 40, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, - 55, 55, 55, 55, 55, 55, 55, 55, 55, 55 - -scheme == 4: - - 0, 16, 16, 32, 32, 32, 48, 48, 48, 48, 64, 64, 64, 64, - 64, 64, 80, 80, 80, 80, 80, 80, 80, 80, 95, 95, 95, 95, - 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 109, 109, - 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, - 109, 109, 109, 109, 109, 109, 109, 109 - -scheme == 5: - 0, 32, 32, 64, 64, 64, 96, 96, 96, 96, 127, 127, 127, 127, - 127, 127, 157, 157, 157, 157, 157, 157, 157, 157, 185, 185, 185, 185, - 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 211, 211, - 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, - 211, 211, 211, 211, 211, 211, 211, 211 - -scheme == 6: - - 0, 64, 64, 127, 127, 127, 188, 188, 188, 188, 246, 246, 246, 246, - 246, 246, 300, 300, 300, 300, 300, 300, 300, 300, 348, 348, 348, 348, - 348, 348, 348, 348, 348, 348, 348, 348, 348, 348, 348, 348, 388, 388, - 388, 388, 388, 388, 388, 388, 388, 388, 388, 388, 388, 388, 388, 388, - 388, 388, 388, 388, 388, 388, 388, 388 - -Table M.17 – nonzero_buckets - - 0, 1, 2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, - 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, - 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 - -Table M.29 – context_modes table - - 0, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, - 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, - 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0 - - 0, 1, 1, 1, 1, 0, 0, 0, 2, 3, 1, 1, 1, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, - 0, 2, 2, 0, 0, 0, 0, 0, 0, 2, 2, 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 - \ No newline at end of file diff --git a/media/libjxl/src/doc/tables/all_tables.sh b/media/libjxl/src/doc/tables/all_tables.sh deleted file mode 100644 index 6fc98eb8c9..0000000000 --- a/media/libjxl/src/doc/tables/all_tables.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) the JPEG XL Project Authors. All rights reserved. -# -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -cat dct_gen.md \ - is_zero_base.md num_nonzeros_base.md brn_proto.md app0.md icc.md ducky.md \ - adobe.md stock_counts.md stock_values.md symbol_order.md stock_quant.md \ - quant.md freq_context.md num_nonzero_context.md nonzero_buckets.md \ - context_modes.md > all_tables.md diff --git a/media/libjxl/src/doc/tables/app0.md b/media/libjxl/src/doc/tables/app0.md deleted file mode 100644 index 266f210473..0000000000 --- a/media/libjxl/src/doc/tables/app0.md +++ /dev/null @@ -1,6 +0,0 @@ -#### Table M.4 – APP0 template - -``` -0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00 -``` - diff --git a/media/libjxl/src/doc/tables/brn_proto.md b/media/libjxl/src/doc/tables/brn_proto.md deleted file mode 100644 index b5f80b6a7f..0000000000 --- a/media/libjxl/src/doc/tables/brn_proto.md +++ /dev/null @@ -1,23 +0,0 @@ -#### Table M.3 – Protocol Buffer descriptor of top-level structure of losslessly compressed JPEG stream - -```protobuf -message Header { - optional uint64 width = 1; - optional uint64 height = 2; - required uint64 version_and_component_count_code = 3; - optional uint64 subsampling_code = 4; -} - -message Jpeg { - required bytes signature = 1; - required Header header = 2; - optional bytes meta_data = 3; - optional bytes jpeg1_internals = 4; - optional bytes quant_data = 5; - optional bytes histogram_data = 6; - optional bytes dc_data = 7; - optional bytes ac_data = 8; - optional bytes original_jpg = 9; -} -``` - diff --git a/media/libjxl/src/doc/tables/context_modes.md b/media/libjxl/src/doc/tables/context_modes.md deleted file mode 100644 index 59bff36497..0000000000 --- a/media/libjxl/src/doc/tables/context_modes.md +++ /dev/null @@ -1,13 +0,0 @@ -#### Table M.29 – context_modes table - -``` -0, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, -0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, -0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0 -``` - -``` -0, 1, 1, 1, 1, 0, 0, 0, 2, 3, 1, 1, 1, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, -0, 2, 2, 0, 0, 0, 0, 0, 0, 2, 2, 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 -``` diff --git a/media/libjxl/src/doc/tables/dct_gen.md b/media/libjxl/src/doc/tables/dct_gen.md deleted file mode 100644 index f3b59e2485..0000000000 --- a/media/libjxl/src/doc/tables/dct_gen.md +++ /dev/null @@ -1,241 +0,0 @@ -#### Electronic Insert I.1 – DCT-II / DCT-III code generator - -```python -####################################################################### -# DCT-II / DCT-III generator -# -# Based on: -# "A low multiplicative complexity fast recursive DCT-2 algorithm" -# by Maxim Vashkevich and Alexander Petrovsky / arXiv / 20 Jul 2012 -####################################################################### - -import math -import sys -N = 8 - -####################################################################### -# Base transforms / generators -####################################################################### - -CNTR = 0 -def makeTmp(): - global CNTR - result = "t{:02d}".format(CNTR) - CNTR = CNTR + 1 - return result - -def makeVar(i): - return "i{:02d}".format(i) - -def add(x, y): - tmp = makeTmp() - print(tmp + " = " + x + " + " + y + ";") - return tmp - -def sub(x, y): - tmp = makeTmp() - print(tmp + " = " + x + " - " + y + ";") - return tmp - -def mul(x, c): - tmp = makeTmp() - print(tmp + " = " + x + " * " + c + ";") - return tmp - -# 2.0 * math.cos((a + 0.0) / (b + 0.0) * math.pi) -def C2(a, b): - return "c_c2_" + str(a) + "_" + str(b) - -# 1.0 / C2(a, b) -def iC2(a, b): - return "c_ic2_" + str(a) + "_" + str(b) - -####################################################################### -# Utilities -####################################################################### - -# Generate identity matrix. Usually this matrix is passed to -# DCT algorithm to generate "basis" vectors of the transform. -def makeVars(): - return [makeVar(i) for i in range(N)] - -# Split list of variables info halves. -def split(x): - m = len(x) - m2 = m // 2 - return (x[0 : m2], x[m2 : m]) - -# Make a list of variables in a reverse order. -def reverse(varz): - m = len(varz) - result = [0] * m - for i in range(m): - result[i] = varz[m - 1 - i] - return result - -# Apply permutation -def permute(x, p): - return [x[p[i]] for i in range(len(p))] - -def transposePermutation(p): - n = len(p) - result = [0] * n - for i in range(n): - result[p[i]] = i - return result - -# See paper. Split even-odd elements. -def P(n): - if n == 1: - return [0] - n2 = n // 2 - return [2 * i for i in range(n2)] + [2 * i + 1 for i in range(n2)] - -# See paper. Interleave first and second half. -def Pt(n): - return transposePermutation(P(n)) - -####################################################################### -# Scheme -####################################################################### - -def B2(x): - n = len(x) - n2 = n // 2 - if n == 1: - raise "ooops" - (top, bottom) = split(x) - bottom = reverse(bottom) - t = [add(top[i], bottom[i]) for i in range(n2)] - b = [sub(top[i], bottom[i]) for i in range(n2)] - return t + b - -def iB2(x): - n = len(x) - n2 = n // 2 - if n == 1: - raise "ooops" - (top, bottom) = split(x) - t = [add(top[i], bottom[i]) for i in range(n2)] - b = [sub(top[i], bottom[i]) for i in range(n2)] - return t + reverse(b) - -def B4(x, rn): - n = len(x) - n2 = n // 2 - if n == 1: - raise "ooops" - (top, bottom) = split(x) - rbottom = reverse(bottom) - t = [sub(top[i], rbottom[i]) for i in range(n2)] - b = [mul(bottom[i], C2(rn, 2 * N)) for i in range(n2)] - top = [add(t[i], b[i]) for i in range(n2)] - bottom = [sub(t[i], b[i]) for i in range(n2)] - return top + bottom - -def iB4(x, rn): - n = len(x) - n2 = n // 2 - if n == 1: - raise "ooops" - (top, bottom) = split(x) - t = [add(top[i], bottom[i]) for i in range(n2)] - b = [sub(top[i], bottom[i]) for i in range(n2)] - bottom = [mul(b[i], iC2(rn, 2 * N)) for i in range(n2)] - rbottom = reverse(bottom) - top = [add(t[i], rbottom[i]) for i in range(n2)] - return top + bottom - -def P4(n): - if n == 1: - return [0] - if n == 2: - return [0, 1] - n2 = n // 2 - result = [0] * n - tc = 0 - bc = 0 - i = 0 - result[i] = tc; tc = tc + 1; i = i + 1 - turn = True - while i < n - 1: - if turn: - result[i] = n2 + bc; bc = bc + 1; i = i + 1 - result[i] = n2 + bc; bc = bc + 1; i = i + 1 - else: - result[i] = tc; tc = tc + 1; i = i + 1 - result[i] = tc; tc = tc + 1; i = i + 1 - turn = not turn - result[i] = tc; tc = tc + 1; i = i + 1 - return result - -def iP4(n): - return transposePermutation(P4(n)) - -def d2n(x): - n = len(x) - if n == 1: - return x - y = B2(x) - (top, bottom) = split(y) - return permute(d2n(top) + d4n(bottom, N // 2), Pt(n)) - -def id2n(x): - n = len(x) - if n == 1: - return x - (top, bottom) = split(permute(x, P(n))) - return iB2(id2n(top) + id4n(bottom, N // 2)) - -def d4n(x, rn): - n = len(x) - if n == 1: - return x - y = B4(x, rn) - (top, bottom) = split(y) - rn2 = rn // 2 - return permute(d4n(top, rn2) + d4n(bottom, N - rn2), P4(n)) - -def id4n(x, rn): - n = len(x) - if n == 1: - return x - (top, bottom) = split(permute(x, iP4(n))) - rn2 = rn // 2 - y = id4n(top, rn2) + id4n(bottom, N -rn2) - return iB4(y, rn) - -####################################################################### -# Main. -####################################################################### - -def help(): - print("Usage: %s [N [T]]" % sys.argv[0]) - print(" N should be the power of 2, default is 8") - print(" T is one of {2, 3}, default is 2") - sys.exit() - -def parseInt(s): - try: - return int(s) - except ValueError: - help() - -if __name__ == "__main__": - if len(sys.argv) < 1 or len(sys.argv) > 3: help() - if len(sys.argv) >= 2: - N = parseInt(sys.argv[1]) - if (N & (N - 1)) != 0: help() - type = 0 - if len(sys.argv) >= 3: - typeOption = sys.argv[2] - if len(typeOption) != 1: help() - type = "23".index(typeOption) - if type == -1: help() - if type == 0: - vars = d2n(makeVars()) - else: # type == 1 - vars = id2n(makeVars()) - print("Output vector: " + str(vars)) -``` - diff --git a/media/libjxl/src/doc/tables/ducky.md b/media/libjxl/src/doc/tables/ducky.md deleted file mode 100644 index 307f688042..0000000000 --- a/media/libjxl/src/doc/tables/ducky.md +++ /dev/null @@ -1,6 +0,0 @@ -#### Table M.7 – "Ducky" marker template - -``` -0xEC, 0x00, 0x11, 0x44, 0x75, 0x63, 0x6B, 0x79, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00 -``` - diff --git a/media/libjxl/src/doc/tables/freq_context.md b/media/libjxl/src/doc/tables/freq_context.md deleted file mode 100644 index 3e218fb1b8..0000000000 --- a/media/libjxl/src/doc/tables/freq_context.md +++ /dev/null @@ -1,54 +0,0 @@ -#### Table M.15 – freq_context - -`scheme == 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, 0, -0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 -``` - -`scheme == 1`: -``` -0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 -``` - -`scheme == 2`: -``` -0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, -3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1 -``` - -`scheme == 3`: -``` -0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, -6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, -7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 2, 2, 2 -``` - -`scheme == 4`: -``` - 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, - 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, -13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, -15, 15, 15, 15, 15, 15, 15, 15, 15, 15 -``` - -`scheme == 5`: -``` - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16, -17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 24, 24, -25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, -29, 29, 30, 30, 30, 30, 31, 31, 31, 31 -``` - -`scheme == 6`: -``` - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, -18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, -54, 55, 56, 57, 58, 59, 60, 61, 62, 63 -``` - diff --git a/media/libjxl/src/doc/tables/icc.md b/media/libjxl/src/doc/tables/icc.md deleted file mode 100644 index 1f3b4cde39..0000000000 --- a/media/libjxl/src/doc/tables/icc.md +++ /dev/null @@ -1,6 +0,0 @@ -#### Table M.6 – common ICC profile template - -``` -0xE2, 0x0C, 0x58, 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00, 0x01, 0x01, 0x00, 0x00, 0x0C, 0x48, 0x4C, 0x69, 0x6E, 0x6F, 0x02, 0x10, 0x00, 0x00, 0x6D, 0x6E, 0x74, 0x72, 0x52, 0x47, 0x42, 0x20, 0x58, 0x59, 0x5A, 0x20, 0x07, 0xCE, 0x00, 0x02, 0x00, 0x09, 0x00, 0x06, 0x00, 0x31, 0x00, 0x00, 0x61, 0x63, 0x73, 0x70, 0x4D, 0x53, 0x46, 0x54, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x43, 0x20, 0x73, 0x52, 0x47, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xF6, 0xD6, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xD3, 0x2D, 0x48, 0x50, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x63, 0x70, 0x72, 0x74, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x33, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x01, 0x84, 0x00, 0x00, 0x00, 0x6C, 0x77, 0x74, 0x70, 0x74, 0x00, 0x00, 0x01, 0xF0, 0x00, 0x00, 0x00, 0x14, 0x62, 0x6B, 0x70, 0x74, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x14, 0x72, 0x58, 0x59, 0x5A, 0x00, 0x00, 0x02, 0x18, 0x00, 0x00, 0x00, 0x14, 0x67, 0x58, 0x59, 0x5A, 0x00, 0x00, 0x02, 0x2C, 0x00, 0x00, 0x00, 0x14, 0x62, 0x58, 0x59, 0x5A, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x14, 0x64, 0x6D, 0x6E, 0x64, 0x00, 0x00, 0x02, 0x54, 0x00, 0x00, 0x00, 0x70, 0x64, 0x6D, 0x64, 0x64, 0x00, 0x00, 0x02, 0xC4, 0x00, 0x00, 0x00, 0x88, 0x76, 0x75, 0x65, 0x64, 0x00, 0x00, 0x03, 0x4C, 0x00, 0x00, 0x00, 0x86, 0x76, 0x69, 0x65, 0x77, 0x00, 0x00, 0x03, 0xD4, 0x00, 0x00, 0x00, 0x24, 0x6C, 0x75, 0x6D, 0x69, 0x00, 0x00, 0x03, 0xF8, 0x00, 0x00, 0x00, 0x14, 0x6D, 0x65, 0x61, 0x73, 0x00, 0x00, 0x04, 0x0C, 0x00, 0x00, 0x00, 0x24, 0x74, 0x65, 0x63, 0x68, 0x00, 0x00, 0x04, 0x30, 0x00, 0x00, 0x00, 0x0C, 0x72, 0x54, 0x52, 0x43, 0x00, 0x00, 0x04, 0x3C, 0x00, 0x00, 0x08, 0x0C, 0x67, 0x54, 0x52, 0x43, 0x00, 0x00, 0x04, 0x3C, 0x00, 0x00, 0x08, 0x0C, 0x62, 0x54, 0x52, 0x43, 0x00, 0x00, 0x04, 0x3C, 0x00, 0x00, 0x08, 0x0C, 0x74, 0x65, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x28, 0x63, 0x29, 0x20, 0x31, 0x39, 0x39, 0x38, 0x20, 0x48, 0x65, 0x77, 0x6C, 0x65, 0x74, 0x74, 0x2D, 0x50, 0x61, 0x63, 0x6B, 0x61, 0x72, 0x64, 0x20, 0x43, 0x6F, 0x6D, 0x70, 0x61, 0x6E, 0x79, 0x00, 0x00, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x73, 0x52, 0x47, 0x42, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2D, 0x32, 0x2E, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x73, 0x52, 0x47, 0x42, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2D, 0x32, 0x2E, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF3, 0x51, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x16, 0xCC, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6F, 0xA2, 0x00, 0x00, 0x38, 0xF5, 0x00, 0x00, 0x03, 0x90, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x99, 0x00, 0x00, 0xB7, 0x85, 0x00, 0x00, 0x18, 0xDA, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0xA0, 0x00, 0x00, 0x0F, 0x84, 0x00, 0x00, 0xB6, 0xCF, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x49, 0x45, 0x43, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x65, 0x63, 0x2E, 0x63, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x49, 0x45, 0x43, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x65, 0x63, 0x2E, 0x63, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x49, 0x45, 0x43, 0x20, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2D, 0x32, 0x2E, 0x31, 0x20, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, 0x20, 0x52, 0x47, 0x42, 0x20, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72, 0x20, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x2D, 0x20, 0x73, 0x52, 0x47, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x49, 0x45, 0x43, 0x20, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2D, 0x32, 0x2E, 0x31, 0x20, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6C, 0x74, 0x20, 0x52, 0x47, 0x42, 0x20, 0x63, 0x6F, 0x6C, 0x6F, 0x75, 0x72, 0x20, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x2D, 0x20, 0x73, 0x52, 0x47, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x65, 0x73, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6E, 0x63, 0x65, 0x20, 0x56, 0x69, 0x65, 0x77, 0x69, 0x6E, 0x67, 0x20, 0x43, 0x6F, 0x6E, 0x64, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x6E, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2D, 0x32, 0x2E, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6E, 0x63, 0x65, 0x20, 0x56, 0x69, 0x65, 0x77, 0x69, 0x6E, 0x67, 0x20, 0x43, 0x6F, 0x6E, 0x64, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x69, 0x6E, 0x20, 0x49, 0x45, 0x43, 0x36, 0x31, 0x39, 0x36, 0x36, 0x2D, 0x32, 0x2E, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x69, 0x65, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xA4, 0xFE, 0x00, 0x14, 0x5F, 0x2E, 0x00, 0x10, 0xCF, 0x14, 0x00, 0x03, 0xED, 0xCC, 0x00, 0x04, 0x13, 0x0B, 0x00, 0x03, 0x5C, 0x9E, 0x00, 0x00, 0x00, 0x01, 0x58, 0x59, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 0x09, 0x56, 0x00, 0x50, 0x00, 0x00, 0x00, 0x57, 0x1F, 0xE7, 0x6D, 0x65, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x8F, 0x00, 0x00, 0x00, 0x02, 0x73, 0x69, 0x67, 0x20, 0x00, 0x00, 0x00, 0x00, 0x43, 0x52, 0x54, 0x20, 0x63, 0x75, 0x72, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x0F, 0x00, 0x14, 0x00, 0x19, 0x00, 0x1E, 0x00, 0x23, 0x00, 0x28, 0x00, 0x2D, 0x00, 0x32, 0x00, 0x37, 0x00, 0x3B, 0x00, 0x40, 0x00, 0x45, 0x00, 0x4A, 0x00, 0x4F, 0x00, 0x54, 0x00, 0x59, 0x00, 0x5E, 0x00, 0x63, 0x00, 0x68, 0x00, 0x6D, 0x00, 0x72, 0x00, 0x77, 0x00, 0x7C, 0x00, 0x81, 0x00, 0x86, 0x00, 0x8B, 0x00, 0x90, 0x00, 0x95, 0x00, 0x9A, 0x00, 0x9F, 0x00, 0xA4, 0x00, 0xA9, 0x00, 0xAE, 0x00, 0xB2, 0x00, 0xB7, 0x00, 0xBC, 0x00, 0xC1, 0x00, 0xC6, 0x00, 0xCB, 0x00, 0xD0, 0x00, 0xD5, 0x00, 0xDB, 0x00, 0xE0, 0x00, 0xE5, 0x00, 0xEB, 0x00, 0xF0, 0x00, 0xF6, 0x00, 0xFB, 0x01, 0x01, 0x01, 0x07, 0x01, 0x0D, 0x01, 0x13, 0x01, 0x19, 0x01, 0x1F, 0x01, 0x25, 0x01, 0x2B, 0x01, 0x32, 0x01, 0x38, 0x01, 0x3E, 0x01, 0x45, 0x01, 0x4C, 0x01, 0x52, 0x01, 0x59, 0x01, 0x60, 0x01, 0x67, 0x01, 0x6E, 0x01, 0x75, 0x01, 0x7C, 0x01, 0x83, 0x01, 0x8B, 0x01, 0x92, 0x01, 0x9A, 0x01, 0xA1, 0x01, 0xA9, 0x01, 0xB1, 0x01, 0xB9, 0x01, 0xC1, 0x01, 0xC9, 0x01, 0xD1, 0x01, 0xD9, 0x01, 0xE1, 0x01, 0xE9, 0x01, 0xF2, 0x01, 0xFA, 0x02, 0x03, 0x02, 0x0C, 0x02, 0x14, 0x02, 0x1D, 0x02, 0x26, 0x02, 0x2F, 0x02, 0x38, 0x02, 0x41, 0x02, 0x4B, 0x02, 0x54, 0x02, 0x5D, 0x02, 0x67, 0x02, 0x71, 0x02, 0x7A, 0x02, 0x84, 0x02, 0x8E, 0x02, 0x98, 0x02, 0xA2, 0x02, 0xAC, 0x02, 0xB6, 0x02, 0xC1, 0x02, 0xCB, 0x02, 0xD5, 0x02, 0xE0, 0x02, 0xEB, 0x02, 0xF5, 0x03, 0x00, 0x03, 0x0B, 0x03, 0x16, 0x03, 0x21, 0x03, 0x2D, 0x03, 0x38, 0x03, 0x43, 0x03, 0x4F, 0x03, 0x5A, 0x03, 0x66, 0x03, 0x72, 0x03, 0x7E, 0x03, 0x8A, 0x03, 0x96, 0x03, 0xA2, 0x03, 0xAE, 0x03, 0xBA, 0x03, 0xC7, 0x03, 0xD3, 0x03, 0xE0, 0x03, 0xEC, 0x03, 0xF9, 0x04, 0x06, 0x04, 0x13, 0x04, 0x20, 0x04, 0x2D, 0x04, 0x3B, 0x04, 0x48, 0x04, 0x55, 0x04, 0x63, 0x04, 0x71, 0x04, 0x7E, 0x04, 0x8C, 0x04, 0x9A, 0x04, 0xA8, 0x04, 0xB6, 0x04, 0xC4, 0x04, 0xD3, 0x04, 0xE1, 0x04, 0xF0, 0x04, 0xFE, 0x05, 0x0D, 0x05, 0x1C, 0x05, 0x2B, 0x05, 0x3A, 0x05, 0x49, 0x05, 0x58, 0x05, 0x67, 0x05, 0x77, 0x05, 0x86, 0x05, 0x96, 0x05, 0xA6, 0x05, 0xB5, 0x05, 0xC5, 0x05, 0xD5, 0x05, 0xE5, 0x05, 0xF6, 0x06, 0x06, 0x06, 0x16, 0x06, 0x27, 0x06, 0x37, 0x06, 0x48, 0x06, 0x59, 0x06, 0x6A, 0x06, 0x7B, 0x06, 0x8C, 0x06, 0x9D, 0x06, 0xAF, 0x06, 0xC0, 0x06, 0xD1, 0x06, 0xE3, 0x06, 0xF5, 0x07, 0x07, 0x07, 0x19, 0x07, 0x2B, 0x07, 0x3D, 0x07, 0x4F, 0x07, 0x61, 0x07, 0x74, 0x07, 0x86, 0x07, 0x99, 0x07, 0xAC, 0x07, 0xBF, 0x07, 0xD2, 0x07, 0xE5, 0x07, 0xF8, 0x08, 0x0B, 0x08, 0x1F, 0x08, 0x32, 0x08, 0x46, 0x08, 0x5A, 0x08, 0x6E, 0x08, 0x82, 0x08, 0x96, 0x08, 0xAA, 0x08, 0xBE, 0x08, 0xD2, 0x08, 0xE7, 0x08, 0xFB, 0x09, 0x10, 0x09, 0x25, 0x09, 0x3A, 0x09, 0x4F, 0x09, 0x64, 0x09, 0x79, 0x09, 0x8F, 0x09, 0xA4, 0x09, 0xBA, 0x09, 0xCF, 0x09, 0xE5, 0x09, 0xFB, 0x0A, 0x11, 0x0A, 0x27, 0x0A, 0x3D, 0x0A, 0x54, 0x0A, 0x6A, 0x0A, 0x81, 0x0A, 0x98, 0x0A, 0xAE, 0x0A, 0xC5, 0x0A, 0xDC, 0x0A, 0xF3, 0x0B, 0x0B, 0x0B, 0x22, 0x0B, 0x39, 0x0B, 0x51, 0x0B, 0x69, 0x0B, 0x80, 0x0B, 0x98, 0x0B, 0xB0, 0x0B, 0xC8, 0x0B, 0xE1, 0x0B, 0xF9, 0x0C, 0x12, 0x0C, 0x2A, 0x0C, 0x43, 0x0C, 0x5C, 0x0C, 0x75, 0x0C, 0x8E, 0x0C, 0xA7, 0x0C, 0xC0, 0x0C, 0xD9, 0x0C, 0xF3, 0x0D, 0x0D, 0x0D, 0x26, 0x0D, 0x40, 0x0D, 0x5A, 0x0D, 0x74, 0x0D, 0x8E, 0x0D, 0xA9, 0x0D, 0xC3, 0x0D, 0xDE, 0x0D, 0xF8, 0x0E, 0x13, 0x0E, 0x2E, 0x0E, 0x49, 0x0E, 0x64, 0x0E, 0x7F, 0x0E, 0x9B, 0x0E, 0xB6, 0x0E, 0xD2, 0x0E, 0xEE, 0x0F, 0x09, 0x0F, 0x25, 0x0F, 0x41, 0x0F, 0x5E, 0x0F, 0x7A, 0x0F, 0x96, 0x0F, 0xB3, 0x0F, 0xCF, 0x0F, 0xEC, 0x10, 0x09, 0x10, 0x26, 0x10, 0x43, 0x10, 0x61, 0x10, 0x7E, 0x10, 0x9B, 0x10, 0xB9, 0x10, 0xD7, 0x10, 0xF5, 0x11, 0x13, 0x11, 0x31, 0x11, 0x4F, 0x11, 0x6D, 0x11, 0x8C, 0x11, 0xAA, 0x11, 0xC9, 0x11, 0xE8, 0x12, 0x07, 0x12, 0x26, 0x12, 0x45, 0x12, 0x64, 0x12, 0x84, 0x12, 0xA3, 0x12, 0xC3, 0x12, 0xE3, 0x13, 0x03, 0x13, 0x23, 0x13, 0x43, 0x13, 0x63, 0x13, 0x83, 0x13, 0xA4, 0x13, 0xC5, 0x13, 0xE5, 0x14, 0x06, 0x14, 0x27, 0x14, 0x49, 0x14, 0x6A, 0x14, 0x8B, 0x14, 0xAD, 0x14, 0xCE, 0x14, 0xF0, 0x15, 0x12, 0x15, 0x34, 0x15, 0x56, 0x15, 0x78, 0x15, 0x9B, 0x15, 0xBD, 0x15, 0xE0, 0x16, 0x03, 0x16, 0x26, 0x16, 0x49, 0x16, 0x6C, 0x16, 0x8F, 0x16, 0xB2, 0x16, 0xD6, 0x16, 0xFA, 0x17, 0x1D, 0x17, 0x41, 0x17, 0x65, 0x17, 0x89, 0x17, 0xAE, 0x17, 0xD2, 0x17, 0xF7, 0x18, 0x1B, 0x18, 0x40, 0x18, 0x65, 0x18, 0x8A, 0x18, 0xAF, 0x18, 0xD5, 0x18, 0xFA, 0x19, 0x20, 0x19, 0x45, 0x19, 0x6B, 0x19, 0x91, 0x19, 0xB7, 0x19, 0xDD, 0x1A, 0x04, 0x1A, 0x2A, 0x1A, 0x51, 0x1A, 0x77, 0x1A, 0x9E, 0x1A, 0xC5, 0x1A, 0xEC, 0x1B, 0x14, 0x1B, 0x3B, 0x1B, 0x63, 0x1B, 0x8A, 0x1B, 0xB2, 0x1B, 0xDA, 0x1C, 0x02, 0x1C, 0x2A, 0x1C, 0x52, 0x1C, 0x7B, 0x1C, 0xA3, 0x1C, 0xCC, 0x1C, 0xF5, 0x1D, 0x1E, 0x1D, 0x47, 0x1D, 0x70, 0x1D, 0x99, 0x1D, 0xC3, 0x1D, 0xEC, 0x1E, 0x16, 0x1E, 0x40, 0x1E, 0x6A, 0x1E, 0x94, 0x1E, 0xBE, 0x1E, 0xE9, 0x1F, 0x13, 0x1F, 0x3E, 0x1F, 0x69, 0x1F, 0x94, 0x1F, 0xBF, 0x1F, 0xEA, 0x20, 0x15, 0x20, 0x41, 0x20, 0x6C, 0x20, 0x98, 0x20, 0xC4, 0x20, 0xF0, 0x21, 0x1C, 0x21, 0x48, 0x21, 0x75, 0x21, 0xA1, 0x21, 0xCE, 0x21, 0xFB, 0x22, 0x27, 0x22, 0x55, 0x22, 0x82, 0x22, 0xAF, 0x22, 0xDD, 0x23, 0x0A, 0x23, 0x38, 0x23, 0x66, 0x23, 0x94, 0x23, 0xC2, 0x23, 0xF0, 0x24, 0x1F, 0x24, 0x4D, 0x24, 0x7C, 0x24, 0xAB, 0x24, 0xDA, 0x25, 0x09, 0x25, 0x38, 0x25, 0x68, 0x25, 0x97, 0x25, 0xC7, 0x25, 0xF7, 0x26, 0x27, 0x26, 0x57, 0x26, 0x87, 0x26, 0xB7, 0x26, 0xE8, 0x27, 0x18, 0x27, 0x49, 0x27, 0x7A, 0x27, 0xAB, 0x27, 0xDC, 0x28, 0x0D, 0x28, 0x3F, 0x28, 0x71, 0x28, 0xA2, 0x28, 0xD4, 0x29, 0x06, 0x29, 0x38, 0x29, 0x6B, 0x29, 0x9D, 0x29, 0xD0, 0x2A, 0x02, 0x2A, 0x35, 0x2A, 0x68, 0x2A, 0x9B, 0x2A, 0xCF, 0x2B, 0x02, 0x2B, 0x36, 0x2B, 0x69, 0x2B, 0x9D, 0x2B, 0xD1, 0x2C, 0x05, 0x2C, 0x39, 0x2C, 0x6E, 0x2C, 0xA2, 0x2C, 0xD7, 0x2D, 0x0C, 0x2D, 0x41, 0x2D, 0x76, 0x2D, 0xAB, 0x2D, 0xE1, 0x2E, 0x16, 0x2E, 0x4C, 0x2E, 0x82, 0x2E, 0xB7, 0x2E, 0xEE, 0x2F, 0x24, 0x2F, 0x5A, 0x2F, 0x91, 0x2F, 0xC7, 0x2F, 0xFE, 0x30, 0x35, 0x30, 0x6C, 0x30, 0xA4, 0x30, 0xDB, 0x31, 0x12, 0x31, 0x4A, 0x31, 0x82, 0x31, 0xBA, 0x31, 0xF2, 0x32, 0x2A, 0x32, 0x63, 0x32, 0x9B, 0x32, 0xD4, 0x33, 0x0D, 0x33, 0x46, 0x33, 0x7F, 0x33, 0xB8, 0x33, 0xF1, 0x34, 0x2B, 0x34, 0x65, 0x34, 0x9E, 0x34, 0xD8, 0x35, 0x13, 0x35, 0x4D, 0x35, 0x87, 0x35, 0xC2, 0x35, 0xFD, 0x36, 0x37, 0x36, 0x72, 0x36, 0xAE, 0x36, 0xE9, 0x37, 0x24, 0x37, 0x60, 0x37, 0x9C, 0x37, 0xD7, 0x38, 0x14, 0x38, 0x50, 0x38, 0x8C, 0x38, 0xC8, 0x39, 0x05, 0x39, 0x42, 0x39, 0x7F, 0x39, 0xBC, 0x39, 0xF9, 0x3A, 0x36, 0x3A, 0x74, 0x3A, 0xB2, 0x3A, 0xEF, 0x3B, 0x2D, 0x3B, 0x6B, 0x3B, 0xAA, 0x3B, 0xE8, 0x3C, 0x27, 0x3C, 0x65, 0x3C, 0xA4, 0x3C, 0xE3, 0x3D, 0x22, 0x3D, 0x61, 0x3D, 0xA1, 0x3D, 0xE0, 0x3E, 0x20, 0x3E, 0x60, 0x3E, 0xA0, 0x3E, 0xE0, 0x3F, 0x21, 0x3F, 0x61, 0x3F, 0xA2, 0x3F, 0xE2, 0x40, 0x23, 0x40, 0x64, 0x40, 0xA6, 0x40, 0xE7, 0x41, 0x29, 0x41, 0x6A, 0x41, 0xAC, 0x41, 0xEE, 0x42, 0x30, 0x42, 0x72, 0x42, 0xB5, 0x42, 0xF7, 0x43, 0x3A, 0x43, 0x7D, 0x43, 0xC0, 0x44, 0x03, 0x44, 0x47, 0x44, 0x8A, 0x44, 0xCE, 0x45, 0x12, 0x45, 0x55, 0x45, 0x9A, 0x45, 0xDE, 0x46, 0x22, 0x46, 0x67, 0x46, 0xAB, 0x46, 0xF0, 0x47, 0x35, 0x47, 0x7B, 0x47, 0xC0, 0x48, 0x05, 0x48, 0x4B, 0x48, 0x91, 0x48, 0xD7, 0x49, 0x1D, 0x49, 0x63, 0x49, 0xA9, 0x49, 0xF0, 0x4A, 0x37, 0x4A, 0x7D, 0x4A, 0xC4, 0x4B, 0x0C, 0x4B, 0x53, 0x4B, 0x9A, 0x4B, 0xE2, 0x4C, 0x2A, 0x4C, 0x72, 0x4C, 0xBA, 0x4D, 0x02, 0x4D, 0x4A, 0x4D, 0x93, 0x4D, 0xDC, 0x4E, 0x25, 0x4E, 0x6E, 0x4E, 0xB7, 0x4F, 0x00, 0x4F, 0x49, 0x4F, 0x93, 0x4F, 0xDD, 0x50, 0x27, 0x50, 0x71, 0x50, 0xBB, 0x51, 0x06, 0x51, 0x50, 0x51, 0x9B, 0x51, 0xE6, 0x52, 0x31, 0x52, 0x7C, 0x52, 0xC7, 0x53, 0x13, 0x53, 0x5F, 0x53, 0xAA, 0x53, 0xF6, 0x54, 0x42, 0x54, 0x8F, 0x54, 0xDB, 0x55, 0x28, 0x55, 0x75, 0x55, 0xC2, 0x56, 0x0F, 0x56, 0x5C, 0x56, 0xA9, 0x56, 0xF7, 0x57, 0x44, 0x57, 0x92, 0x57, 0xE0, 0x58, 0x2F, 0x58, 0x7D, 0x58, 0xCB, 0x59, 0x1A, 0x59, 0x69, 0x59, 0xB8, 0x5A, 0x07, 0x5A, 0x56, 0x5A, 0xA6, 0x5A, 0xF5, 0x5B, 0x45, 0x5B, 0x95, 0x5B, 0xE5, 0x5C, 0x35, 0x5C, 0x86, 0x5C, 0xD6, 0x5D, 0x27, 0x5D, 0x78, 0x5D, 0xC9, 0x5E, 0x1A, 0x5E, 0x6C, 0x5E, 0xBD, 0x5F, 0x0F, 0x5F, 0x61, 0x5F, 0xB3, 0x60, 0x05, 0x60, 0x57, 0x60, 0xAA, 0x60, 0xFC, 0x61, 0x4F, 0x61, 0xA2, 0x61, 0xF5, 0x62, 0x49, 0x62, 0x9C, 0x62, 0xF0, 0x63, 0x43, 0x63, 0x97, 0x63, 0xEB, 0x64, 0x40, 0x64, 0x94, 0x64, 0xE9, 0x65, 0x3D, 0x65, 0x92, 0x65, 0xE7, 0x66, 0x3D, 0x66, 0x92, 0x66, 0xE8, 0x67, 0x3D, 0x67, 0x93, 0x67, 0xE9, 0x68, 0x3F, 0x68, 0x96, 0x68, 0xEC, 0x69, 0x43, 0x69, 0x9A, 0x69, 0xF1, 0x6A, 0x48, 0x6A, 0x9F, 0x6A, 0xF7, 0x6B, 0x4F, 0x6B, 0xA7, 0x6B, 0xFF, 0x6C, 0x57, 0x6C, 0xAF, 0x6D, 0x08, 0x6D, 0x60, 0x6D, 0xB9, 0x6E, 0x12, 0x6E, 0x6B, 0x6E, 0xC4, 0x6F, 0x1E, 0x6F, 0x78, 0x6F, 0xD1, 0x70, 0x2B, 0x70, 0x86, 0x70, 0xE0, 0x71, 0x3A, 0x71, 0x95, 0x71, 0xF0, 0x72, 0x4B, 0x72, 0xA6, 0x73, 0x01, 0x73, 0x5D, 0x73, 0xB8, 0x74, 0x14, 0x74, 0x70, 0x74, 0xCC, 0x75, 0x28, 0x75, 0x85, 0x75, 0xE1, 0x76, 0x3E, 0x76, 0x9B, 0x76, 0xF8, 0x77, 0x56, 0x77, 0xB3, 0x78, 0x11, 0x78, 0x6E, 0x78, 0xCC, 0x79, 0x2A, 0x79, 0x89, 0x79, 0xE7, 0x7A, 0x46, 0x7A, 0xA5, 0x7B, 0x04, 0x7B, 0x63, 0x7B, 0xC2, 0x7C, 0x21, 0x7C, 0x81, 0x7C, 0xE1, 0x7D, 0x41, 0x7D, 0xA1, 0x7E, 0x01, 0x7E, 0x62, 0x7E, 0xC2, 0x7F, 0x23, 0x7F, 0x84, 0x7F, 0xE5, 0x80, 0x47, 0x80, 0xA8, 0x81, 0x0A, 0x81, 0x6B, 0x81, 0xCD, 0x82, 0x30, 0x82, 0x92, 0x82, 0xF4, 0x83, 0x57, 0x83, 0xBA, 0x84, 0x1D, 0x84, 0x80, 0x84, 0xE3, 0x85, 0x47, 0x85, 0xAB, 0x86, 0x0E, 0x86, 0x72, 0x86, 0xD7, 0x87, 0x3B, 0x87, 0x9F, 0x88, 0x04, 0x88, 0x69, 0x88, 0xCE, 0x89, 0x33, 0x89, 0x99, 0x89, 0xFE, 0x8A, 0x64, 0x8A, 0xCA, 0x8B, 0x30, 0x8B, 0x96, 0x8B, 0xFC, 0x8C, 0x63, 0x8C, 0xCA, 0x8D, 0x31, 0x8D, 0x98, 0x8D, 0xFF, 0x8E, 0x66, 0x8E, 0xCE, 0x8F, 0x36, 0x8F, 0x9E, 0x90, 0x06, 0x90, 0x6E, 0x90, 0xD6, 0x91, 0x3F, 0x91, 0xA8, 0x92, 0x11, 0x92, 0x7A, 0x92, 0xE3, 0x93, 0x4D, 0x93, 0xB6, 0x94, 0x20, 0x94, 0x8A, 0x94, 0xF4, 0x95, 0x5F, 0x95, 0xC9, 0x96, 0x34, 0x96, 0x9F, 0x97, 0x0A, 0x97, 0x75, 0x97, 0xE0, 0x98, 0x4C, 0x98, 0xB8, 0x99, 0x24, 0x99, 0x90, 0x99, 0xFC, 0x9A, 0x68, 0x9A, 0xD5, 0x9B, 0x42, 0x9B, 0xAF, 0x9C, 0x1C, 0x9C, 0x89, 0x9C, 0xF7, 0x9D, 0x64, 0x9D, 0xD2, 0x9E, 0x40, 0x9E, 0xAE, 0x9F, 0x1D, 0x9F, 0x8B, 0x9F, 0xFA, 0xA0, 0x69, 0xA0, 0xD8, 0xA1, 0x47, 0xA1, 0xB6, 0xA2, 0x26, 0xA2, 0x96, 0xA3, 0x06, 0xA3, 0x76, 0xA3, 0xE6, 0xA4, 0x56, 0xA4, 0xC7, 0xA5, 0x38, 0xA5, 0xA9, 0xA6, 0x1A, 0xA6, 0x8B, 0xA6, 0xFD, 0xA7, 0x6E, 0xA7, 0xE0, 0xA8, 0x52, 0xA8, 0xC4, 0xA9, 0x37, 0xA9, 0xA9, 0xAA, 0x1C, 0xAA, 0x8F, 0xAB, 0x02, 0xAB, 0x75, 0xAB, 0xE9, 0xAC, 0x5C, 0xAC, 0xD0, 0xAD, 0x44, 0xAD, 0xB8, 0xAE, 0x2D, 0xAE, 0xA1, 0xAF, 0x16, 0xAF, 0x8B, 0xB0, 0x00, 0xB0, 0x75, 0xB0, 0xEA, 0xB1, 0x60, 0xB1, 0xD6, 0xB2, 0x4B, 0xB2, 0xC2, 0xB3, 0x38, 0xB3, 0xAE, 0xB4, 0x25, 0xB4, 0x9C, 0xB5, 0x13, 0xB5, 0x8A, 0xB6, 0x01, 0xB6, 0x79, 0xB6, 0xF0, 0xB7, 0x68, 0xB7, 0xE0, 0xB8, 0x59, 0xB8, 0xD1, 0xB9, 0x4A, 0xB9, 0xC2, 0xBA, 0x3B, 0xBA, 0xB5, 0xBB, 0x2E, 0xBB, 0xA7, 0xBC, 0x21, 0xBC, 0x9B, 0xBD, 0x15, 0xBD, 0x8F, 0xBE, 0x0A, 0xBE, 0x84, 0xBE, 0xFF, 0xBF, 0x7A, 0xBF, 0xF5, 0xC0, 0x70, 0xC0, 0xEC, 0xC1, 0x67, 0xC1, 0xE3, 0xC2, 0x5F, 0xC2, 0xDB, 0xC3, 0x58, 0xC3, 0xD4, 0xC4, 0x51, 0xC4, 0xCE, 0xC5, 0x4B, 0xC5, 0xC8, 0xC6, 0x46, 0xC6, 0xC3, 0xC7, 0x41, 0xC7, 0xBF, 0xC8, 0x3D, 0xC8, 0xBC, 0xC9, 0x3A, 0xC9, 0xB9, 0xCA, 0x38, 0xCA, 0xB7, 0xCB, 0x36, 0xCB, 0xB6, 0xCC, 0x35, 0xCC, 0xB5, 0xCD, 0x35, 0xCD, 0xB5, 0xCE, 0x36, 0xCE, 0xB6, 0xCF, 0x37, 0xCF, 0xB8, 0xD0, 0x39, 0xD0, 0xBA, 0xD1, 0x3C, 0xD1, 0xBE, 0xD2, 0x3F, 0xD2, 0xC1, 0xD3, 0x44, 0xD3, 0xC6, 0xD4, 0x49, 0xD4, 0xCB, 0xD5, 0x4E, 0xD5, 0xD1, 0xD6, 0x55, 0xD6, 0xD8, 0xD7, 0x5C, 0xD7, 0xE0, 0xD8, 0x64, 0xD8, 0xE8, 0xD9, 0x6C, 0xD9, 0xF1, 0xDA, 0x76, 0xDA, 0xFB, 0xDB, 0x80, 0xDC, 0x05, 0xDC, 0x8A, 0xDD, 0x10, 0xDD, 0x96, 0xDE, 0x1C, 0xDE, 0xA2, 0xDF, 0x29, 0xDF, 0xAF, 0xE0, 0x36, 0xE0, 0xBD, 0xE1, 0x44, 0xE1, 0xCC, 0xE2, 0x53, 0xE2, 0xDB, 0xE3, 0x63, 0xE3, 0xEB, 0xE4, 0x73, 0xE4, 0xFC, 0xE5, 0x84, 0xE6, 0x0D, 0xE6, 0x96, 0xE7, 0x1F, 0xE7, 0xA9, 0xE8, 0x32, 0xE8, 0xBC, 0xE9, 0x46, 0xE9, 0xD0, 0xEA, 0x5B, 0xEA, 0xE5, 0xEB, 0x70, 0xEB, 0xFB, 0xEC, 0x86, 0xED, 0x11, 0xED, 0x9C, 0xEE, 0x28, 0xEE, 0xB4, 0xEF, 0x40, 0xEF, 0xCC, 0xF0, 0x58, 0xF0, 0xE5, 0xF1, 0x72, 0xF1, 0xFF, 0xF2, 0x8C, 0xF3, 0x19, 0xF3, 0xA7, 0xF4, 0x34, 0xF4, 0xC2, 0xF5, 0x50, 0xF5, 0xDE, 0xF6, 0x6D, 0xF6, 0xFB, 0xF7, 0x8A, 0xF8, 0x19, 0xF8, 0xA8, 0xF9, 0x38, 0xF9, 0xC7, 0xFA, 0x57, 0xFA, 0xE7, 0xFB, 0x77, 0xFC, 0x07, 0xFC, 0x98, 0xFD, 0x29, 0xFD, 0xBA, 0xFE, 0x4B, 0xFE, 0xDC, 0xFF, 0x6D, 0xFF, 0xFF -``` - diff --git a/media/libjxl/src/doc/tables/is_zero_base.md b/media/libjxl/src/doc/tables/is_zero_base.md deleted file mode 100644 index 7e2d081f3b..0000000000 --- a/media/libjxl/src/doc/tables/is_zero_base.md +++ /dev/null @@ -1,9 +0,0 @@ -#### Table M.1 – is_zero_base table - -``` -228, 216, 216, 195, 192, 189, 182, 184, 179, 176, 171, 168, 166, 159, -156, 151, 151, 150, 150, 146, 144, 138, 138, 137, 135, 131, 127, 126, -124, 123, 124, 123, 122, 121, 118, 117, 114, 115, 116, 116, 115, 115, -114, 111, 111, 111, 112, 111, 110, 110, 110, 111, 111, 114, 110, 111, -112, 113, 116, 120, 126, 131, 147, 160 -``` diff --git a/media/libjxl/src/doc/tables/markdown-pdf.css b/media/libjxl/src/doc/tables/markdown-pdf.css deleted file mode 100644 index c1efc1cc2b..0000000000 --- a/media/libjxl/src/doc/tables/markdown-pdf.css +++ /dev/null @@ -1,36 +0,0 @@ -/* - settings.json: - "markdown-pdf.styles": ["markdown-pdf.css",], - "markdown-pdf.format": "Letter", - "markdown-pdf.margin.top": "1in", - "markdown-pdf.margin.bottom": "1in", - "markdown-pdf.margin.left": "1in", - "markdown-pdf.margin.right": "1in", - "markdown-pdf.stylesRelativePathFile" : true, - "markdown-pdf.displayHeaderFooter": false, - */ - -body { - font-family: "Times"; - font-size: 10pt; - padding: 0; -} - -h4 { - font-family: "Times New Roman"; - font-size: 10pt; - font-weight: bold; -} - -code { - font-family: Consolas, "Source Code Pro"; - font-size: 10pt; -} - -pre.hljs code > div { - padding: 0px; -} - -:not(pre):not(.hljs) > code { - color: #4d4d4c; -} diff --git a/media/libjxl/src/doc/tables/nonzero_buckets.md b/media/libjxl/src/doc/tables/nonzero_buckets.md deleted file mode 100644 index 77a5a396e2..0000000000 --- a/media/libjxl/src/doc/tables/nonzero_buckets.md +++ /dev/null @@ -1,9 +0,0 @@ -#### Table M.17 – nonzero_buckets - -``` - 0, 1, 2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, - 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, - 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 -``` - diff --git a/media/libjxl/src/doc/tables/num_nonzero_context.md b/media/libjxl/src/doc/tables/num_nonzero_context.md deleted file mode 100644 index b73d48c38d..0000000000 --- a/media/libjxl/src/doc/tables/num_nonzero_context.md +++ /dev/null @@ -1,60 +0,0 @@ -#### Table M.16 – num_nonzero_context - -`scheme == 0`: -``` -0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, -6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, -7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 -``` - -`scheme == 1`: -``` - 0, 2, 2, 4, 4, 4, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10, -10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, -12, 12, 12, 12, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, -14, 14, 14, 14, 14, 14, 14, 14, 14, 14 -``` - -`scheme == 2`: -``` - 0, 4, 4, 8, 8, 8, 12, 12, 12, 12, 16, 16, 16, 16, 16, 16, 20, 20, -20, 20, 20, 20, 20, 20, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, -24, 24, 24, 24, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, -28, 28, 28, 28, 28, 28, 28, 28, 28, 28 -``` - -`scheme == 3`: -``` - 0, 8, 8, 16, 16, 16, 24, 24, 24, 24, 32, 32, 32, 32, 32, 32, 40, 40, -40, 40, 40, 40, 40, 40, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, -48, 48, 48, 48, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, -55, 55, 55, 55, 55, 55, 55, 55, 55, 55 -``` - -`scheme == 4`: -``` - 0, 16, 16, 32, 32, 32, 48, 48, 48, 48, 64, 64, 64, 64, - 64, 64, 80, 80, 80, 80, 80, 80, 80, 80, 95, 95, 95, 95, - 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 109, 109, -109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, -109, 109, 109, 109, 109, 109, 109, 109 -``` - -`scheme == 5`: -``` - 0, 32, 32, 64, 64, 64, 96, 96, 96, 96, 127, 127, 127, 127, -127, 127, 157, 157, 157, 157, 157, 157, 157, 157, 185, 185, 185, 185, -185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 185, 211, 211, -211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, 211, -211, 211, 211, 211, 211, 211, 211, 211 -``` - -`scheme == 6`: -``` - 0, 64, 64, 127, 127, 127, 188, 188, 188, 188, 246, 246, 246, 246, -246, 246, 300, 300, 300, 300, 300, 300, 300, 300, 348, 348, 348, 348, -348, 348, 348, 348, 348, 348, 348, 348, 348, 348, 348, 348, 388, 388, -388, 388, 388, 388, 388, 388, 388, 388, 388, 388, 388, 388, 388, 388, -388, 388, 388, 388, 388, 388, 388, 388 -``` - diff --git a/media/libjxl/src/doc/tables/num_nonzeros_base.md b/media/libjxl/src/doc/tables/num_nonzeros_base.md deleted file mode 100644 index 165c7389cc..0000000000 --- a/media/libjxl/src/doc/tables/num_nonzeros_base.md +++ /dev/null @@ -1,258 +0,0 @@ -#### Table M.2 – num_nonzeros_base table - -``` -251, 252, 117, 249, 161, 136, 83, 238, 184, 126, 137, 129, 140, 119, - 70, 213, 160, 175, 174, 130, 166, 134, 122, 125, 131, 144, 136, 133, -139, 123, 79, 216, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` -254, 252, 174, 232, 189, 155, 122, 177, 204, 173, 146, 149, 141, 133, -103, 109, 167, 187, 168, 142, 154, 147, 125, 139, 144, 138, 138, 153, -141, 133, 90, 121, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` -251, 240, 197, 176, 184, 177, 114, 89, 194, 165, 153, 161, 158, 136, - 92, 95, 123, 171, 160, 140, 148, 136, 129, 139, 145, 136, 143, 134, -138, 124, 92, 154, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` -247, 220, 201, 110, 194, 176, 147, 59, 175, 171, 156, 157, 152, 146, -115, 114, 88, 151, 164, 141, 153, 135, 141, 131, 146, 139, 140, 145, -138, 137, 112, 184, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` -238, 179, 203, 63, 194, 173, 149, 71, 139, 169, 154, 159, 150, 146, -117, 143, 78, 122, 152, 137, 149, 138, 138, 133, 134, 142, 142, 142, -148, 128, 118, 199, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` -227, 127, 200, 44, 192, 170, 148, 100, 102, 161, 156, 153, 148, 149, -124, 160, 88, 101, 134, 132, 149, 145, 134, 134, 136, 141, 138, 142, -144, 137, 116, 208, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` -214, 86, 195, 44, 187, 163, 148, 126, 81, 147, 156, 152, 150, 144, -121, 172, 96, 95, 117, 122, 145, 152, 136, 133, 135, 135, 131, 142, -141, 135, 114, 217, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` -198, 56, 191, 54, 171, 162, 147, 144, 74, 128, 152, 149, 150, 142, -119, 177, 101, 100, 106, 111, 135, 154, 136, 137, 136, 132, 133, 142, -144, 130, 117, 222, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` -176, 40, 189, 73, 147, 159, 148, 152, 79, 106, 147, 149, 151, 139, -123, 188, 108, 110, 106, 97, 125, 151, 137, 138, 135, 135, 134, 136, -140, 131, 116, 221, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` -148, 33, 185, 88, 117, 158, 145, 163, 95, 91, 137, 146, 150, 140, -120, 197, 115, 116, 114, 92, 114, 144, 130, 133, 132, 133, 129, 140, -138, 130, 111, 224, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` -117, 31, 180, 104, 93, 150, 143, 166, 99, 85, 124, 139, 148, 142, -118, 201, 105, 120, 120, 90, 107, 135, 127, 130, 131, 131, 132, 140, -142, 133, 114, 229, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 87, 35, 170, 110, 78, 141, 144, 176, 106, 90, 112, 132, 143, 138, -119, 204, 111, 121, 125, 90, 105, 131, 124, 122, 129, 128, 129, 137, -138, 133, 114, 227, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 63, 42, 159, 123, 73, 127, 142, 191, 105, 91, 105, 123, 139, 137, -120, 209, 117, 110, 122, 98, 110, 125, 115, 123, 122, 126, 128, 134, -141, 129, 113, 229, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 45, 53, 146, 135, 71, 114, 138, 193, 100, 98, 98, 113, 133, 135, -118, 222, 113, 111, 139, 103, 107, 126, 111, 119, 121, 122, 127, 135, -141, 128, 114, 242, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 33, 60, 132, 138, 75, 100, 134, 203, 112, 99, 98, 105, 126, 131, -115, 229, 107, 93, 121, 106, 108, 122, 106, 109, 114, 116, 127, 133, -143, 128, 110, 242, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 24, 70, 118, 134, 76, 87, 130, 201, 110, 96, 99, 97, 119, 130, -111, 229, 97, 104, 125, 102, 112, 125, 101, 109, 113, 114, 125, 129, -142, 127, 112, 241, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 17, 65, 100, 121, 80, 75, 124, 174, 117, 100, 94, 93, 114, 128, -110, 216, 103, 94, 113, 122, 118, 126, 113, 108, 105, 108, 122, 128, -141, 125, 113, 238, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 12, 70, 82, 132, 78, 65, 118, 155, 136, 103, 97, 89, 106, 124, -111, 215, 115, 123, 129, 99, 104, 127, 110, 108, 101, 109, 118, 126, -136, 123, 110, 233, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 8, 66, 61, 117, 91, 59, 108, 195, 101, 112, 99, 99, 99, 116, -106, 230, 127, 99, 144, 101, 118, 137, 117, 111, 106, 104, 116, 121, -134, 122, 110, 223, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 6, 78, 42, 146, 101, 54, 94, 201, 116, 102, 110, 94, 92, 108, -103, 214, 108, 111, 127, 102, 121, 132, 120, 121, 95, 98, 110, 121, -129, 117, 107, 235, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 5, 93, 29, 145, 102, 52, 77, 216, 108, 115, 108, 102, 89, 97, - 94, 229, 89, 103, 139, 120, 103, 151, 102, 100, 97, 96, 99, 111, -125, 116, 104, 242, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 4, 105, 21, 145, 100, 54, 64, 217, 100, 122, 128, 87, 88, 91, - 87, 230, 112, 80, 148, 95, 146, 123, 96, 140, 90, 91, 98, 106, -122, 111, 100, 249, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 4, 130, 14, 142, 104, 56, 51, 208, 116, 135, 100, 89, 82, 84, - 75, 239, 85, 85, 122, 125, 94, 144, 151, 136, 92, 97, 104, 109, -113, 110, 91, 246, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 3, 126, 9, 172, 105, 57, 39, 219, 95, 120, 118, 96, 93, 75, - 66, 241, 102, 134, 96, 156, 146, 162, 130, 112, 82, 89, 97, 101, -116, 103, 82, 254, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 3, 149, 7, 182, 122, 54, 29, 224, 103, 100, 113, 96, 90, 74, - 55, 250, 127, 94, 118, 93, 135, 160, 113, 130, 95, 117, 106, 96, -111, 97, 77, 242, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 3, 150, 4, 170, 138, 59, 20, 229, 91, 150, 107, 98, 92, 68, - 48, 245, 113, 64, 114, 111, 134, 127, 102, 104, 85, 118, 103, 107, -102, 91, 72, 245, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 3, 171, 3, 165, 137, 62, 14, 211, 96, 127, 132, 121, 95, 62, - 37, 248, 102, 57, 144, 85, 127, 191, 102, 97, 127, 104, 91, 102, -107, 81, 64, 254, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 2, 166, 2, 196, 122, 65, 10, 243, 102, 93, 117, 92, 96, 63, - 29, 251, 169, 159, 149, 96, 91, 139, 157, 40, 100, 89, 120, 92, -109, 79, 58, 247, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 2, 176, 2, 189, 118, 48, 7, 219, 68, 43, 109, 96, 129, 75, - 19, 254, 2, 3, 185, 6, 102, 127, 127, 127, 1, 131, 83, 99, -107, 80, 45, 254, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 1, 205, 2, 208, 64, 89, 4, 223, 29, 169, 29, 123, 118, 76, - 11, 240, 202, 243, 65, 6, 12, 243, 96, 55, 102, 102, 114, 102, -107, 74, 31, 247, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 1, 216, 1, 214, 127, 94, 2, 234, 145, 3, 127, 106, 155, 80, - 4, 247, 4, 65, 86, 127, 127, 127, 127, 102, 127, 143, 143, 108, -113, 80, 16, 216, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - -``` - 2, 199, 1, 222, 93, 94, 1, 232, 2, 65, 74, 139, 201, 48, - 2, 254, 169, 127, 52, 243, 251, 249, 102, 86, 202, 153, 65, 65, -146, 69, 8, 238, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, -128, 128, 128, 128, 128, 128, 128 -``` - diff --git a/media/libjxl/src/doc/tables/quant.md b/media/libjxl/src/doc/tables/quant.md deleted file mode 100644 index 1fb80d754c..0000000000 --- a/media/libjxl/src/doc/tables/quant.md +++ /dev/null @@ -1,19 +0,0 @@ -#### Table M.13 – template quant tables - -`is_luma == true`: -``` - 16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, - 55, 14, 13, 16, 24, 40, 57, 69, 56, 14, 17, 22, 29, 51, 87, - 80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, -104, 113, 92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, -112, 100, 103, 99 -``` - -`is_luma == false`: -``` -17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 24, 26, -56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, -99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, -99, 99, 99, 99, 99, 99, 99, 99, 99, 99 -``` - diff --git a/media/libjxl/src/doc/tables/stock_counts.md b/media/libjxl/src/doc/tables/stock_counts.md deleted file mode 100644 index 6e8e4458c3..0000000000 --- a/media/libjxl/src/doc/tables/stock_counts.md +++ /dev/null @@ -1,22 +0,0 @@ -#### Table M.9 – stock counts arrays - -`is_ac == 0`, `stock_index == 0`: -``` -0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0 -``` - -`is_ac == 0`, `stock_index == 1`: -``` -0, 0, 1, 5, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0 -``` - -`is_ac == 1`, `stock_index == 0`: -``` -0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 126 -``` - -`is_ac == 1`, `stock_index == 1`: -``` -0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 120 -``` - diff --git a/media/libjxl/src/doc/tables/stock_quant.md b/media/libjxl/src/doc/tables/stock_quant.md deleted file mode 100644 index b32fd3c7d1..0000000000 --- a/media/libjxl/src/doc/tables/stock_quant.md +++ /dev/null @@ -1,130 +0,0 @@ -#### Table M.12 – stock quant tables - -`is_luma == true`, `stock_index == 0`: -``` - 3, 2, 2, 3, 5, 8, 10, 12, 2, 2, 3, 4, 5, 12, 12, 11, 3, 3, - 3, 5, 8, 11, 14, 11, 3, 3, 4, 6, 10, 17, 16, 12, 4, 4, 7, 11, -14, 22, 21, 15, 5, 7, 11, 13, 16, 21, 23, 18, 10, 13, 16, 17, 21, 24, -24, 20, 14, 18, 19, 20, 22, 20, 21, 20 -``` - -`is_luma == true`, `stock_index == 1`: -``` - 8, 6, 5, 8, 12, 20, 26, 31, 6, 6, 7, 10, 13, 29, 30, 28, 7, 7, - 8, 12, 20, 29, 35, 28, 7, 9, 11, 15, 26, 44, 40, 31, 9, 11, 19, 28, -34, 55, 52, 39, 12, 18, 28, 32, 41, 52, 57, 46, 25, 32, 39, 44, 52, 61, -60, 51, 36, 46, 48, 49, 56, 50, 52, 50 -``` - -`is_luma == true`, `stock_index == 2`: -``` - 6, 4, 4, 6, 10, 16, 20, 24, 5, 5, 6, 8, 10, 23, 24, 22, 6, 5, - 6, 10, 16, 23, 28, 22, 6, 7, 9, 12, 20, 35, 32, 25, 7, 9, 15, 22, -27, 44, 41, 31, 10, 14, 22, 26, 32, 42, 45, 37, 20, 26, 31, 35, 41, 48, -48, 40, 29, 37, 38, 39, 45, 40, 41, 40 -``` - -`is_luma == true`, `stock_index == 3`: -``` - 5, 3, 3, 5, 7, 12, 15, 18, 4, 4, 4, 6, 8, 17, 18, 17, 4, 4, - 5, 7, 12, 17, 21, 17, 4, 5, 7, 9, 15, 26, 24, 19, 5, 7, 11, 17, -20, 33, 31, 23, 7, 11, 17, 19, 24, 31, 34, 28, 15, 19, 23, 26, 31, 36, -36, 30, 22, 28, 29, 29, 34, 30, 31, 30 -``` - -`is_luma == true`, `stock_index == 4`: -``` - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 -``` - -`is_luma == true`, `stock_index == 5`: -``` - 2, 1, 1, 2, 2, 4, 5, 6, 1, 1, 1, 2, 3, 6, 6, 6, 1, 1, - 2, 2, 4, 6, 7, 6, 1, 2, 2, 3, 5, 9, 8, 6, 2, 2, 4, 6, - 7, 11, 10, 8, 2, 4, 6, 6, 8, 10, 11, 9, 5, 6, 8, 9, 10, 12, -12, 10, 7, 9, 10, 10, 11, 10, 10, 10 -``` - -`is_luma == true`, `stock_index == 6`: -``` - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, - 1, 2, 2, 3, 1, 1, 1, 1, 2, 2, 3, 3, 1, 1, 1, 2, 2, 3, - 3, 3, 1, 1, 2, 2, 3, 3, 3, 3 -``` - -`is_luma == true`, `stock_index == 7`: -``` -10, 7, 6, 10, 14, 24, 31, 37, 7, 7, 8, 11, 16, 35, 36, 33, 8, 8, -10, 14, 24, 34, 41, 34, 8, 10, 13, 17, 31, 52, 48, 37, 11, 13, 22, 34, -41, 65, 62, 46, 14, 21, 33, 38, 49, 62, 68, 55, 29, 38, 47, 52, 62, 73, -72, 61, 43, 55, 57, 59, 67, 60, 62, 59 -``` - -`is_luma == false`, `stock_index == 0`: -``` - 9, 9, 9, 12, 11, 12, 24, 13, 13, 24, 50, 33, 28, 33, 50, 50, 50, 50, -50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, -50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, -50, 50, 50, 50, 50, 50, 50, 50, 50, 50 -``` - -`is_luma == false`, `stock_index == 1`: -``` - 3, 4, 5, 9, 20, 20, 20, 20, 4, 4, 5, 13, 20, 20, 20, 20, 5, 5, -11, 20, 20, 20, 20, 20, 9, 13, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, -20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, -20, 20, 20, 20, 20, 20, 20, 20, 20, 20 -``` - -`is_luma == false`, `stock_index == 2`: -``` - 9, 9, 12, 24, 50, 50, 50, 50, 9, 11, 13, 33, 50, 50, 50, 50, 12, 13, -28, 50, 50, 50, 50, 50, 24, 33, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, -50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, -50, 50, 50, 50, 50, 50, 50, 50, 50, 50 -``` - -`is_luma == false`, `stock_index == 3`: -``` - 5, 5, 7, 14, 30, 30, 30, 30, 5, 6, 8, 20, 30, 30, 30, 30, 7, 8, -17, 30, 30, 30, 30, 30, 14, 20, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, -30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, -30, 30, 30, 30, 30, 30, 30, 30, 30, 30 -``` - -`is_luma == false`, `stock_index == 4`: -``` - 7, 7, 10, 19, 40, 40, 40, 40, 7, 8, 10, 26, 40, 40, 40, 40, 10, 10, -22, 40, 40, 40, 40, 40, 19, 26, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, -40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, -40, 40, 40, 40, 40, 40, 40, 40, 40, 40 -``` - -`is_luma == false`, `stock_index == 5`: -``` - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 -``` - -`is_luma == false`, `stock_index == 6`: -``` - 2, 2, 2, 5, 10, 10, 10, 10, 2, 2, 3, 7, 10, 10, 10, 10, 2, 3, - 6, 10, 10, 10, 10, 10, 5, 7, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, -10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, -10, 10, 10, 10, 10, 10, 10, 10, 10, 10 -``` - -`is_luma == false`, `stock_index == 7`: -``` -10, 11, 14, 28, 59, 59, 59, 59, 11, 13, 16, 40, 59, 59, 59, 59, 14, 16, -34, 59, 59, 59, 59, 59, 28, 40, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, -59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, -59, 59, 59, 59, 59, 59, 59, 59, 59, 59 -``` - diff --git a/media/libjxl/src/doc/tables/stock_values.md b/media/libjxl/src/doc/tables/stock_values.md deleted file mode 100644 index 8e67cff1e8..0000000000 --- a/media/libjxl/src/doc/tables/stock_values.md +++ /dev/null @@ -1,44 +0,0 @@ -#### Table M.10 – stock values arrays - -`is_ac == 0`, `stock_index == 0`: -``` -0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 256 -``` - -`is_ac == 0`, `stock_index == 1`: -``` -0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 256 -``` - -`is_ac == 1`, `stock_index == 0`: -``` - 1, 2, 3, 0, 4, 17, 5, 18, 33, 49, 65, 6, 19, 81, - 97, 7, 34, 113, 20, 50, 129, 145, 161, 8, 35, 66, 177, 193, - 21, 82, 209, 240, 36, 51, 98, 114, 130, 9, 10, 22, 23, 24, - 25, 26, 37, 38, 39, 40, 41, 42, 52, 53, 54, 55, 56, 57, - 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, - 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, -118, 119, 120, 121, 122, 131, 132, 133, 134, 135, 136, 137, 138, 146, -147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, 166, 167, -168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, 194, 195, -196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, 215, 216, -217, 218, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 241, 242, -243, 244, 245, 246, 247, 248, 249, 250, 256 -``` - -`is_ac == 1`, `stock_index == 1`: -``` - 0, 1, 2, 3, 17, 4, 5, 33, 49, 6, 18, 65, 81, 7, - 97, 113, 19, 34, 50, 129, 8, 20, 66, 145, 161, 177, 193, 9, - 35, 51, 82, 240, 21, 98, 114, 209, 10, 22, 36, 52, 225, 37, -241, 23, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55, 56, - 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, - 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, -117, 118, 119, 120, 121, 122, 130, 131, 132, 133, 134, 135, 136, 137, -138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, -166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, -194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, -215, 216, 217, 218, 226, 227, 228, 229, 230, 231, 232, 233, 234, 242, -243, 244, 245, 246, 247, 248, 249, 250, 256 -``` - diff --git a/media/libjxl/src/doc/tables/symbol_order.md b/media/libjxl/src/doc/tables/symbol_order.md deleted file mode 100644 index a196c0f529..0000000000 --- a/media/libjxl/src/doc/tables/symbol_order.md +++ /dev/null @@ -1,30 +0,0 @@ -#### Table M.11 – predefined symbol order - -`is_ac == 0`: -``` -0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 -``` - -`is_ac == 1`: -``` - 1, 0, 2, 3, 17, 4, 5, 33, 18, 49, 65, 6, 81, 19, - 97, 7, 34, 113, 50, 129, 20, 145, 161, 8, 35, 66, 177, 193, - 21, 82, 209, 240, 36, 51, 98, 114, 9, 130, 10, 22, 52, 225, - 23, 37, 241, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55, - 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, - 86, 87, 88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, -116, 117, 118, 119, 120, 121, 122, 131, 132, 133, 134, 135, 136, 137, -138, 146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163, 164, 165, -166, 167, 168, 169, 170, 178, 179, 180, 181, 182, 183, 184, 185, 186, -194, 195, 196, 197, 198, 199, 200, 201, 202, 210, 211, 212, 213, 214, -215, 216, 217, 218, 226, 227, 228, 229, 230, 231, 232, 233, 234, 242, -243, 244, 245, 246, 247, 248, 249, 250, 16, 32, 48, 64, 80, 96, -112, 128, 144, 160, 176, 192, 208, 11, 12, 13, 14, 15, 27, 28, - 29, 30, 31, 43, 44, 45, 46, 47, 59, 60, 61, 62, 63, 75, - 76, 77, 78, 79, 91, 92, 93, 94, 95, 107, 108, 109, 110, 111, -123, 124, 125, 126, 127, 139, 140, 141, 142, 143, 155, 156, 157, 158, -159, 171, 172, 173, 174, 175, 187, 188, 189, 190, 191, 203, 204, 205, -206, 207, 219, 220, 221, 222, 223, 224, 235, 236, 237, 238, 239, 251, -252, 253, 254, 255 -``` - diff --git a/media/libjxl/src/doc/vuln_playbook.md b/media/libjxl/src/doc/vuln_playbook.md index 1326d70a9e..b22c1c4d08 100644 --- a/media/libjxl/src/doc/vuln_playbook.md +++ b/media/libjxl/src/doc/vuln_playbook.md @@ -195,7 +195,7 @@ engineers on the required backports. A security bug is a bug that can potentially be exploited to let an attacker gain unauthorized access or privileges. For example, gaining code execution in libjxl decoder by decoding a malicious .jxl file is a security but hitting a -`JXL_ASSERT()` is not necessarily one. +`JXL_DASSERT` is not necessarily one. The supported use cases to consider in the context of security bugs that require a vulnerability disclosure are "release" builds. The disclosure is intended for @@ -215,22 +215,9 @@ released software will not have the developer code. This developer code is in the same libjxl repository for convenience. When considering the impact of a bug, "release" mode should be assumed. In -release mode `JXL_ASSERT()` and `JXL_CHECK()` are enabled, but `JXL_DASSERT()` -are not. This means that if a `JXL_DASSERT()` protects an out-of-bounds (OOB) -write, then the impact of a bug hitting the `JXL_DASSERT()` is at least an -OOB write. On the other hand, if a bug ends up hitting a `JXL_CHECK()` instead -of continuing, the only impact is the process abort instead of whatever else is -possible after the `JXL_CHECK()`. - -Asserts in `libjxl` *tools* cause the tool process to abort, but don't affect -the caller. Either crashing or returning an error (non-zero exit code) would -have the same effect, so `JXL_ASSERT()` failures in the tools have no security -or functional impact. - -Asserts in `libjxl` libraries, meant to be linked into other processes, cause -the caller process to abort, potentially causing a Denial of Service, however, -Denial of Service issues are *not* considered security bugs by this policy. -These are still issues and should be fixed, but they are not security issues. +non-release mode `JXL_DASSERT` is enabled. This means that if a +`JXL_DASSERT` protects an out-of-bounds (OOB) write, then the impact of a bug +hitting the `JXL_DASSERT` is at least an OOB write. Out-of-bounds (OOB) reads in process memory are considered security vulnerabilities. OOB reads may allow an attacker to read other buffers from the diff --git a/media/libjxl/src/doc/xl_overview.md b/media/libjxl/src/doc/xl_overview.md index cfcfcb5f81..80e1cb15d4 100644 --- a/media/libjxl/src/doc/xl_overview.md +++ b/media/libjxl/src/doc/xl_overview.md @@ -124,7 +124,7 @@ To diagnose the cause of encoder/decoder failures (which often only result in a generic "decode failed" message), build using the following command: ```bash -CMAKE_FLAGS="-DJXL_CRASH_ON_ERROR" ./ci.sh opt +CMAKE_FLAGS="-DJXL_CRASH_ON_ERROR" ./ci.sh debug ``` In such builds, the first JXL_FAILURE will print a message identifying where the @@ -150,7 +150,7 @@ multiple image lines. **DCT size selection**: `ac_strategy.cc` -**[Gaborish]**: `gaborish.h`. +**[Gaborish]**: `enc_gaborish.h`. **[Edge preserving filter]**: `epf.h` diff --git a/media/libjxl/src/docker/Dockerfile.jpegxl-builder b/media/libjxl/src/docker/Dockerfile.jpegxl-builder deleted file mode 100644 index 16e0077eea..0000000000 --- a/media/libjxl/src/docker/Dockerfile.jpegxl-builder +++ /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. - -# Build an Ubuntu-based docker image with the installed software needed to -# develop and test JPEG XL. - -FROM ubuntu:bionic - -# Set a prompt for when using it locally. -ENV PS1="\[\033[01;33m\]\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ " - -COPY scripts/99_norecommends /etc/apt/apt.conf.d/99_norecommends - -COPY scripts /jpegxl_scripts - -ARG DEBIAN_FRONTEND=noninteractive - -RUN /jpegxl_scripts/jpegxl_builder.sh && \ - rm -rf /jpegxl_scripts diff --git a/media/libjxl/src/docker/Dockerfile.jpegxl-builder-run-aarch64 b/media/libjxl/src/docker/Dockerfile.jpegxl-builder-run-aarch64 deleted file mode 100644 index a9f38a4015..0000000000 --- a/media/libjxl/src/docker/Dockerfile.jpegxl-builder-run-aarch64 +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) the JPEG XL Project Authors. All rights reserved. -# -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -# Build an Ubuntu-based docker image for aarch64 with the installed software -# needed to run JPEG XL. This is only useful when running on actual aarch64 -# hardware. - -FROM arm64v8/ubuntu:bionic - -COPY scripts/99_norecommends /etc/apt/apt.conf.d/99_norecommends - -# Set a prompt for when using it locally. -ENV PS1="\[\033[01;33m\]\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ " - -ARG DEBIAN_FRONTEND=noninteractive - -RUN set -ex; \ - apt-get update -y; \ - apt-get install -y \ - bsdmainutils \ - cmake \ - curl \ - ca-certificates \ - extra-cmake-modules \ - git \ - imagemagick \ - libjpeg8 \ - libgif7 \ - libgoogle-perftools4 \ - libopenexr22 \ - libpng16-16 \ - libqt5x11extras5 \ - libsdl2-2.0-0 \ - parallel; \ - rm -rf /var/lib/apt/lists/*; diff --git a/media/libjxl/src/docker/README.md b/media/libjxl/src/docker/README.md deleted file mode 100644 index 874df1cb80..0000000000 --- a/media/libjxl/src/docker/README.md +++ /dev/null @@ -1,7 +0,0 @@ -### Docker container infrastructure for JPEG XL - -This directory contains the requirements to build a docker image for the -JPEG XL project builder. - -Docker images need to be created and upload manually. See ./build.sh for -details. diff --git a/media/libjxl/src/docker/build.sh b/media/libjxl/src/docker/build.sh deleted file mode 100644 index 3d4727f6a4..0000000000 --- a/media/libjxl/src/docker/build.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) the JPEG XL Project Authors. All rights reserved. -# -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -set -eu - -MYDIR=$(dirname $(realpath "$0")) - -declare -a TARGETS - -load_targets() { - # Built-in OSX "find" does not support "-m". - FIND=$(which "gfind" || which "find") - for f in $(${FIND} -maxdepth 1 -name 'Dockerfile.*' | sort); do - local target="${f#*Dockerfile.}" - TARGETS+=("${target}") - done -} - -usage() { - cat >&2 <&2 - done -} - -build_target() { - local target="$1" - - local dockerfile="${MYDIR}/Dockerfile.${target}" - # JPEG XL builder images are stored in the gcr.io/jpegxl project. - local tag="gcr.io/jpegxl/${target}" - - echo "Building ${target}" - if ! sudo docker build --no-cache -t "${tag}" -f "${dockerfile}" "${MYDIR}" \ - >"${target}.log" 2>&1; then - echo "${target} failed. See ${target}.log" >&2 - else - echo "Done, to upload image run:" >&2 - echo " sudo docker push ${tag}" - if [[ "${JPEGXL_PUSH:-}" == "1" ]]; then - echo "sudo docker push ${tag}" >&2 - sudo docker push "${tag}" - # The RepoDigest is only created after it is pushed. - local fulltag=$(sudo docker inspect --format="{{.RepoDigests}}" "${tag}") - fulltag="${fulltag#[}" - fulltag="${fulltag%]}" - echo "Updating .gitlab-ci.yml to ${fulltag}" >&2 - sed -E "s;${tag}@sha256:[0-9a-f]+;${fulltag};" \ - -i "${MYDIR}/../.gitlab-ci.yml" - fi - fi -} - -main() { - cd "${MYDIR}" - local target="${1:-}" - - load_targets - if [[ -z "${target}" ]]; then - usage $0 - exit 1 - fi - - if [[ "${target}" == "all" ]]; then - for target in "${TARGETS[@]}"; do - build_target "${target}" - done - else - for target in "$@"; do - build_target "${target}" - done - fi -} - -main "$@" diff --git a/media/libjxl/src/docker/scripts/99_norecommends b/media/libjxl/src/docker/scripts/99_norecommends deleted file mode 100644 index 96d672811d..0000000000 --- a/media/libjxl/src/docker/scripts/99_norecommends +++ /dev/null @@ -1 +0,0 @@ -APT::Install-Recommends "false"; diff --git a/media/libjxl/src/docker/scripts/binutils_align_fix.patch b/media/libjxl/src/docker/scripts/binutils_align_fix.patch deleted file mode 100644 index 6066252db8..0000000000 --- a/media/libjxl/src/docker/scripts/binutils_align_fix.patch +++ /dev/null @@ -1,28 +0,0 @@ -Description: fix lack of alignment in relocations (crashes on mingw) -See https://sourceware.org/git/?p=binutils-gdb.git;a=patch;h=73af69e74974eaa155eec89867e3ccc77ab39f6d -From: Marc -Date: Fri, 9 Nov 2018 11:13:50 +0000 -Subject: [PATCH] Allow for compilers that do not produce aligned .rdat - sections in PE format files. - ---- a/upstream/ld/scripttempl/pe.sc 2020-05-12 18:45:12.000000000 +0200 -+++ b/upstream/ld/scripttempl/pe.sc 2020-05-12 18:47:12.000000000 +0200 -@@ -143,6 +143,7 @@ - .rdata ${RELOCATING+BLOCK(__section_alignment__)} : - { - ${R_RDATA} -+ . = ALIGN(4); - ${RELOCATING+__rt_psrelocs_start = .;} - ${RELOCATING+KEEP(*(.rdata_runtime_pseudo_reloc))} - ${RELOCATING+__rt_psrelocs_end = .;} ---- a/upstream/ld/scripttempl/pep.sc 2020-05-12 18:45:19.000000000 +0200 -+++ b/upstream/ld/scripttempl/pep.sc 2020-05-12 18:47:18.000000000 +0200 -@@ -143,6 +143,7 @@ - .rdata ${RELOCATING+BLOCK(__section_alignment__)} : - { - ${R_RDATA} -+ . = ALIGN(4); - ${RELOCATING+__rt_psrelocs_start = .;} - ${RELOCATING+KEEP(*(.rdata_runtime_pseudo_reloc))} - ${RELOCATING+__rt_psrelocs_end = .;} - diff --git a/media/libjxl/src/docker/scripts/emsdk_install.sh b/media/libjxl/src/docker/scripts/emsdk_install.sh deleted file mode 100644 index 6cf225a9d9..0000000000 --- a/media/libjxl/src/docker/scripts/emsdk_install.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) the JPEG XL Project Authors. All rights reserved. -# -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -EMSDK_URL="https://github.com/emscripten-core/emsdk/archive/main.tar.gz" -EMSDK_DIR="/opt/emsdk" - -EMSDK_RELEASE="2.0.23" - -set -eu -x - -# Temporary files cleanup hooks. -CLEANUP_FILES=() -cleanup() { - if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then - rm -fr "${CLEANUP_FILES[@]}" - fi -} -trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT - -main() { - local workdir=$(mktemp -d --suffix=emsdk) - CLEANUP_FILES+=("${workdir}") - - local emsdktar="${workdir}/emsdk.tar.gz" - curl --output "${emsdktar}" "${EMSDK_URL}" --location - mkdir -p "${EMSDK_DIR}" - tar -zxf "${emsdktar}" -C "${EMSDK_DIR}" --strip-components=1 - - cd "${EMSDK_DIR}" - ./emsdk install --shallow "${EMSDK_RELEASE}" - ./emsdk activate --embedded "${EMSDK_RELEASE}" -} - -main "$@" diff --git a/media/libjxl/src/docker/scripts/jpegxl_builder.sh b/media/libjxl/src/docker/scripts/jpegxl_builder.sh deleted file mode 100644 index bf9f19d4ed..0000000000 --- a/media/libjxl/src/docker/scripts/jpegxl_builder.sh +++ /dev/null @@ -1,518 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) the JPEG XL Project Authors. All rights reserved. -# -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -# Main entry point for all the Dockerfile for jpegxl-builder. This centralized -# file helps sharing code and configuration between Dockerfiles. - -set -eux - -MYDIR=$(dirname $(realpath "$0")) - -# libjpeg-turbo. -JPEG_TURBO_RELEASE="2.0.4" -JPEG_TURBO_URL="https://github.com/libjpeg-turbo/libjpeg-turbo/archive/${JPEG_TURBO_RELEASE}.tar.gz" -JPEG_TURBO_SHA256="7777c3c19762940cff42b3ba4d7cd5c52d1671b39a79532050c85efb99079064" - -# zlib (dependency of libpng) -ZLIB_RELEASE="1.2.11" -ZLIB_URL="https://www.zlib.net/zlib-${ZLIB_RELEASE}.tar.gz" -ZLIB_SHA256="c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1" -# The name in the .pc and the .dll generated don't match in zlib for Windows -# because they use different .dll names in Windows. We avoid that by defining -# UNIX=1. We also install all the .dll files to ${prefix}/lib instead of the -# default ${prefix}/bin. -ZLIB_FLAGS='-DUNIX=1 -DINSTALL_PKGCONFIG_DIR=/${CMAKE_INSTALL_PREFIX}/lib/pkgconfig -DINSTALL_BIN_DIR=/${CMAKE_INSTALL_PREFIX}/lib' - -# libpng -LIBPNG_RELEASE="1.6.37" -LIBPNG_URL="https://github.com/glennrp/libpng/archive/v${LIBPNG_RELEASE}.tar.gz" -LIBPNG_SHA256="ca74a0dace179a8422187671aee97dd3892b53e168627145271cad5b5ac81307" - -# giflib -GIFLIB_RELEASE="5.2.1" -GIFLIB_URL="https://netcologne.dl.sourceforge.net/project/giflib/giflib-${GIFLIB_RELEASE}.tar.gz" -GIFLIB_SHA256="31da5562f44c5f15d63340a09a4fd62b48c45620cd302f77a6d9acf0077879bd" - -# A patch needed to compile GIFLIB in mingw. -GIFLIB_PATCH_URL="https://github.com/msys2/MINGW-packages/raw/3afde38fcee7b3ba2cafd97d76cca8f06934504f/mingw-w64-giflib/001-mingw-build.patch" -GIFLIB_PATCH_SHA256="2b2262ddea87fc07be82e10aeb39eb699239f883c899aa18a16e4d4e40af8ec8" - -# webp -WEBP_RELEASE="1.0.2" -WEBP_URL="https://codeload.github.com/webmproject/libwebp/tar.gz/v${WEBP_RELEASE}" -WEBP_SHA256="347cf85ddc3497832b5fa9eee62164a37b249c83adae0ba583093e039bf4881f" - -# Google benchmark -BENCHMARK_RELEASE="1.5.2" -BENCHMARK_URL="https://github.com/google/benchmark/archive/v${BENCHMARK_RELEASE}.tar.gz" -BENCHMARK_SHA256="dccbdab796baa1043f04982147e67bb6e118fe610da2c65f88912d73987e700c" -BENCHMARK_FLAGS="-DGOOGLETEST_PATH=${MYDIR}/../../third_party/googletest" -# attribute(format(__MINGW_PRINTF_FORMAT, ...)) doesn't work in our -# environment, so we disable the warning. -BENCHMARK_FLAGS="-DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_TESTING=OFF \ - -DCMAKE_CXX_FLAGS=-Wno-ignored-attributes \ - -DCMAKE_POSITION_INDEPENDENT_CODE=ON" - -# V8 -V8_VERSION="9.3.22" - -# Temporary files cleanup hooks. -CLEANUP_FILES=() -cleanup() { - if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then - rm -fr "${CLEANUP_FILES[@]}" - fi -} -trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT - -# List of Ubuntu arch names supported by the builder (such as "i386"). -LIST_ARCHS=( - amd64 - i386 - arm64 - armhf -) - -# List of target triplets supported by the builder. -LIST_TARGETS=( - x86_64-linux-gnu - i686-linux-gnu - arm-linux-gnueabihf - aarch64-linux-gnu -) -LIST_MINGW_TARGETS=( - i686-w64-mingw32 - x86_64-w64-mingw32 -) -LIST_WASM_TARGETS=( - wasm32 -) - -# Setup the apt repositories and supported architectures. -setup_apt() { - apt-get update -y - apt-get install -y curl gnupg ca-certificates - - apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1E9377A2BA9EF27F - - # node sources. - cat >/etc/apt/sources.list.d/nodesource.list <>"${newlist}" - fi - - main_list=$(echo "${main_list[@]}" | tr ' ' ,) - grep -v -E '^#' "${bkplist}" | - sed -E "s;^deb (http[^ ]+) (.*)\$;deb [arch=${main_list}] \\1 \\2\ndeb-src [arch=${main_list}] \\1 \\2;" \ - >>"${newlist}" - mv "${newlist}" /etc/apt/sources.list -} - -install_pkgs() { - packages=( - # Native compilers (minimum for SIMD is clang-7) - clang-7 clang-format-7 clang-tidy-7 - - # TODO: Consider adding clang-8 to every builder: - # clang-8 clang-format-8 clang-tidy-8 - - # For cross-compiling to Windows with mingw. - mingw-w64 - wine64 - wine-binfmt - - # Native tools. - bsdmainutils - cmake - extra-cmake-modules - git - llvm - nasm - ninja-build - parallel - pkg-config - - # For compiling / testing JNI wrapper. JDK8 is almost 2x smaller than JDK11 - # openjdk-8-jdk-headless would be 50MB smaller, unfortunately, CMake - # does mistakenly thinks it does not contain JNI feature. - openjdk-8-jdk - - # These are used by the ./ci.sh lint in the native builder. - clang-format-7 - clang-format-8 - - # For coverage builds - gcovr - - # For compiling giflib documentation. - xmlto - - # Common libraries. - libstdc++-8-dev - - # We don't use tcmalloc on archs other than amd64. This installs - # libgoogle-perftools4:amd64. - google-perftools - - # NodeJS for running WASM tests - nodejs - - # To generate API documentation. - doxygen - - # Freezes version that builds (passes tests). Newer version - # (2.30-21ubuntu1~18.04.4) claims to fix "On Intel Skylake - # (-march=native) generated avx512 instruction can be wrong", - # but newly added tests does not pass. Perhaps the problem is - # that mingw package is not updated. - binutils-source=2.30-15ubuntu1 - ) - - # Install packages that are arch-dependent. - local ubarch - for ubarch in "${LIST_ARCHS[@]}"; do - packages+=( - # Library dependencies. These normally depend on the target architecture - # we are compiling for and can't usually be installed for multiple - # architectures at the same time. - libgif7:"${ubarch}" - libjpeg-dev:"${ubarch}" - libpng-dev:"${ubarch}" - libqt5x11extras5-dev:"${ubarch}" - - libstdc++-8-dev:"${ubarch}" - qtbase5-dev:"${ubarch}" - - # For OpenEXR: - libilmbase12:"${ubarch}" - libopenexr22:"${ubarch}" - - # TCMalloc dependency - libunwind-dev:"${ubarch}" - - # Cross-compiling tools per arch. - libc6-dev-"${ubarch}"-cross - libstdc++-8-dev-"${ubarch}"-cross - ) - done - - local target - for target in "${LIST_TARGETS[@]}"; do - # Per target cross-compiling tools. - if [[ "${target}" != "x86_64-linux-gnu" ]]; then - packages+=( - binutils-"${target}" - gcc-"${target}" - ) - fi - done - - # Install all the manual packages via "apt install" for the main arch. These - # will be installed for other archs via manual download and unpack. - apt install -y "${packages[@]}" "${UNPACK_PKGS[@]}" -} - -# binutils <2.32 need a patch. -install_binutils() { - local workdir=$(mktemp -d --suffix=_install) - CLEANUP_FILES+=("${workdir}") - pushd "${workdir}" - apt source binutils-mingw-w64 - apt -y build-dep binutils-mingw-w64 - cd binutils-mingw-w64-8ubuntu1 - cp "${MYDIR}/binutils_align_fix.patch" debian/patches - echo binutils_align_fix.patch >> debian/patches/series - dpkg-buildpackage -b - cd .. - dpkg -i *deb - popd -} - -# Install a library from the source code for multiple targets. -# Usage: install_from_source [] -install_from_source() { - local package="$1" - shift - - local url - eval "url=\${${package}_URL}" - local sha256 - eval "sha256=\${${package}_SHA256}" - # Optional package flags - local pkgflags - eval "pkgflags=\${${package}_FLAGS:-}" - - local workdir=$(mktemp -d --suffix=_install) - CLEANUP_FILES+=("${workdir}") - - local tarfile="${workdir}"/$(basename "${url}") - curl -L --output "${tarfile}" "${url}" - if ! echo "${sha256} ${tarfile}" | sha256sum -c --status -; then - echo "SHA256 mismatch for ${url}: expected ${sha256} but found:" - sha256sum "${tarfile}" - exit 1 - fi - - local target - for target in "$@"; do - echo "Installing ${package} for target ${target} from ${url}" - - local srcdir="${workdir}/source-${target}" - mkdir -p "${srcdir}" - tar -zxf "${tarfile}" -C "${srcdir}" --strip-components=1 - - local prefix="/usr" - if [[ "${target}" != "x86_64-linux-gnu" ]]; then - prefix="/usr/${target}" - fi - - # Apply patches to buildfiles. - if [[ "${package}" == "GIFLIB" && "${target}" == *mingw32 ]]; then - # GIFLIB Makefile has several problems so we need to fix them here. We are - # using a patch from MSYS2 that already fixes the compilation for mingw. - local make_patch="${srcdir}/libgif.patch" - curl -L "${GIFLIB_PATCH_URL}" -o "${make_patch}" - echo "${GIFLIB_PATCH_SHA256} ${make_patch}" | sha256sum -c --status - - patch "${srcdir}/Makefile" < "${make_patch}" - elif [[ "${package}" == "LIBPNG" && "${target}" == wasm* ]]; then - # Cut the dependency to libm; there is pull request to fix it, so this - # might not be needed in the future. - sed -i 's/APPLE/EMSCRIPTEN/g' "${srcdir}/CMakeLists.txt" - fi - - local cmake_args=() - local export_args=("CC=clang-7" "CXX=clang++-7") - local cmake="cmake" - local make="make" - local system_name="Linux" - if [[ "${target}" == *mingw32 ]]; then - system_name="Windows" - # When compiling with clang, CMake doesn't detect that we are using mingw. - cmake_args+=( - -DMINGW=1 - # Googletest needs this when cross-compiling to windows - -DCMAKE_CROSSCOMPILING=1 - -DHAVE_STD_REGEX=0 - -DHAVE_POSIX_REGEX=0 - -DHAVE_GNU_POSIX_REGEX=0 - ) - local windres=$(which ${target}-windres || true) - if [[ -n "${windres}" ]]; then - cmake_args+=(-DCMAKE_RC_COMPILER="${windres}") - fi - fi - if [[ "${target}" == wasm* ]]; then - system_name="WASM" - cmake="emcmake cmake" - make="emmake make" - export_args=() - cmake_args+=( - -DCMAKE_FIND_ROOT_PATH="${prefix}" - -DCMAKE_PREFIX_PATH="${prefix}" - ) - # Static and shared library link to the same file -> race condition. - nproc=1 - else - nproc=`nproc --all` - fi - cmake_args+=(-DCMAKE_SYSTEM_NAME="${system_name}") - - if [[ "${target}" != "x86_64-linux-gnu" ]]; then - # Cross-compiling. - cmake_args+=( - -DCMAKE_C_COMPILER_TARGET="${target}" - -DCMAKE_CXX_COMPILER_TARGET="${target}" - -DCMAKE_SYSTEM_PROCESSOR="${target%%-*}" - ) - fi - - if [[ -e "${srcdir}/CMakeLists.txt" ]]; then - # Most packages use cmake for building which is easier to configure for - # cross-compiling. - if [[ "${package}" == "JPEG_TURBO" && "${target}" == wasm* ]]; then - # JT erroneously detects WASM CPU as i386 and tries to use asm. - # Wasm/Emscripten support for dynamic linking is incomplete; disable - # to avoid CMake warning. - cmake_args+=(-DWITH_SIMD=0 -DENABLE_SHARED=OFF) - fi - ( - cd "${srcdir}" - export ${export_args[@]} - ${cmake} \ - -DCMAKE_INSTALL_PREFIX="${prefix}" \ - "${cmake_args[@]}" ${pkgflags} - ${make} -j${nproc} - ${make} install - ) - elif [[ "${package}" == "GIFLIB" ]]; then - # GIFLIB doesn't yet have a cmake build system. There is a pull - # request in giflib for adding CMakeLists.txt so this might not be - # needed in the future. - ( - cd "${srcdir}" - local giflib_make_flags=( - CFLAGS="-O2 --target=${target} -std=gnu99" - PREFIX="${prefix}" - ) - if [[ "${target}" != wasm* ]]; then - giflib_make_flags+=(CC=clang-7) - fi - # giflib make dependencies are not properly set up so parallel building - # doesn't work for everything. - ${make} -j${nproc} libgif.a "${giflib_make_flags[@]}" - ${make} -j${nproc} all "${giflib_make_flags[@]}" - ${make} install "${giflib_make_flags[@]}" - ) - else - echo "Don't know how to install ${package}" - exit 1 - fi - - # CMake mistakenly uses ".so" libraries and EMCC fails to link properly. - if [[ "${target}" == wasm* ]]; then - rm -f "${prefix}/lib"/*.so* - fi - done -} - -# Packages that are manually unpacked for each architecture. -UNPACK_PKGS=( - libgif-dev - libclang-common-7-dev - - # For OpenEXR: - libilmbase-dev - libopenexr-dev - - # TCMalloc - libgoogle-perftools-dev - libtcmalloc-minimal4 - libgoogle-perftools4 -) - -# Main script entry point. -main() { - cd "${MYDIR}" - - # Configure the repositories with the sources for multi-arch cross - # compilation. - setup_apt - apt-get update -y - apt-get dist-upgrade -y - - install_pkgs - install_binutils - apt clean - - # Remove prebuilt Java classes cache. - rm /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/classes.jsa - - # Manually extract packages for the target arch that can't install it directly - # at the same time as the native ones. - local ubarch - for ubarch in "${LIST_ARCHS[@]}"; do - if [[ "${ubarch}" != "amd64" ]]; then - local pkg - for pkg in "${UNPACK_PKGS[@]}"; do - apt download "${pkg}":"${ubarch}" - dpkg -x "${pkg}"_*_"${ubarch}".deb / - done - fi - done - # TODO: Add clang from the llvm repos. This is problematic since we are - # installing libclang-common-7-dev:"${ubarch}" from the ubuntu ports repos - # which is not available in the llvm repos so it might have a different - # version than the ubuntu ones. - - # Remove the win32 libgcc version. The gcc-mingw-w64-x86-64 (and i686) - # packages install two libgcc versions: - # /usr/lib/gcc/x86_64-w64-mingw32/7.3-posix - # /usr/lib/gcc/x86_64-w64-mingw32/7.3-win32 - # (exact libgcc version number depends on the package version). - # - # Clang will pick the best libgcc, sorting by version, but it doesn't - # seem to be a way to specify one or the other one, except by passing - # -nostdlib and setting all the include paths from the command line. - # To check which one is being used you can run: - # clang++-7 --target=x86_64-w64-mingw32 -v -print-libgcc-file-name - # We need to use the "posix" versions for thread support, so here we - # just remove the other one. - local target - for target in "${LIST_MINGW_TARGETS[@]}"; do - update-alternatives --set "${target}-gcc" $(which "${target}-gcc-posix") - local gcc_win32_path=$("${target}-cpp-win32" -print-libgcc-file-name) - rm -rf $(dirname "${gcc_win32_path}") - done - - # TODO: Add msan for the target when cross-compiling. This only installs it - # for amd64. - ./msan_install.sh - - # Build and install qemu user-linux targets. - ./qemu_install.sh - - # Install emscripten SDK. - ./emsdk_install.sh - - # Setup environment for building WASM libraries from sources. - source /opt/emsdk/emsdk_env.sh - - # Install some dependency libraries manually for the different targets. - - install_from_source JPEG_TURBO "${LIST_MINGW_TARGETS[@]}" "${LIST_WASM_TARGETS[@]}" - install_from_source ZLIB "${LIST_MINGW_TARGETS[@]}" "${LIST_WASM_TARGETS[@]}" - install_from_source LIBPNG "${LIST_MINGW_TARGETS[@]}" "${LIST_WASM_TARGETS[@]}" - install_from_source GIFLIB "${LIST_MINGW_TARGETS[@]}" "${LIST_WASM_TARGETS[@]}" - # webp in Ubuntu is relatively old so we install it from source for everybody. - install_from_source WEBP "${LIST_TARGETS[@]}" "${LIST_MINGW_TARGETS[@]}" - - install_from_source BENCHMARK "${LIST_TARGETS[@]}" "${LIST_MINGW_TARGETS[@]}" - - # Install v8. v8 has better WASM SIMD support than NodeJS 14 (LTS). - # First we need the installer to install v8. - npm install jsvu -g - # install specific version; - HOME=/opt jsvu --os=linux64 "v8@${V8_VERSION}" - ln -s "/opt/.jsvu/v8-${V8_VERSION}" "/opt/.jsvu/v8" - - # Cleanup. - find /var/lib/apt/lists/ -mindepth 1 -delete -} - -main "$@" diff --git a/media/libjxl/src/docker/scripts/msan_install.sh b/media/libjxl/src/docker/scripts/msan_install.sh deleted file mode 100644 index 0216f62b04..0000000000 --- a/media/libjxl/src/docker/scripts/msan_install.sh +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) the JPEG XL Project Authors. All rights reserved. -# -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -set -eu - -MYDIR=$(dirname $(realpath "$0")) - -# Convenience flag to pass both CMAKE_C_FLAGS and CMAKE_CXX_FLAGS -CMAKE_FLAGS=${CMAKE_FLAGS:-} -CMAKE_C_FLAGS=${CMAKE_C_FLAGS:-${CMAKE_FLAGS}} -CMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS:-${CMAKE_FLAGS}} -CMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS:-} - -CLANG_VERSION="${CLANG_VERSION:-}" -# Detect the clang version suffix and store it in CLANG_VERSION. For example, -# "6.0" for clang 6 or "7" for clang 7. -detect_clang_version() { - if [[ -n "${CLANG_VERSION}" ]]; then - return 0 - fi - local clang_version=$("${CC:-clang}" --version | head -n1) - local llvm_tag - case "${clang_version}" in - "clang version 6."*) - CLANG_VERSION="6.0" - ;; - "clang version 7."*) - CLANG_VERSION="7" - ;; - "clang version 8."*) - CLANG_VERSION="8" - ;; - "clang version 9."*) - CLANG_VERSION="9" - ;; - *) - echo "Unknown clang version: ${clang_version}" >&2 - return 1 - esac -} - -# Temporary files cleanup hooks. -CLEANUP_FILES=() -cleanup() { - if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then - rm -fr "${CLEANUP_FILES[@]}" - fi -} -trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT - -# Install libc++ libraries compiled with msan in the msan_prefix for the current -# compiler version. -cmd_msan_install() { - local tmpdir=$(mktemp -d) - CLEANUP_FILES+=("${tmpdir}") - # Detect the llvm to install: - export CC="${CC:-clang}" - export CXX="${CXX:-clang++}" - detect_clang_version - local llvm_tag - case "${CLANG_VERSION}" in - "6.0") - llvm_tag="llvmorg-6.0.1" - ;; - "7") - llvm_tag="llvmorg-7.0.1" - ;; - "8") - llvm_tag="llvmorg-8.0.0" - ;; - *) - echo "Unknown clang version: ${clang_version}" >&2 - return 1 - esac - local llvm_targz="${tmpdir}/${llvm_tag}.tar.gz" - curl -L --show-error -o "${llvm_targz}" \ - "https://github.com/llvm/llvm-project/archive/${llvm_tag}.tar.gz" - tar -C "${tmpdir}" -zxf "${llvm_targz}" - local llvm_root="${tmpdir}/llvm-project-${llvm_tag}" - - local msan_prefix="${HOME}/.msan/${CLANG_VERSION}" - rm -rf "${msan_prefix}" - - declare -A CMAKE_EXTRAS - CMAKE_EXTRAS[libcxx]="\ - -DLIBCXX_CXX_ABI=libstdc++ \ - -DLIBCXX_INSTALL_EXPERIMENTAL_LIBRARY=ON" - - for project in libcxx; do - local proj_build="${tmpdir}/build-${project}" - local proj_dir="${llvm_root}/${project}" - mkdir -p "${proj_build}" - cmake -B"${proj_build}" -H"${proj_dir}" \ - -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DLLVM_USE_SANITIZER=Memory \ - -DLLVM_PATH="${llvm_root}/llvm" \ - -DLLVM_CONFIG_PATH="$(which llvm-config llvm-config-7 llvm-config-6.0 | \ - head -n1)" \ - -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS}" \ - -DCMAKE_C_FLAGS="${CMAKE_C_FLAGS}" \ - -DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" \ - -DCMAKE_INSTALL_PREFIX="${msan_prefix}" \ - ${CMAKE_EXTRAS[${project}]} - cmake --build "${proj_build}" - ninja -C "${proj_build}" install - done -} - -main() { - set -x - for version in 6.0 7 8; do - if ! which "clang-${version}" >/dev/null; then - echo "Skipping msan install for clang version ${version}" - continue - fi - ( - trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT - export CLANG_VERSION=${version} - export CC=clang-${version} - export CXX=clang++-${version} - cmd_msan_install - ) & - done - wait -} - -main "$@" diff --git a/media/libjxl/src/docker/scripts/qemu_install.sh b/media/libjxl/src/docker/scripts/qemu_install.sh deleted file mode 100644 index 8106c4471d..0000000000 --- a/media/libjxl/src/docker/scripts/qemu_install.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env bash -# Copyright (c) the JPEG XL Project Authors. All rights reserved. -# -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -QEMU_RELEASE="4.1.0" -QEMU_URL="https://download.qemu.org/qemu-${QEMU_RELEASE}.tar.xz" -QEMU_ARCHS=( - aarch64 - arm - i386 - # TODO: Consider adding these: - # aarch64_be - # mips64el - # mips64 - # mips - # ppc64 - # ppc -) - -# Ubuntu packages not installed that are needed to build qemu. -QEMU_BUILD_DEPS=( - libglib2.0-dev - libpixman-1-dev - flex - bison -) - -set -eu -x - -# Temporary files cleanup hooks. -CLEANUP_FILES=() -cleanup() { - if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then - rm -fr "${CLEANUP_FILES[@]}" - fi -} -trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT - -main() { - local workdir=$(mktemp -d --suffix=qemu) - CLEANUP_FILES+=("${workdir}") - - apt install -y "${QEMU_BUILD_DEPS[@]}" - - local qemutar="${workdir}/qemu.tar.gz" - curl --output "${qemutar}" "${QEMU_URL}" - tar -Jxf "${qemutar}" -C "${workdir}" - local srcdir="${workdir}/qemu-${QEMU_RELEASE}" - - local builddir="${workdir}/build" - local prefixdir="${workdir}/prefix" - mkdir -p "${builddir}" - - # List of targets to build. - local targets="" - local make_targets=() - local target - for target in "${QEMU_ARCHS[@]}"; do - targets="${targets} ${target}-linux-user" - # Build just the linux-user targets. - make_targets+=("${target}-linux-user/all") - done - - cd "${builddir}" - "${srcdir}/configure" \ - --prefix="${prefixdir}" \ - --static --disable-system --enable-linux-user \ - --target-list="${targets}" - - make -j $(nproc --all || echo 1) "${make_targets[@]}" - - # Manually install these into the non-standard location. This script runs as - # root anyway. - for target in "${QEMU_ARCHS[@]}"; do - cp "${target}-linux-user/qemu-${target}" "/usr/bin/qemu-${target}-static" - done - - apt autoremove -y --purge "${QEMU_BUILD_DEPS[@]}" -} - -main "$@" diff --git a/media/libjxl/src/examples/CMakeLists.txt b/media/libjxl/src/examples/CMakeLists.txt index 88dc27c49f..6b2b99c124 100644 --- a/media/libjxl/src/examples/CMakeLists.txt +++ b/media/libjxl/src/examples/CMakeLists.txt @@ -11,46 +11,18 @@ 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(JxlThreads REQUIRED IMPORTED_TARGET libjxl_threads) +pkg_check_modules(Jxl REQUIRED IMPORTED_TARGET libjxl libjxl_cms libjxl_threads) # Build the example encoder/decoder binaries using the default shared libraries # installed. +add_executable(decode_exif_metadata decode_exif_metadata.cc) +target_link_libraries(decode_exif_metadata PkgConfig::Jxl) + add_executable(decode_oneshot decode_oneshot.cc) -target_link_libraries(decode_oneshot PkgConfig::Jxl PkgConfig::JxlThreads) +target_link_libraries(decode_oneshot PkgConfig::Jxl) add_executable(decode_progressive decode_progressive.cc) -target_link_libraries(decode_progressive PkgConfig::Jxl PkgConfig::JxlThreads) +target_link_libraries(decode_progressive PkgConfig::Jxl) add_executable(encode_oneshot encode_oneshot.cc) -target_link_libraries(encode_oneshot PkgConfig::Jxl 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) -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}" -) -add_library(StaticJxlThreads INTERFACE IMPORTED GLOBAL) -set_target_properties(StaticJxlThreads PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${JxlThreads_STATIC_INCLUDE_DIR}" - INTERFACE_COMPILE_OPTIONS "${JxlThreads_STATIC_CFLAGS_OTHER}" - # libgcc uses weak symbols for pthread which means that -lpthread is not - # linked when compiling a static binary. This is a platform-specific fix for - # that. - 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) - -add_executable(encode_oneshot_static encode_oneshot.cc) -target_link_libraries(encode_oneshot_static - -static StaticJxl StaticJxlThreads) +target_link_libraries(encode_oneshot PkgConfig::Jxl) diff --git a/media/libjxl/src/examples/decode_exif_metadata.cc b/media/libjxl/src/examples/decode_exif_metadata.cc index adfe5f8424..c1609edde3 100644 --- a/media/libjxl/src/examples/decode_exif_metadata.cc +++ b/media/libjxl/src/examples/decode_exif_metadata.cc @@ -7,6 +7,8 @@ // available at once). The example outputs the pixels and color information to a // floating point image and an ICC profile on disk. +#include +#include #include #include #include @@ -14,16 +16,14 @@ #include -#include "jxl/decode.h" -#include "jxl/decode_cxx.h" - bool DecodeJpegXlExif(const uint8_t* jxl, size_t size, std::vector* exif) { auto dec = JxlDecoderMake(nullptr); // We're only interested in the Exif boxes in this example, so don't // subscribe to events related to pixel data. - if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BOX)) { + if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents( + dec.get(), JXL_DEC_BOX | JXL_DEC_BOX_COMPLETE)) { fprintf(stderr, "JxlDecoderSubscribeEvents failed\n"); return false; } @@ -57,8 +57,9 @@ bool DecodeJpegXlExif(const uint8_t* jxl, size_t size, return true; } JxlBoxType type; - if (JXL_DEC_SUCCESS != - JxlDecoderGetBoxType(dec.get(), type, support_decompression)) { + status = JxlDecoderGetBoxType(dec.get(), type, + TO_JXL_BOOL(support_decompression)); + if (JXL_DEC_SUCCESS != status) { fprintf(stderr, "Error, failed to get box type\n"); return false; } @@ -72,7 +73,7 @@ bool DecodeJpegXlExif(const uint8_t* jxl, size_t size, exif->resize(exif->size() + kChunkSize); JxlDecoderSetBoxBuffer(dec.get(), exif->data() + output_pos, exif->size() - output_pos); - } else if (status == JXL_DEC_SUCCESS) { + } else if (status == JXL_DEC_BOX_COMPLETE) { if (!exif->empty()) { size_t remaining = JxlDecoderReleaseBoxBuffer(dec.get()); exif->resize(exif->size() - remaining); @@ -97,7 +98,7 @@ bool LoadFile(const char* filename, std::vector* out) { return false; } - long size = ftell(file); + long size = ftell(file); // NOLINT // Avoid invalid file or directory. if (size >= LONG_MAX || size < 0) { fclose(file); diff --git a/media/libjxl/src/examples/decode_oneshot.cc b/media/libjxl/src/examples/decode_oneshot.cc index 932193fd17..b7e17c21a4 100644 --- a/media/libjxl/src/examples/decode_oneshot.cc +++ b/media/libjxl/src/examples/decode_oneshot.cc @@ -7,7 +7,12 @@ // available at once). The example outputs the pixels and color information to a // floating point image and an ICC profile on disk. -#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -15,11 +20,6 @@ #include -#include "jxl/decode.h" -#include "jxl/decode_cxx.h" -#include "jxl/resizable_parallel_runner.h" -#include "jxl/resizable_parallel_runner_cxx.h" - /** Decodes JPEG XL image to floating point pixels and ICC Profile. Pixel are * stored as floating point, as interleaved RGBA (4 floating point values per * pixel), line per line from top to bottom. Pixel values have nominal range @@ -77,15 +77,14 @@ bool DecodeJpegXlOneShot(const uint8_t* jxl, size_t size, // Get the ICC color profile of the pixel data size_t icc_size; if (JXL_DEC_SUCCESS != - JxlDecoderGetICCProfileSize( - dec.get(), &format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size)) { + JxlDecoderGetICCProfileSize(dec.get(), JXL_COLOR_PROFILE_TARGET_DATA, + &icc_size)) { fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n"); return false; } icc_profile->resize(icc_size); if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile( - dec.get(), &format, - JXL_COLOR_PROFILE_TARGET_DATA, + dec.get(), JXL_COLOR_PROFILE_TARGET_DATA, icc_profile->data(), icc_profile->size())) { fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n"); return false; @@ -98,13 +97,13 @@ bool DecodeJpegXlOneShot(const uint8_t* jxl, size_t size, return false; } if (buffer_size != *xsize * *ysize * 16) { - fprintf(stderr, "Invalid out buffer size %" PRIu64 " %" PRIu64 "\n", - static_cast(buffer_size), - static_cast(*xsize * *ysize * 16)); + fprintf(stderr, "Invalid out buffer size %d %d\n", + static_cast(buffer_size), + static_cast(*xsize * *ysize * 16)); return false; } pixels->resize(*xsize * *ysize * 4); - void* pixels_buffer = (void*)pixels->data(); + void* pixels_buffer = static_cast(pixels->data()); size_t pixels_buffer_size = pixels->size() * sizeof(float); if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format, pixels_buffer, @@ -143,8 +142,8 @@ bool WritePFM(const char* filename, const float* pixels, size_t xsize, uint8_t little_endian[4]; memcpy(little_endian, &endian_test, 4); - fprintf(file, "PF\n%d %d\n%s\n", (int)xsize, (int)ysize, - little_endian[0] ? "-1.0" : "1.0"); + fprintf(file, "PF\n%d %d\n%s\n", static_cast(xsize), + static_cast(ysize), little_endian[0] ? "-1.0" : "1.0"); for (int y = ysize - 1; y >= 0; y--) { for (size_t x = 0; x < xsize; x++) { for (size_t c = 0; c < 3; c++) { @@ -170,7 +169,7 @@ bool LoadFile(const char* filename, std::vector* out) { return false; } - long size = ftell(file); + long size = ftell(file); // NOLINT // Avoid invalid file or directory. if (size >= LONG_MAX || size < 0) { fclose(file); @@ -229,7 +228,8 @@ int main(int argc, char* argv[]) { std::vector pixels; std::vector icc_profile; - size_t xsize = 0, ysize = 0; + size_t xsize = 0; + size_t ysize = 0; if (!DecodeJpegXlOneShot(jxl.data(), jxl.size(), &pixels, &xsize, &ysize, &icc_profile)) { fprintf(stderr, "Error while decoding the jxl file\n"); diff --git a/media/libjxl/src/examples/decode_progressive.cc b/media/libjxl/src/examples/decode_progressive.cc index 77d2a0f5cc..7a3a9aa33b 100644 --- a/media/libjxl/src/examples/decode_progressive.cc +++ b/media/libjxl/src/examples/decode_progressive.cc @@ -6,19 +6,22 @@ // This C++ example decodes a JPEG XL image progressively (input bytes are // passed in chunks). The example outputs the intermediate steps to PAM files. -#include +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif + +#include +#include +#include +#include #include -#include -#include -#include +#include // PRIu64 +#include +#include +#include #include -#include "jxl/decode.h" -#include "jxl/decode_cxx.h" -#include "jxl/resizable_parallel_runner.h" -#include "jxl/resizable_parallel_runner_cxx.h" - bool WritePAM(const char* filename, const uint8_t* buffer, size_t w, size_t h) { FILE* fp = fopen(filename, "wb"); if (!fp) { @@ -26,11 +29,14 @@ bool WritePAM(const char* filename, const uint8_t* buffer, size_t w, size_t h) { return false; } fprintf(fp, - "P7\nWIDTH %" PRIu64 "\nHEIGHT %" PRIu64 - "\nDEPTH 4\nMAXVAL 255\nTUPLTYPE " + "P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\nTUPLTYPE " "RGB_ALPHA\nENDHDR\n", - static_cast(w), static_cast(h)); - fwrite(buffer, 1, w * h * 4, fp); + static_cast(w), static_cast(h)); + size_t num_bytes = w * h * 4; + if (fwrite(buffer, 1, num_bytes, fp) != num_bytes) { + fclose(fp); + return false; + }; if (fclose(fp) != 0) { return false; } @@ -44,7 +50,8 @@ bool DecodeJpegXlProgressive(const uint8_t* jxl, size_t size, const char* filename, size_t chunksize) { std::vector pixels; std::vector icc_profile; - size_t xsize = 0, ysize = 0; + size_t xsize = 0; + size_t ysize = 0; // Multi-threaded parallel runner. auto runner = JxlResizableParallelRunnerMake(nullptr); @@ -121,16 +128,14 @@ bool DecodeJpegXlProgressive(const uint8_t* jxl, size_t size, // Get the ICC color profile of the pixel data size_t icc_size; if (JXL_DEC_SUCCESS != - JxlDecoderGetICCProfileSize(dec.get(), &format, - JXL_COLOR_PROFILE_TARGET_ORIGINAL, - &icc_size)) { + JxlDecoderGetICCProfileSize( + dec.get(), JXL_COLOR_PROFILE_TARGET_ORIGINAL, &icc_size)) { fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n"); return false; } icc_profile.resize(icc_size); if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile( - dec.get(), &format, - JXL_COLOR_PROFILE_TARGET_ORIGINAL, + dec.get(), JXL_COLOR_PROFILE_TARGET_ORIGINAL, icc_profile.data(), icc_profile.size())) { fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n"); return false; @@ -149,11 +154,9 @@ bool DecodeJpegXlProgressive(const uint8_t* jxl, size_t size, return false; } pixels.resize(xsize * ysize * 4); - void* pixels_buffer = (void*)pixels.data(); - size_t pixels_buffer_size = pixels.size() * sizeof(float); if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format, - pixels_buffer, - pixels_buffer_size)) { + pixels.data(), + pixels.size())) { fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n"); return false; } @@ -175,7 +178,7 @@ bool LoadFile(const char* filename, std::vector* out) { return false; } - long size = ftell(file); + long size = ftell(file); // NOLINT // Avoid invalid file or directory. if (size >= LONG_MAX || size < 0) { fclose(file); @@ -221,7 +224,7 @@ int main(int argc, char* argv[]) { } size_t chunksize = jxl.size(); if (argc > 3) { - long cs = atol(argv[3]); + long cs = atol(argv[3]); // NOLINT if (cs < 100) { fprintf(stderr, "Chunk size is too low, try at least 100 bytes\n"); return 1; diff --git a/media/libjxl/src/examples/encode_oneshot.cc b/media/libjxl/src/examples/encode_oneshot.cc index f1cd9ab00f..6c45a70411 100644 --- a/media/libjxl/src/examples/encode_oneshot.cc +++ b/media/libjxl/src/examples/encode_oneshot.cc @@ -6,18 +6,22 @@ // This example encodes a file containing a floating point image to another // file containing JPEG XL image with a single frame. +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include #include #include #include -#include "jxl/encode.h" -#include "jxl/encode_cxx.h" -#include "jxl/thread_parallel_runner.h" -#include "jxl/thread_parallel_runner_cxx.h" - /** * Reads from .pfm file (Portable FloatMap) * @@ -35,15 +39,16 @@ bool ReadPFM(const char* filename, std::vector* pixels, uint32_t* xsize, return false; } uint32_t endian_test = 1; - uint8_t little_endian[4]; - memcpy(little_endian, &endian_test, 4); + uint8_t little_endian_check[4]; + memcpy(little_endian_check, &endian_test, 4); + bool little_endian = (little_endian_check[0] == 1); if (fseek(file, 0, SEEK_END) != 0) { fclose(file); return false; } - long size = ftell(file); + long size = ftell(file); // NOLINT // Avoid invalid file or directory. if (size >= LONG_MAX || size < 0) { fclose(file); @@ -59,7 +64,8 @@ bool ReadPFM(const char* filename, std::vector* pixels, uint32_t* xsize, data.resize(size); size_t readsize = fread(data.data(), 1, size, file); - if ((long)readsize != size) { + if (static_cast(readsize) != size) { // NOLINT + fclose(file); return false; } if (fclose(file) != 0) { @@ -111,12 +117,13 @@ bool ReadPFM(const char* filename, std::vector* pixels, uint32_t* xsize, fprintf(stderr, "%s doesn't seem to be a Portable FloatMap file (pixel data bytes " "are %d, but expected %d * %d * 3 * 4 + %d (%d).\n", - filename, (int)data.size(), (int)*ysize, (int)*xsize, (int)offset, - (int)(*ysize * *xsize * 3 * 4 + offset)); + filename, static_cast(data.size()), static_cast(*ysize), + static_cast(*xsize), static_cast(offset), + static_cast(*ysize * *xsize * 3 * 4 + offset)); return false; } - if (!!little_endian[0] != input_little_endian) { + if (little_endian != input_little_endian) { fprintf(stderr, "%s has a different endianness than we do, conversion is not " "supported.\n", @@ -127,7 +134,7 @@ bool ReadPFM(const char* filename, std::vector* pixels, uint32_t* xsize, pixels->resize(*ysize * *xsize * 3); for (int y = *ysize - 1; y >= 0; y--) { - for (int x = 0; x < (int)*xsize; x++) { + for (int x = 0; x < static_cast(*xsize); x++) { for (int c = 0; c < 3; c++) { memcpy(pixels->data() + (y * *xsize + x) * 3 + c, data.data() + offset, sizeof(float)); @@ -175,8 +182,8 @@ bool EncodeJxlOneshot(const std::vector& pixels, const uint32_t xsize, } JxlColorEncoding color_encoding = {}; - JxlColorEncodingSetToSRGB(&color_encoding, - /*is_gray=*/pixel_format.num_channels < 3); + JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3); + JxlColorEncodingSetToSRGB(&color_encoding, is_gray); if (JXL_ENC_SUCCESS != JxlEncoderSetColorEncoding(enc.get(), &color_encoding)) { fprintf(stderr, "JxlEncoderSetColorEncoding failed\n"); @@ -188,7 +195,7 @@ bool EncodeJxlOneshot(const std::vector& pixels, const uint32_t xsize, if (JXL_ENC_SUCCESS != JxlEncoderAddImageFrame(frame_settings, &pixel_format, - (void*)pixels.data(), + static_cast(pixels.data()), sizeof(float) * pixels.size())) { fprintf(stderr, "JxlEncoderAddImageFrame failed\n"); return false; @@ -229,6 +236,7 @@ bool WriteFile(const std::vector& bytes, const char* filename) { if (fwrite(bytes.data(), sizeof(uint8_t), bytes.size(), file) != bytes.size()) { fprintf(stderr, "Could not write bytes to %s\n", filename); + fclose(file); return false; } if (fclose(file) != 0) { diff --git a/media/libjxl/src/examples/examples.cmake b/media/libjxl/src/examples/examples.cmake index fd159578bc..b13731ca3c 100644 --- a/media/libjxl/src/examples/examples.cmake +++ b/media/libjxl/src/examples/examples.cmake @@ -3,6 +3,8 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. +add_executable(decode_exif_metadata ${CMAKE_CURRENT_LIST_DIR}/decode_exif_metadata.cc) +target_link_libraries(decode_exif_metadata jxl_dec jxl_threads) add_executable(decode_oneshot ${CMAKE_CURRENT_LIST_DIR}/decode_oneshot.cc) target_link_libraries(decode_oneshot jxl_dec jxl_threads) add_executable(decode_progressive ${CMAKE_CURRENT_LIST_DIR}/decode_progressive.cc) diff --git a/media/libjxl/src/experimental/fast_lossless/fast_lossless.cc b/media/libjxl/src/experimental/fast_lossless/fast_lossless.cc deleted file mode 100644 index 9b442aac19..0000000000 --- a/media/libjxl/src/experimental/fast_lossless/fast_lossless.cc +++ /dev/null @@ -1,1362 +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 "fast_lossless.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#if (!defined(__BYTE_ORDER__) || (__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__)) -#error "system not known to be little endian" -#endif - -struct BitWriter { - void Allocate(size_t maximum_bit_size) { - assert(data == nullptr); - // Leave some padding. - data.reset((uint8_t*)malloc(maximum_bit_size / 8 + 32)); - } - - void Write(uint32_t count, uint64_t bits) { - buffer |= bits << bits_in_buffer; - bits_in_buffer += count; - memcpy(data.get() + bytes_written, &buffer, 8); - size_t bytes_in_buffer = bits_in_buffer / 8; - bits_in_buffer -= bytes_in_buffer * 8; - buffer >>= bytes_in_buffer * 8; - bytes_written += bytes_in_buffer; - } - - void ZeroPadToByte() { - if (bits_in_buffer != 0) { - Write(8 - bits_in_buffer, 0); - } - } - - std::unique_ptr data = {nullptr, free}; - size_t bytes_written = 0; - size_t bits_in_buffer = 0; - uint64_t buffer = 0; -}; - -constexpr size_t kLZ77Offset = 224; -constexpr size_t kLZ77MinLength = 16; - -struct PrefixCode { - static constexpr size_t kNumLZ77 = 17; - static constexpr size_t kNumRaw = 15; - - alignas(32) uint8_t raw_nbits[16] = {}; - alignas(32) uint8_t raw_bits[16] = {}; - uint8_t lz77_nbits[kNumLZ77] = {}; - - uint16_t lz77_bits[kNumLZ77] = {}; - - 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, - }; - uint16_t rev16 = (kNibbleLookup[bits & 0xF] << 12) | - (kNibbleLookup[(bits >> 4) & 0xF] << 8) | - (kNibbleLookup[(bits >> 8) & 0xF] << 4) | - (kNibbleLookup[bits >> 12]); - return rev16 >> (16 - nbits); - } - - // Create the prefix codes given the code lengths. - // Supports the code lengths being split into two halves. - static void ComputeCanonicalCode(const uint8_t* first_chunk_nbits, - uint8_t* first_chunk_bits, - size_t first_chunk_size, - const uint8_t* second_chunk_nbits, - uint16_t* second_chunk_bits, - size_t second_chunk_size) { - uint8_t code_length_counts[16] = {}; - for (size_t i = 0; i < first_chunk_size; i++) { - code_length_counts[first_chunk_nbits[i]]++; - assert(first_chunk_nbits[i] <= 7); - assert(first_chunk_nbits[i] > 0); - } - for (size_t i = 0; i < second_chunk_size; i++) { - code_length_counts[second_chunk_nbits[i]]++; - } - - uint16_t next_code[16] = {}; - - uint16_t code = 0; - for (size_t i = 1; i < 16; i++) { - code = (code + code_length_counts[i - 1]) << 1; - next_code[i] = code; - } - - for (size_t i = 0; i < first_chunk_size; i++) { - first_chunk_bits[i] = - BitReverse(first_chunk_nbits[i], next_code[first_chunk_nbits[i]]++); - } - for (size_t i = 0; i < second_chunk_size; i++) { - second_chunk_bits[i] = - BitReverse(second_chunk_nbits[i], next_code[second_chunk_nbits[i]]++); - } - } - - PrefixCode(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[kNumRaw + 1]; - memcpy(level1_counts, raw_counts, kNumRaw * sizeof(uint64_t)); - size_t numraw = kNumRaw; - 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]; - } - uint8_t level1_nbits[kNumRaw + 1] = {}; - ComputeCodeLengths(level1_counts, numraw + 1, 7, level1_nbits); - - uint8_t level2_nbits[kNumLZ77] = {}; - ComputeCodeLengths(lz77_counts, kNumLZ77, 15 - level1_nbits[numraw], - level2_nbits); - for (size_t i = 0; i < numraw; i++) { - raw_nbits[i] = level1_nbits[i]; - } - for (size_t i = 0; i < kNumLZ77; i++) { - lz77_nbits[i] = - level2_nbits[i] ? level1_nbits[numraw] + level2_nbits[i] : 0; - } - - ComputeCanonicalCode(raw_nbits, raw_bits, numraw, lz77_nbits, lz77_bits, - kNumLZ77); - } - - static void ComputeCodeLengths(uint64_t* freqs, size_t n, size_t limit, - uint8_t* nbits) { - if (n <= 1) return; - assert(n <= (1 << limit)); - assert(n <= 32); - int parent[64] = {}; - int height[64] = {}; - using QElem = std::pair; - std::priority_queue, std::greater> q; - // Standard Huffman code construction. On failure (i.e. if going beyond the - // length limit), try again with halved frequencies. - while (true) { - size_t num_nodes = 0; - for (size_t i = 0; i < n; i++) { - if (freqs[i] == 0) continue; - q.emplace(freqs[i], num_nodes++); - } - if (num_nodes <= 1) return; - while (q.size() > 1) { - QElem n1 = q.top(); - q.pop(); - QElem n2 = q.top(); - q.pop(); - size_t next = num_nodes++; - parent[n1.second] = next; - parent[n2.second] = next; - q.emplace(n1.first + n2.first, next); - } - assert(q.size() == 1); - q.pop(); - bool is_ok = true; - for (size_t i = num_nodes - 1; i-- > 0;) { - height[i] = height[parent[i]] + 1; - is_ok &= height[i] <= limit; - } - if (is_ok) { - num_nodes = 0; - for (size_t i = 0; i < n; i++) { - if (freqs[i] == 0) continue; - nbits[i] = height[num_nodes++]; - } - break; - } else { - for (size_t i = 0; i < n; i++) { - freqs[i] = (freqs[i] + 1) >> 1; - } - } - } - } - - 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 < kNumRaw; i++) { - code_length_counts[raw_nbits[i]]++; - } - for (size_t i = 0; i < kNumLZ77; i++) { - code_length_counts[lz77_nbits[i]]++; - } - uint8_t code_length_nbits[18] = {}; - ComputeCodeLengths(code_length_counts, 18, 5, code_length_nbits); - writer->Write(2, 0b00); // HSKIP = 0, i.e. don't skip code lengths. - - // As per Brotli RFC. - uint8_t code_length_order[18] = {1, 2, 3, 4, 0, 5, 17, 6, 16, - 7, 8, 9, 10, 11, 12, 13, 14, 15}; - uint8_t code_length_length_nbits[] = {2, 4, 3, 2, 2, 4}; - uint8_t code_length_length_bits[] = {0, 7, 3, 2, 1, 15}; - - // 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--; - } - 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]); - } - - // Compute the canonical codes for the codes that represent the lengths of - // 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. - for (size_t i = 0; i < kNumRaw; i++) { - writer->Write(code_length_nbits[raw_nbits[i]], - code_length_bits[raw_nbits[i]]); - } - size_t num_lz77 = kNumLZ77; - while (lz77_nbits[num_lz77 - 1] == 0) { - num_lz77--; - } - // Encode 0s until 224 (start of LZ77 symbols). This is in total 224-15 = - // 209. - static_assert(kLZ77Offset == 224, ""); - static_assert(kNumRaw == 15, ""); - 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, 0b110); // (27-2)*8 + 9 = 209 - // Encode LZ77 symbols, with values 224+i*16. - for (size_t i = 0; i < num_lz77; i++) { - writer->Write(code_length_nbits[lz77_nbits[i]], - code_length_bits[lz77_nbits[i]]); - if (i != num_lz77 - 1) { - // Encode gap between LZ77 symbols: 15 zeros. - writer->Write(code_length_nbits[17], code_length_bits[17]); - writer->Write(3, 0b000); // 3 - writer->Write(code_length_nbits[17], code_length_bits[17]); - writer->Write(3, 0b100); // (3-2)*8+7 = 15 - } - } - } -}; - -constexpr size_t kChunkSize = 16; - -void EncodeHybridUint000(uint32_t value, uint32_t* token, uint32_t* nbits, - uint32_t* bits) { - uint32_t n = 31 - __builtin_clz(value); - *token = value ? n + 1 : 0; - *nbits = value ? n : 0; - *bits = value ? value - (1 << n) : 0; -} - -void AppendWriter(BitWriter* dest, const BitWriter* src) { - if (dest->bits_in_buffer == 0) { - memcpy(dest->data.get() + dest->bytes_written, src->data.get(), - src->bytes_written); - dest->bytes_written += src->bytes_written; - } else { - size_t i = 0; - uint64_t buf = dest->buffer; - uint64_t bits_in_buffer = dest->bits_in_buffer; - uint8_t* dest_buf = dest->data.get() + dest->bytes_written; - // Copy 8 bytes at a time until we reach the border. - for (; i + 8 < src->bytes_written; i += 8) { - uint64_t chunk; - memcpy(&chunk, src->data.get() + i, 8); - uint64_t out = buf | (chunk << bits_in_buffer); - memcpy(dest_buf + i, &out, 8); - buf = chunk >> (64 - bits_in_buffer); - } - dest->buffer = buf; - dest->bytes_written += i; - for (; i < src->bytes_written; i++) { - dest->Write(8, src->data[i]); - } - } - dest->Write(src->bits_in_buffer, src->buffer); -} - -void AssembleFrame(size_t width, size_t height, size_t nb_chans, - size_t bitdepth, - const std::vector>& group_data, - BitWriter* output) { - size_t total_size_groups = 0; - std::vector group_sizes(group_data.size()); - for (size_t i = 0; i < group_data.size(); i++) { - size_t sz = 0; - for (size_t j = 0; j < nb_chans; j++) { - const auto& writer = group_data[i][j]; - sz += writer.bytes_written * 8 + writer.bits_in_buffer; - } - sz = (sz + 7) / 8; - group_sizes[i] = sz; - total_size_groups += sz * 8; - } - output->Allocate(1000 + group_data.size() * 32 + total_size_groups); - - // 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(height); - - // No special ratio. - output->Write(3, 0); - - wsz(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 (bitdepth == 8) { - output->Write(2, 0b00); // bit_depth.bits_per_sample = 8 - } else if (bitdepth == 10) { - output->Write(2, 0b01); // bit_depth.bits_per_sample = 10 - } else if (bitdepth == 12) { - output->Write(2, 0b10); // bit_depth.bits_per_sample = 12 - } else { - output->Write(2, 0b11); // 1 + u(6) - output->Write(6, bitdepth - 1); - } - output->Write(1, 1); // 16-bit-buffer sufficient - bool have_alpha = (nb_chans == 2 || nb_chans == 4); - 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 (nb_chans > 1) { - 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(); - - auto wsz_fh = [output](size_t size) { - if (size < (1 << 8)) { - output->Write(2, 0b00); - output->Write(8, size); - } else if (size - 256 < (1 << 11)) { - output->Write(2, 0b01); - output->Write(11, size - 256); - } else if (size - 2304 < (1 << 14)) { - output->Write(2, 0b10); - output->Write(14, size - 2304); - } else { - output->Write(2, 0b11); - output->Write(30, size - 18688); - } - }; - - // 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 - if (width % kChunkSize == 0) { - output->Write(1, 0); // no custom size or origin - } else { - output->Write(1, 1); // custom size - wsz_fh(0); // x0 = 0 - wsz_fh(0); // y0 = 0 - wsz_fh((width + kChunkSize - 1) / kChunkSize * - kChunkSize); // xsize rounded up to chunk size - wsz_fh(height); // ysize same - } - output->Write(2, 0b00); // kReplace blending mode - if (have_alpha) { - output->Write(2, 0b00); // kReplace blending mode for alpha channel - } - output->Write(1, 1); // 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 < 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. - - for (size_t i = 0; i < group_data.size(); i++) { - for (size_t j = 0; j < nb_chans; j++) { - AppendWriter(output, &group_data[i][j]); - } - output->ZeroPadToByte(); - } -} - -void PrepareDCGlobalCommon(bool is_single_group, size_t width, size_t height, - const PrefixCode& code, BitWriter* output) { - output->Allocate(100000 + (is_single_group ? width * height * 16 : 0)); - // No patches, spline or noise. - output->Write(1, 1); // default DC dequantization factors (?) - output->Write(1, 1); // use global tree / histograms - output->Write(1, 0); // no lz77 for the tree - - output->Write(1, 1); // simple code for the tree's context map - output->Write(2, 0); // all contexts clustered together - output->Write(1, 1); // use prefix code for tree - output->Write(4, 15); // don't do hybriduint for tree - 2 symbols anyway - output->Write(7, 0b0100101); // Alphabet size is 6: we need 0 and 5 (var16) - output->Write(2, 1); // simple prefix code - output->Write(2, 1); // with two symbols - output->Write(3, 0); // 0 - output->Write(3, 5); // 5 - output->Write(5, 0b00010); // tree repr: predictor is 5, all else 0 - - output->Write(1, 1); // Enable lz77 for the main bitstream - output->Write(2, 0b00); // lz77 offset 224 - static_assert(kLZ77Offset == 224, ""); - output->Write(10, 0b0000011111); // lz77 min length 16 - static_assert(kLZ77MinLength == 16, ""); - output->Write(4, 4); // 404 hybrid uint config for lz77: 4 - output->Write(3, 0); // 0 - output->Write(3, 4); // 4 - output->Write(1, 1); // simple code for the context map - output->Write(2, 1); // two clusters - output->Write(1, 1); // raw/lz77 length histogram last - output->Write(1, 0); // distance histogram first - output->Write(1, 1); // use prefix codes - output->Write(4, 0); // 000 hybrid uint config for distances (only need 0) - output->Write(4, 0); // 000 hybrid uint config for symbols (only <= 10) - // Distance alphabet size: - output->Write(5, 0b00001); // 2: just need 1 for RLE (i.e. distance 1) - // Symbol + LZ77 alphabet size: - output->Write(1, 1); // > 1 - output->Write(4, 8); // <= 512 - output->Write(8, 255); // == 512 - - // Distance histogram: - output->Write(2, 1); // simple prefix code - output->Write(2, 0); // with one symbol - output->Write(1, 1); // 1 - - // Symbol + lz77 histogram: - code.WriteTo(output); - - // Group header for global modular image. - output->Write(1, 1); // Global tree - output->Write(1, 1); // All default wp -} - -void PrepareDCGlobal(bool is_single_group, size_t width, size_t height, - size_t nb_chans, size_t bitdepth, const PrefixCode& code, - BitWriter* output) { - PrepareDCGlobalCommon(is_single_group, width, height, code, output); - if (nb_chans > 2) { - output->Write(2, 0b01); // 1 transform - output->Write(2, 0b00); // RCT - output->Write(5, 0b00000); // Starting from ch 0 - output->Write(2, 0b00); // YCoCg - } else { - output->Write(2, 0b00); // no transforms - } - if (!is_single_group) { - output->ZeroPadToByte(); - } -} - -void EncodeHybridUint404_Mul16(uint32_t value, uint32_t* token_div16, - uint32_t* nbits, uint32_t* bits) { - // NOTE: token in libjxl is actually << 4. - uint32_t n = 31 - __builtin_clz(value); - *token_div16 = value < 16 ? 0 : n - 3; - *nbits = value < 16 ? 0 : n - 4; - *bits = value < 16 ? 0 : (value >> 4) - (1 << *nbits); -} - -#ifdef FASTLL_ENABLE_AVX2_INTRINSICS -#include -void EncodeChunk(const uint16_t* residuals, const PrefixCode& prefix_code, - BitWriter& output) { - static_assert(kChunkSize == 16, "Chunk size must be 16"); - auto value = _mm256_load_si256((__m256i*)residuals); - - // we know that residuals[i] has at most 12 bits, so we just need 3 nibbles - // and don't need to mask the third. However we do need to set the high - // byte to 0xFF, which will make table lookups return 0. - auto lo_nibble = - _mm256_or_si256(_mm256_and_si256(value, _mm256_set1_epi16(0xF)), - _mm256_set1_epi16(0xFF00)); - auto mi_nibble = _mm256_or_si256( - _mm256_and_si256(_mm256_srli_epi16(value, 4), _mm256_set1_epi16(0xF)), - _mm256_set1_epi16(0xFF00)); - auto hi_nibble = - _mm256_or_si256(_mm256_srli_epi16(value, 8), _mm256_set1_epi16(0xFF00)); - - auto lo_lut = _mm256_broadcastsi128_si256( - _mm_setr_epi8(0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4)); - auto mi_lut = _mm256_broadcastsi128_si256( - _mm_setr_epi8(0, 5, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8)); - auto hi_lut = _mm256_broadcastsi128_si256(_mm_setr_epi8( - 0, 9, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12)); - - auto lo_token = _mm256_shuffle_epi8(lo_lut, lo_nibble); - auto mi_token = _mm256_shuffle_epi8(mi_lut, mi_nibble); - auto hi_token = _mm256_shuffle_epi8(hi_lut, hi_nibble); - - auto token = _mm256_max_epi16(lo_token, _mm256_max_epi16(mi_token, hi_token)); - auto nbits = _mm256_subs_epu16(token, _mm256_set1_epi16(1)); - - // Compute 1< 64 bit lanes. - auto nbits_hi32 = _mm256_srli_epi64(nbits, 32); - auto nbits_lo32 = _mm256_and_si256(nbits, _mm256_set1_epi64x(0xFFFFFFFF)); - auto bits_hi32 = _mm256_srli_epi64(bits, 32); - auto bits_lo32 = _mm256_and_si256(bits, _mm256_set1_epi64x(0xFFFFFFFF)); - - nbits = _mm256_add_epi64(nbits_hi32, nbits_lo32); - bits = _mm256_or_si256(_mm256_sllv_epi64(bits_hi32, nbits_lo32), bits_lo32); - - alignas(32) uint64_t nbits_simd[4] = {}; - alignas(32) uint64_t bits_simd[4] = {}; - - _mm256_store_si256((__m256i*)nbits_simd, nbits); - _mm256_store_si256((__m256i*)bits_simd, bits); - - // Manually merge the buffer bits with the SIMD bits. - // Necessary because Write() is only guaranteed to work with <=56 bits. - // Trying to SIMD-fy this code results in slower speed (and definitely less - // clarity). - { - for (size_t i = 0; i < 4; i++) { - output.buffer |= bits_simd[i] << output.bits_in_buffer; - memcpy(output.data.get() + output.bytes_written, &output.buffer, 8); - // If >> 64, next_buffer is unused. - uint64_t next_buffer = bits_simd[i] >> (64 - output.bits_in_buffer); - output.bits_in_buffer += nbits_simd[i]; - // This `if` seems to be faster than using ternaries. - if (output.bits_in_buffer >= 64) { - output.buffer = next_buffer; - output.bits_in_buffer -= 64; - output.bytes_written += 8; - } - } - memcpy(output.data.get() + output.bytes_written, &output.buffer, 8); - size_t bytes_in_buffer = output.bits_in_buffer / 8; - output.bits_in_buffer -= bytes_in_buffer * 8; - output.buffer >>= bytes_in_buffer * 8; - output.bytes_written += bytes_in_buffer; - } -} -#endif - -#ifdef FASTLL_ENABLE_NEON_INTRINSICS -#include - -void EncodeChunk(const uint16_t* residuals, const PrefixCode& code, - BitWriter& output) { - uint16x8_t res = vld1q_u16(residuals); - uint16x8_t token = vsubq_u16(vdupq_n_u16(16), vclzq_u16(res)); - uint16x8_t nbits = vqsubq_u16(token, vdupq_n_u16(1)); - uint16x8_t bits = vqsubq_u16(res, vshlq_s16(vdupq_n_s16(1), nbits)); - uint16x8_t huff_bits = - vandq_u16(vdupq_n_u16(0xFF), vqtbl1q_u8(vld1q_u8(code.raw_bits), token)); - uint16x8_t huff_nbits = - vandq_u16(vdupq_n_u16(0xFF), vqtbl1q_u8(vld1q_u8(code.raw_nbits), token)); - bits = vorrq_u16(vshlq_u16(bits, huff_nbits), huff_bits); - nbits = vaddq_u16(nbits, huff_nbits); - - // Merge nbits and bits from 16-bit to 32-bit lanes. - uint32x4_t nbits_lo16 = vandq_u32(nbits, vdupq_n_u32(0xFFFF)); - uint32x4_t bits_hi16 = vshlq_u32(vshrq_n_u32(bits, 16), nbits_lo16); - uint32x4_t bits_lo16 = vandq_u32(bits, vdupq_n_u32(0xFFFF)); - - uint32x4_t nbits32 = vsraq_n_u32(nbits_lo16, nbits, 16); - uint32x4_t bits32 = vorrq_u32(bits_hi16, bits_lo16); - - // Merging up to 64 bits is not faster. - - // Manually merge the buffer bits with the SIMD bits. - // A bit faster. - for (size_t i = 0; i < 4; i++) { - output.buffer |= bits32[i] << output.bits_in_buffer; - memcpy(output.data.get() + output.bytes_written, &output.buffer, 8); - output.bits_in_buffer += nbits32[i]; - size_t bytes_in_buffer = output.bits_in_buffer / 8; - output.bits_in_buffer -= bytes_in_buffer * 8; - output.buffer >>= bytes_in_buffer * 8; - output.bytes_written += bytes_in_buffer; - } -} -#endif - -template -struct ChunkEncoder { - static void EncodeRle(size_t count, const PrefixCode& code, - BitWriter& output) { - if (count == 0) return; - count -= kLZ77MinLength; - unsigned token_div16, nbits, bits; - EncodeHybridUint404_Mul16(count, &token_div16, &nbits, &bits); - output.Write( - code.lz77_nbits[token_div16] + nbits, - (bits << code.lz77_nbits[token_div16]) | code.lz77_bits[token_div16]); - } - - inline void Chunk(size_t run, uint16_t* residuals) { - EncodeRle(run, *code, *output); -#if defined(FASTLL_ENABLE_AVX2_INTRINSICS) && FASTLL_ENABLE_AVX2_INTRINSICS - if (bytedepth == 1) { - EncodeChunk(residuals, *code, *output); - return; - } -#elif defined(FASTLL_ENABLE_NEON_INTRINSICS) && FASTLL_ENABLE_NEON_INTRINSICS - if (bytedepth == 1) { - EncodeChunk(residuals, *code, *output); - if (kChunkSize > 8) { - EncodeChunk(residuals + 8, *code, *output); - } - return; - } -#endif - for (size_t ix = 0; ix < kChunkSize; ix++) { - unsigned token, nbits, bits; - EncodeHybridUint000(residuals[ix], &token, &nbits, &bits); - output->Write(code->raw_nbits[token] + nbits, - code->raw_bits[token] | bits << code->raw_nbits[token]); - } - } - - inline void Finalize(size_t run) { EncodeRle(run, *code, *output); } - - const PrefixCode* code; - BitWriter* output; -}; - -struct ChunkSampleCollector { - void Rle(size_t count, uint64_t* lz77_counts) { - if (count == 0) return; - count -= kLZ77MinLength; - unsigned token_div16, nbits, bits; - EncodeHybridUint404_Mul16(count, &token_div16, &nbits, &bits); - lz77_counts[token_div16]++; - } - - inline void Chunk(size_t run, uint16_t* residuals) { - // Run is broken. Encode the run and encode the individual vector. - Rle(run, lz77_counts); - for (size_t ix = 0; ix < kChunkSize; ix++) { - unsigned token, nbits, bits; - EncodeHybridUint000(residuals[ix], &token, &nbits, &bits); - raw_counts[token]++; - } - } - - // don't count final run since we don't know how long it really is - void Finalize(size_t run) {} - - uint64_t* raw_counts; - uint64_t* lz77_counts; -}; - -constexpr uint16_t PackSigned(int16_t value) { - return (static_cast(value) << 1) ^ - ((static_cast(~value) >> 15) - 1); -} - -template -struct ChannelRowProcessor { - T* t; - inline void ProcessChunk(const int16_t* row, const int16_t* row_left, - const int16_t* row_top, const int16_t* row_topleft) { - bool continue_rle = true; - alignas(32) uint16_t residuals[kChunkSize] = {}; - for (size_t ix = 0; ix < kChunkSize; ix++) { - int16_t px = row[ix]; - int16_t left = row_left[ix]; - int16_t top = row_top[ix]; - int16_t topleft = row_topleft[ix]; - int16_t ac = left - topleft; - int16_t ab = left - top; - int16_t bc = top - topleft; - int16_t grad = static_cast(static_cast(ac) + - static_cast(top)); - int16_t d = ab ^ bc; - int16_t clamp = d < 0 ? top : left; - int16_t s = ac ^ bc; - int16_t pred = s < 0 ? grad : clamp; - residuals[ix] = PackSigned(px - pred); - continue_rle &= residuals[ix] == last; - } - // Run continues, nothing to do. - if (continue_rle) { - run += kChunkSize; - } else { - // Run is broken. Encode the run and encode the individual vector. - t->Chunk(run, residuals); - run = 0; - } - last = residuals[kChunkSize - 1]; - } - void ProcessRow(const int16_t* row, const int16_t* row_left, - const int16_t* row_top, const int16_t* row_topleft, - size_t xs) { - for (size_t x = 0; x + kChunkSize <= xs; x += kChunkSize) { - ProcessChunk(row + x, row_left + x, row_top + x, row_topleft + x); - } - } - - void Finalize() { t->Finalize(run); } - size_t run = 0; - uint16_t last = 0xFFFF; // Can never appear -}; - -template -void ProcessImageArea(const unsigned char* rgba, size_t x0, size_t y0, - size_t oxs, size_t xs, size_t yskip, size_t ys, - size_t row_stride, Processor* processors) { - constexpr size_t kPadding = 16; - - int16_t group_data[nb_chans][2][256 + kPadding * 2] = {}; - int16_t allzero[nb_chans] = {}; - int16_t allone[nb_chans]; - auto get_pixel = [&](size_t x, size_t y, size_t channel) { - int16_t p = rgba[row_stride * (y0 + y) + (x0 + x) * nb_chans * bytedepth + - channel * bytedepth]; - if (bytedepth == 2) { - p <<= 8; - p |= rgba[row_stride * (y0 + y) + (x0 + x) * nb_chans * 2 + channel * 2 + - 1]; - } - return p; - }; - - for (size_t i = 0; i < nb_chans; i++) allone[i] = 0xffff; - for (size_t y = 0; y < ys; y++) { - // Pre-fill rows with YCoCg converted pixels. - for (size_t x = 0; x < oxs; x++) { - if (nb_chans < 3) { - int16_t luma = get_pixel(x, y, 0); - group_data[0][y & 1][x + kPadding] = luma; - if (nb_chans == 2) { - int16_t a = get_pixel(x, y, 1); - group_data[1][y & 1][x + kPadding] = a; - } - } else { - int16_t r = get_pixel(x, y, 0); - int16_t g = get_pixel(x, y, 1); - int16_t b = get_pixel(x, y, 2); - if (nb_chans == 4) { - int16_t a = get_pixel(x, y, 3); - group_data[3][y & 1][x + kPadding] = a; - group_data[1][y & 1][x + kPadding] = a ? r - b : 0; - int16_t tmp = b + (group_data[1][y & 1][x + kPadding] >> 1); - group_data[2][y & 1][x + kPadding] = a ? g - tmp : 0; - group_data[0][y & 1][x + kPadding] = - a ? tmp + (group_data[2][y & 1][x + kPadding] >> 1) : 0; - } else { - group_data[1][y & 1][x + kPadding] = r - b; - int16_t tmp = b + (group_data[1][y & 1][x + kPadding] >> 1); - group_data[2][y & 1][x + kPadding] = g - tmp; - group_data[0][y & 1][x + kPadding] = - tmp + (group_data[2][y & 1][x + kPadding] >> 1); - } - } - for (size_t c = 0; c < nb_chans; c++) { - allzero[c] |= group_data[c][y & 1][x + kPadding]; - allone[c] &= group_data[c][y & 1][x + kPadding]; - } - } - // Deal with x == 0. - for (size_t c = 0; c < nb_chans; c++) { - group_data[c][y & 1][kPadding - 1] = - y > 0 ? group_data[c][(y - 1) & 1][kPadding] : 0; - // Fix topleft. - group_data[c][(y - 1) & 1][kPadding - 1] = - y > 0 ? group_data[c][(y - 1) & 1][kPadding] : 0; - } - // Fill in padding. - for (size_t c = 0; c < nb_chans; c++) { - for (size_t x = oxs; x < xs; x++) { - group_data[c][y & 1][kPadding + x] = - group_data[c][y & 1][kPadding + oxs - 1]; - } - } - if (y < yskip) continue; - for (size_t c = 0; c < nb_chans; c++) { - if (y > 0 && (allzero[c] == 0 || (allone[c] == 0xff && bytedepth == 1))) { - processors[c].run += xs; - continue; - } - - // Get pointers to px/left/top/topleft data to speedup loop. - const int16_t* row = &group_data[c][y & 1][kPadding]; - const int16_t* row_left = &group_data[c][y & 1][kPadding - 1]; - const int16_t* row_top = - y == 0 ? row_left : &group_data[c][(y - 1) & 1][kPadding]; - const int16_t* row_topleft = - y == 0 ? row_left : &group_data[c][(y - 1) & 1][kPadding - 1]; - - processors[c].ProcessRow(row, row_left, row_top, row_topleft, xs); - } - } - for (size_t c = 0; c < nb_chans; c++) { - processors[c].Finalize(); - } -} - -template -void WriteACSection(const unsigned char* rgba, size_t x0, size_t y0, size_t oxs, - size_t ys, size_t row_stride, bool is_single_group, - const PrefixCode& code, std::array& output) { - size_t xs = (oxs + kChunkSize - 1) / kChunkSize * kChunkSize; - for (size_t i = 0; i < nb_chans; i++) { - if (is_single_group && i == 0) continue; - output[i].Allocate(16 * xs * ys * bytedepth + 4); - } - if (!is_single_group) { - // Group header for modular image. - // When the image is single-group, the global modular image is the one that - // contains the pixel data, and there is no group header. - output[0].Write(1, 1); // Global tree - output[0].Write(1, 1); // All default wp - output[0].Write(2, 0b00); // 0 transforms - } - - ChunkEncoder encoders[nb_chans]; - ChannelRowProcessor> row_encoders[nb_chans]; - for (size_t c = 0; c < nb_chans; c++) { - row_encoders[c].t = &encoders[c]; - encoders[c].output = &output[c]; - encoders[c].code = &code; - } - ProcessImageArea>, nb_chans, - bytedepth>(rgba, x0, y0, oxs, xs, 0, ys, row_stride, - row_encoders); -} - -constexpr int kHashExp = 16; -constexpr uint32_t kHashSize = 1 << kHashExp; -constexpr uint32_t kHashMultiplier = 2654435761; -constexpr int kMaxColors = 512; - -// can be any function that returns a value in 0 .. kHashSize-1 -// has to map 0 to 0 -inline uint32_t pixel_hash(uint32_t p) { - return (p * kHashMultiplier) >> (32 - kHashExp); -} - -template -void ProcessImageAreaPalette(const unsigned char* rgba, size_t x0, size_t y0, - size_t oxs, size_t xs, size_t yskip, size_t ys, - size_t row_stride, const int16_t* lookup, - Processor* processors) { - constexpr size_t kPadding = 16; - - int16_t group_data[2][256 + kPadding * 2] = {}; - Processor& row_encoder = processors[0]; - - for (size_t y = 0; y < ys; y++) { - // Pre-fill rows with palette converted pixels. - const unsigned char* inrow = rgba + row_stride * (y0 + y) + x0 * nb_chans; - for (size_t x = 0; x < oxs; x++) { - uint32_t p = 0; - memcpy(&p, inrow + x * nb_chans, nb_chans); - group_data[y & 1][x + kPadding] = lookup[pixel_hash(p)]; - } - // Deal with x == 0. - group_data[y & 1][kPadding - 1] = - y > 0 ? group_data[(y - 1) & 1][kPadding] : 0; - // Fix topleft. - group_data[(y - 1) & 1][kPadding - 1] = - y > 0 ? group_data[(y - 1) & 1][kPadding] : 0; - // Fill in padding. - for (size_t x = oxs; x < xs; x++) { - group_data[y & 1][kPadding + x] = group_data[y & 1][kPadding + oxs - 1]; - } - // Get pointers to px/left/top/topleft data to speedup loop. - const int16_t* row = &group_data[y & 1][kPadding]; - const int16_t* row_left = &group_data[y & 1][kPadding - 1]; - const int16_t* row_top = - y == 0 ? row_left : &group_data[(y - 1) & 1][kPadding]; - const int16_t* row_topleft = - y == 0 ? row_left : &group_data[(y - 1) & 1][kPadding - 1]; - - row_encoder.ProcessRow(row, row_left, row_top, row_topleft, xs); - } - row_encoder.Finalize(); -} - -template -void WriteACSectionPalette(const unsigned char* rgba, size_t x0, size_t y0, - size_t oxs, size_t ys, size_t row_stride, - bool is_single_group, const PrefixCode& code, - const int16_t* lookup, BitWriter& output) { - size_t xs = (oxs + kChunkSize - 1) / kChunkSize * kChunkSize; - - if (!is_single_group) { - output.Allocate(16 * xs * ys + 4); - // Group header for modular image. - // When the image is single-group, the global modular image is the one that - // contains the pixel data, and there is no group header. - output.Write(1, 1); // Global tree - output.Write(1, 1); // All default wp - output.Write(2, 0b00); // 0 transforms - } - - ChunkEncoder<1> encoder; - ChannelRowProcessor> row_encoder; - - row_encoder.t = &encoder; - encoder.output = &output; - encoder.code = &code; - ProcessImageAreaPalette>, nb_chans>( - rgba, x0, y0, oxs, xs, 0, ys, row_stride, lookup, &row_encoder); -} - -template -void CollectSamples(const unsigned char* rgba, size_t x0, size_t y0, size_t xs, - size_t row_stride, size_t row_count, uint64_t* raw_counts, - uint64_t* lz77_counts, bool palette, - const int16_t* lookup) { - ChunkSampleCollector sample_collectors[nb_chans]; - ChannelRowProcessor row_sample_collectors[nb_chans]; - for (size_t c = 0; c < nb_chans; c++) { - row_sample_collectors[c].t = &sample_collectors[c]; - sample_collectors[c].raw_counts = raw_counts; - sample_collectors[c].lz77_counts = lz77_counts; - } - if (palette) { - assert(bytedepth == 1); - ProcessImageAreaPalette, - nb_chans>(rgba, x0, y0, xs, xs, 1, 1 + row_count, - row_stride, lookup, - row_sample_collectors); - } else { - ProcessImageArea, nb_chans, - bytedepth>(rgba, x0, y0, xs, xs, 1, 1 + row_count, - row_stride, row_sample_collectors); - } -} - -void PrepareDCGlobalPalette(bool is_single_group, size_t width, size_t height, - const PrefixCode& code, - const std::vector& palette, - size_t pcolors_real, BitWriter* output) { - PrepareDCGlobalCommon(is_single_group, width, height, code, output); - output->Write(2, 0b01); // 1 transform - output->Write(2, 0b01); // Palette - output->Write(5, 0b00000); // Starting from ch 0 - output->Write(2, 0b10); // 4-channel palette (RGBA) - size_t pcolors = (pcolors_real + kChunkSize - 1) / kChunkSize * kChunkSize; - // pcolors <= kMaxColors + kChunkSize - 1 - static_assert(kMaxColors + kChunkSize < 1281, - "add code to signal larger palette sizes"); - if (pcolors < 256) { - output->Write(2, 0b00); - output->Write(8, pcolors); - } else { - output->Write(2, 0b01); - output->Write(10, pcolors - 256); - } - - output->Write(2, 0b00); // nb_deltas == 0 - output->Write(4, 0); // Zero predictor for delta palette - // Encode palette - ChunkEncoder<1> encoder; - ChannelRowProcessor> row_encoder; - row_encoder.t = &encoder; - encoder.output = output; - encoder.code = &code; - int16_t p[4][32 + 1024] = {}; - uint8_t prgba[4]; - int i = 0; - int have_zero = 0; - if (palette[pcolors_real - 1] == 0) have_zero = 1; - for (; i < pcolors; i++) { - if (i < pcolors_real) { - memcpy(prgba, &palette[i], 4); - } - p[0][16 + i + have_zero] = prgba[0]; - p[1][16 + i + have_zero] = prgba[1]; - p[2][16 + i + have_zero] = prgba[2]; - p[3][16 + i + have_zero] = prgba[3]; - } - p[0][15] = 0; - row_encoder.ProcessRow(p[0] + 16, p[0] + 15, p[0] + 15, p[0] + 15, pcolors); - p[1][15] = p[0][16]; - p[0][15] = p[0][16]; - row_encoder.ProcessRow(p[1] + 16, p[1] + 15, p[0] + 16, p[0] + 15, pcolors); - p[2][15] = p[1][16]; - p[1][15] = p[1][16]; - row_encoder.ProcessRow(p[2] + 16, p[2] + 15, p[1] + 16, p[1] + 15, pcolors); - p[3][15] = p[2][16]; - p[2][15] = p[2][16]; - row_encoder.ProcessRow(p[3] + 16, p[3] + 15, p[2] + 16, p[2] + 15, pcolors); - row_encoder.Finalize(); - - if (!is_single_group) { - output->ZeroPadToByte(); - } -} - -template -size_t LLEnc(const unsigned char* rgba, size_t width, size_t stride, - size_t height, size_t bitdepth, int effort, - unsigned char** output) { - size_t bytes_per_sample = (bitdepth > 8 ? 2 : 1); - assert(bytedepth == bytes_per_sample); - assert(width != 0); - assert(height != 0); - assert(stride >= nb_chans * bytes_per_sample * width); - (void)bytes_per_sample; - - // Count colors to try palette - std::vector palette(kHashSize); - palette[0] = 1; - int16_t lookup[kHashSize]; - lookup[0] = 0; - int pcolors = 0; - bool collided = - effort < 2 || bitdepth != 8 || nb_chans < 4; // todo: also do rgb palette - for (size_t y = 0; y < height && !collided; y++) { - const unsigned char* r = rgba + stride * y; - size_t x = 0; - if (nb_chans == 4) { - // this is just an unrolling of the next loop - for (; x + 7 < width; x += 8) { - uint32_t p[8], index[8]; - memcpy(p, r + x * 4, 32); - for (int i = 0; i < 8; i++) index[i] = pixel_hash(p[i]); - for (int i = 0; i < 8; i++) { - uint32_t init_entry = index[i] ? 0 : 1; - if (init_entry != palette[index[i]] && p[i] != palette[index[i]]) { - collided = true; - } - } - for (int i = 0; i < 8; i++) palette[index[i]] = p[i]; - } - for (; x < width; x++) { - uint32_t p; - memcpy(&p, r + x * 4, 4); - uint32_t index = pixel_hash(p); - uint32_t init_entry = index ? 0 : 1; - if (init_entry != palette[index] && p != palette[index]) { - collided = true; - } - palette[index] = p; - } - } else { - for (; x < width; x++) { - uint32_t p = 0; - memcpy(&p, r + x * nb_chans, nb_chans); - uint32_t index = pixel_hash(p); - uint32_t init_entry = index ? 0 : 1; - if (init_entry != palette[index] && p != palette[index]) { - collided = true; - } - palette[index] = p; - } - } - } - - int nb_entries = 0; - if (!collided) { - if (palette[0] == 0) pcolors = 1; - if (palette[0] == 1) palette[0] = 0; - bool have_color = false; - uint8_t minG = 255, maxG = 0; - for (int k = 0; k < kHashSize; k++) { - if (palette[k] == 0) continue; - uint8_t p[4]; - memcpy(p, &palette[k], 4); - // move entries to front so sort has less work - palette[nb_entries] = palette[k]; - if (p[0] != p[1] || p[0] != p[2]) have_color = true; - if (p[1] < minG) minG = p[1]; - if (p[1] > maxG) maxG = p[1]; - nb_entries++; - // don't do palette if too many colors are needed - if (nb_entries + pcolors > kMaxColors) { - collided = true; - break; - } - } - if (!have_color) { - // don't do palette if it's just grayscale without many holes - if (maxG - minG < nb_entries * 1.4f) collided = true; - } - } - if (!collided) { - std::sort( - palette.begin(), palette.begin() + nb_entries, - [](uint32_t ap, uint32_t bp) { - if (ap == 0) return false; - if (bp == 0) return true; - uint8_t a[4], b[4]; - memcpy(a, &ap, 4); - memcpy(b, &bp, 4); - float ay, by; - ay = (0.299f * a[0] + 0.587f * a[1] + 0.114f * a[2] + 0.01f) * a[3]; - by = (0.299f * b[0] + 0.587f * b[1] + 0.114f * b[2] + 0.01f) * b[3]; - return ay < by; // sort on alpha*luma - }); - for (int k = 0; k < nb_entries; k++) { - if (palette[k] == 0) break; - lookup[pixel_hash(palette[k])] = pcolors++; - } - } - - // Width gets padded to kChunkSize, but this computation doesn't change - // because of that. - size_t num_groups_x = (width + 255) / 256; - size_t num_groups_y = (height + 255) / 256; - size_t num_dc_groups_x = (width + 2047) / 2048; - size_t num_dc_groups_y = (height + 2047) / 2048; - - uint64_t raw_counts[16] = {}; - uint64_t lz77_counts[17] = {}; - - // sample the middle (effort * 2) rows of every group - 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, lz77_counts, - !collided, lookup); - } - - uint64_t base_raw_counts[16] = {3843, 852, 1270, 1214, 1014, 727, 481, 300, - 159, 51, 5, 1, 1, 1, 1, 1}; - - bool doing_ycocg = nb_chans > 2 && collided; - for (size_t i = bitdepth + 2 + (doing_ycocg ? 1 : 0); i < 16; i++) { - base_raw_counts[i] = 0; - } - uint64_t base_lz77_counts[17] = { - // short runs will be sampled, but long ones won't. - // near full-group run is quite common (e.g. all-opaque alpha) - 18, 12, 9, 11, 15, 2, 2, 1, 1, 1, 1, 2, 300, 0, 0, 0, 0}; - - for (size_t i = 0; i < 16; i++) { - raw_counts[i] = (raw_counts[i] << 8) + base_raw_counts[i]; - } - if (!collided) { - unsigned token, nbits, bits; - EncodeHybridUint000(PackSigned(pcolors - 1), &token, &nbits, &bits); - // ensure all palette indices can actually be encoded - for (size_t i = 0; i < token + 1; i++) - raw_counts[i] = std::max(raw_counts[i], 1); - // these tokens are only used for the palette itself so they can get a bad - // code - for (size_t i = token + 1; i < 10; i++) raw_counts[i] = 1; - } - for (size_t i = 0; i < 17; i++) { - lz77_counts[i] = (lz77_counts[i] << 8) + base_lz77_counts[i]; - } - alignas(32) PrefixCode hcode(raw_counts, lz77_counts); - - BitWriter writer; - - bool onegroup = num_groups_x == 1 && num_groups_y == 1; - - size_t num_groups = onegroup ? 1 - : (2 + num_dc_groups_x * num_dc_groups_y + - num_groups_x * num_groups_y); - - std::vector> group_data(num_groups); - if (collided) { - PrepareDCGlobal(onegroup, width, height, nb_chans, bitdepth, hcode, - &group_data[0][0]); - } else { - PrepareDCGlobalPalette(onegroup, width, height, hcode, palette, pcolors, - &group_data[0][0]); - } -#pragma omp parallel for - 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; - 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 = group_data[group_id]; - if (collided) { - WriteACSection(rgba, x0, y0, xs, ys, stride, - onegroup, hcode, gd); - - } else { - WriteACSectionPalette(rgba, x0, y0, xs, ys, stride, onegroup, - hcode, lookup, gd[0]); - } - } - - AssembleFrame(width, height, nb_chans, bitdepth, group_data, &writer); - - *output = writer.data.release(); - return writer.bytes_written; -} - -size_t FastLosslessEncode(const unsigned char* rgba, size_t width, - size_t stride, size_t height, size_t nb_chans, - size_t bitdepth, int effort, unsigned char** output) { - assert(bitdepth <= 12); - assert(bitdepth > 0); - assert(nb_chans <= 4); - assert(nb_chans != 0); - if (bitdepth <= 8) { - if (nb_chans == 1) { - return LLEnc<1, 1>(rgba, width, stride, height, bitdepth, effort, output); - } - if (nb_chans == 2) { - return LLEnc<2, 1>(rgba, width, stride, height, bitdepth, effort, output); - } - if (nb_chans == 3) { - return LLEnc<3, 1>(rgba, width, stride, height, bitdepth, effort, output); - } - if (nb_chans == 4) { - return LLEnc<4, 1>(rgba, width, stride, height, bitdepth, effort, output); - } - } else { - if (nb_chans == 1) { - return LLEnc<1, 2>(rgba, width, stride, height, bitdepth, effort, output); - } - if (nb_chans == 2) { - return LLEnc<2, 2>(rgba, width, stride, height, bitdepth, effort, output); - } - if (nb_chans == 3) { - return LLEnc<3, 2>(rgba, width, stride, height, bitdepth, effort, output); - } - if (nb_chans == 4) { - return LLEnc<4, 2>(rgba, width, stride, height, bitdepth, effort, output); - } - } - return 0; -} diff --git a/media/libjxl/src/experimental/fast_lossless/fast_lossless.h b/media/libjxl/src/experimental/fast_lossless/fast_lossless.h deleted file mode 100644 index f7940e53fe..0000000000 --- a/media/libjxl/src/experimental/fast_lossless/fast_lossless.h +++ /dev/null @@ -1,14 +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 FAST_LOSSLESS_H -#define FAST_LOSSLESS_H -#include - -size_t FastLosslessEncode(const unsigned char* rgba, size_t width, - size_t row_stride, size_t height, size_t nb_chans, - size_t bitdepth, int effort, unsigned char** output); - -#endif diff --git a/media/libjxl/src/flake.lock b/media/libjxl/src/flake.lock new file mode 100644 index 0000000000..3bfd004b14 --- /dev/null +++ b/media/libjxl/src/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1702312524, + "narHash": "sha256-gkZJRDBUCpTPBvQk25G0B7vfbpEYM5s5OZqghkjZsnE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a9bf124c46ef298113270b1f84a164865987a91c", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/media/libjxl/src/flake.nix b/media/libjxl/src/flake.nix new file mode 100644 index 0000000000..64a36311ab --- /dev/null +++ b/media/libjxl/src/flake.nix @@ -0,0 +1,38 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem + (system: + let + pkgs = import nixpkgs { + inherit system; + }; + in + with pkgs; + { + devShells.default = mkShell { + buildInputs = [ + clang + cmake + pkg-config + gtest + doxygen + graphviz + python3 + libclang.python + libpng + giflib + lcms2 + brotli + ]; + shellHook = '' + export CC=clang + export CXX=clang++ + ''; + }; + } + ); +} diff --git a/media/libjxl/src/lib/BUILD b/media/libjxl/src/lib/BUILD new file mode 100644 index 0000000000..3d48919f77 --- /dev/null +++ b/media/libjxl/src/lib/BUILD @@ -0,0 +1,314 @@ +# 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. + +load("@bazel_skylib//rules:copy_file.bzl", "copy_file") +load("@bazel_skylib//rules:expand_template.bzl", "expand_template") + +# 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", + "libjxl_codec_jxl_sources", + "libjxl_codec_npy_sources", + "libjxl_codec_pgx_sources", + "libjxl_codec_pnm_sources", + "libjxl_dec_box_sources", + "libjxl_dec_jpeg_sources", + "libjxl_dec_sources", + "libjxl_enc_sources", + "libjxl_extras_for_tools_sources", + "libjxl_extras_sources", + # "libjxl_gbench_sources", + # "libjxl_jpegli_lib_version", + "libjxl_jpegli_libjpeg_helper_files", + "libjxl_jpegli_sources", + "libjxl_jpegli_testlib_files", + "libjxl_jpegli_tests", + "libjxl_major_version", + "libjxl_minor_version", + "libjxl_patch_version", + "libjxl_public_headers", + "libjxl_testlib_files", + "libjxl_tests", + "libjxl_threads_public_headers", + "libjxl_threads_sources", +) +load( + "jxl_vars.bzl", + "libjxl_deps_brotli", + "libjxl_deps_exr", + "libjxl_deps_gif", + "libjxl_deps_gtest", + "libjxl_deps_hwy", + "libjxl_deps_hwy_nanobenchmark", + "libjxl_deps_hwy_test_util", + "libjxl_deps_jpeg", + "libjxl_deps_png", + "libjxl_deps_runfiles", + "libjxl_deps_skcms", + # "libjxl_deps_testdata", + # "libjxl_deps_webp", + "libjxl_root_package", + "libjxl_test_shards", + "libjxl_test_timeouts", +) + +DEFAULT_VISIBILITY = ["//:__subpackages__"] + +DEFAULT_COMPATIBILITY = [] + +INCLUDES_DIR = "include" + +package( + default_visibility = DEFAULT_VISIBILITY, +) + +licenses(["notice"]) + +exports_files(["LICENSE"]) + +EXPORT_TEMPLATE = """ +#ifndef @_EXPORT_H +#define @_EXPORT_H + +#define @_EXPORT +#define @_NO_EXPORT + +#ifndef @_DEPRECATED +# define @_DEPRECATED __attribute__ ((__deprecated__)) +#endif + +#endif +""" + +JXL_EXPORT_H = INCLUDES_DIR + "/jxl/jxl_export.h" + +genrule( + name = "create_jxl_export", + 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], + cmd = "echo '" + EXPORT_TEMPLATE.replace("@", "JXL_THREADS") + "' > $@", + compatible_with = DEFAULT_COMPATIBILITY, +) + +JXL_VERSION_H = INCLUDES_DIR + "/jxl/version.h" + +expand_template( + name = "expand_jxl_version", + out = JXL_VERSION_H, + compatible_with = DEFAULT_COMPATIBILITY, + substitutions = { + "@JPEGXL_MAJOR_VERSION@": str(libjxl_major_version), + "@JPEGXL_MINOR_VERSION@": str(libjxl_minor_version), + "@JPEGXL_PATCH_VERSION@": str(libjxl_patch_version), + }, + template = "jxl/version.h.in", +) + +cc_library( + name = "jxl_version", + hdrs = [JXL_VERSION_H], + compatible_with = DEFAULT_COMPATIBILITY, + strip_include_prefix = INCLUDES_DIR, +) + +JPEGLI_JCONFIG_H = INCLUDES_DIR + "/jpegli/jconfig.h" + +JPEGLI_JMORECFG_H = INCLUDES_DIR + "/jpegli/jmorecfg.h" + +JPEGLI_JPEGLIB_H = INCLUDES_DIR + "/jpegli/jpeglib.h" + +copy_file( + name = "expand_jconfig", + src = "@libjpeg_turbo//:jconfig.h", + out = JPEGLI_JCONFIG_H, + compatible_with = DEFAULT_COMPATIBILITY, +) + +copy_file( + name = "copy_jmorecfg", + src = "@libjpeg_turbo//:jmorecfg.h", + out = JPEGLI_JMORECFG_H, + compatible_with = DEFAULT_COMPATIBILITY, +) + +copy_file( + name = "copy_jpeglib", + src = "@libjpeg_turbo//:jpeglib.h", + out = JPEGLI_JPEGLIB_H, + compatible_with = DEFAULT_COMPATIBILITY, +) + +cc_library( + name = "includes", + hdrs = libjxl_public_headers + [ + JXL_EXPORT_H, + JXL_CMS_EXPORT_H, + ], + compatible_with = DEFAULT_COMPATIBILITY, + strip_include_prefix = INCLUDES_DIR, + deps = [":jxl_version"], +) + +cc_library( + name = "libjpeg_includes", + hdrs = [ + JPEGLI_JCONFIG_H, + JPEGLI_JMORECFG_H, + JPEGLI_JPEGLIB_H, + ], + compatible_with = DEFAULT_COMPATIBILITY, + strip_include_prefix = INCLUDES_DIR + "/jpegli", +) + +cc_library( + name = "base", + srcs = [path for path in libjxl_base_sources if path.endswith(".cc")], + hdrs = [path for path in libjxl_base_sources if path.endswith(".h")], + compatible_with = DEFAULT_COMPATIBILITY, + deps = [ + ":includes", + ] + libjxl_deps_hwy, +) + +cc_library( + name = "jpegxl", + 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", + ], + 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 + libjxl_cms_sources + if path.endswith(".h") and not path.endswith("-inl.h") + ], + compatible_with = DEFAULT_COMPATIBILITY, + deps = [":jpegxl"], +) + +cc_library( + name = "jpegxl_threads", + srcs = libjxl_threads_sources, + hdrs = libjxl_threads_public_headers + [JXL_THREADS_EXPORT_H], + compatible_with = DEFAULT_COMPATIBILITY, + strip_include_prefix = INCLUDES_DIR, + deps = [ + ":base", + ":includes", + ], +) + +CODEC_FILES = libjxl_codec_apng_sources + libjxl_codec_exr_sources + libjxl_codec_gif_sources + libjxl_codec_jpegli_sources + libjxl_codec_jpg_sources + libjxl_codec_jxl_sources + libjxl_codec_npy_sources + libjxl_codec_pgx_sources + libjxl_codec_pnm_sources + +CODEC_SRCS = [path for path in CODEC_FILES if path.endswith(".cc")] + +CODEC_HDRS = [path for path in CODEC_FILES if path.endswith(".h")] + +cc_library( + name = "jpegli", + srcs = libjxl_jpegli_sources, + hdrs = [ + "jpegli/common_internal.h", # TODO(eustas): should not be here + ], + compatible_with = DEFAULT_COMPATIBILITY, + deps = [ + ":jpegxl_private", + ":libjpeg_includes", + ] + libjxl_deps_hwy, +) + +# TODO(eustas): build codecs separately? +cc_library( + name = "jpegxl_extras", + srcs = libjxl_extras_sources + libjxl_extras_for_tools_sources + CODEC_SRCS, + hdrs = CODEC_HDRS, + compatible_with = DEFAULT_COMPATIBILITY, + defines = [ + "JPEGXL_ENABLE_APNG=1", + "JPEGXL_ENABLE_EXR=1", + "JPEGXL_ENABLE_GIF=1", + "JPEGXL_ENABLE_JPEG=1", + "JPEGXL_ENABLE_JPEGLI=1", + ], + deps = [ + ":jpegli", + ":jpegxl_private", + ":jpegxl_threads", + ":jxl_version", + ] + libjxl_deps_exr + libjxl_deps_gif + libjxl_deps_jpeg + libjxl_deps_png, +) + +TESTLIB_FILES = libjxl_testlib_files + libjxl_jpegli_testlib_files + libjxl_jpegli_libjpeg_helper_files + +cc_library( + name = "test_utils", + testonly = 1, + srcs = [path for path in TESTLIB_FILES if not path.endswith(".h")], + hdrs = [path for path in TESTLIB_FILES if path.endswith(".h")], + compatible_with = DEFAULT_COMPATIBILITY, + defines = [ + 'JPEGXL_ROOT_PACKAGE=\'"' + libjxl_root_package + '"\'', + ], + deps = [ + ":jpegli", + ":jpegxl_extras", + ":jpegxl_private", + ] + libjxl_deps_runfiles, +) + +TESTS = [path.partition(".")[0] for path in libjxl_tests + libjxl_jpegli_tests] + +[ + cc_test( + name = test, + timeout = libjxl_test_timeouts.get(test, "moderate"), + srcs = [ + test + ".cc", + "jpegli/testing.h", + "jxl/testing.h", + ], + data = ["//:testdata"], + shard_count = libjxl_test_shards.get(test, 1), + deps = [ + ":jpegxl_extras", + ":jpegxl_private", + ":jpegxl_threads", + ":test_utils", + ] + libjxl_deps_gtest + libjxl_deps_hwy_test_util + libjxl_deps_hwy_nanobenchmark, + ) + for test in TESTS +] diff --git a/media/libjxl/src/lib/CMakeLists.txt b/media/libjxl/src/lib/CMakeLists.txt index 5c8e0bada5..8973ea6ece 100644 --- a/media/libjxl/src/lib/CMakeLists.txt +++ b/media/libjxl/src/lib/CMakeLists.txt @@ -4,8 +4,8 @@ # license that can be found in the LICENSE file. set(JPEGXL_MAJOR_VERSION 0) -set(JPEGXL_MINOR_VERSION 7) -set(JPEGXL_PATCH_VERSION 0) +set(JPEGXL_MINOR_VERSION 11) +set(JPEGXL_PATCH_VERSION 1) set(JPEGXL_LIBRARY_VERSION "${JPEGXL_MAJOR_VERSION}.${JPEGXL_MINOR_VERSION}.${JPEGXL_PATCH_VERSION}") @@ -15,152 +15,179 @@ set(JPEGXL_LIBRARY_VERSION # 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 7) +set(JPEGXL_SO_MINOR_VERSION 11) if (JPEGXL_MAJOR_VERSION EQUAL 0) -set(JPEGXL_LIBRARY_SOVERSION - "${JPEGXL_MAJOR_VERSION}.${JPEGXL_SO_MINOR_VERSION}") + set(JPEGXL_LIBRARY_SOVERSION + "${JPEGXL_MAJOR_VERSION}.${JPEGXL_SO_MINOR_VERSION}") else() -set(JPEGXL_LIBRARY_SOVERSION "${JPEGXL_MAJOR_VERSION}") + set(JPEGXL_LIBRARY_SOVERSION "${JPEGXL_MAJOR_VERSION}") endif() # List of warning and feature flags for our library and tests. if (MSVC) -set(JPEGXL_INTERNAL_FLAGS - # TODO(janwas): add flags -) + set(JPEGXL_INTERNAL_FLAGS + # TODO(janwas): add flags + ) else () -set(JPEGXL_INTERNAL_FLAGS - # F_FLAGS - -fmerge-all-constants - -fno-builtin-fwrite - -fno-builtin-fread + set(JPEGXL_INTERNAL_FLAGS + # F_FLAGS + -fmerge-all-constants + -fno-builtin-fwrite + -fno-builtin-fread - # WARN_FLAGS - -Wall - -Wextra - -Wc++11-compat - -Warray-bounds - -Wformat-security - -Wimplicit-fallthrough - -Wno-register # Needed by public headers in lcms - -Wno-unused-function - -Wno-unused-parameter - -Wnon-virtual-dtor - -Woverloaded-virtual - -Wvla -) - -# Warning flags supported by clang. -if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - list(APPEND JPEGXL_INTERNAL_FLAGS - -Wdeprecated-increment-bool - # TODO(deymo): Add -Wextra-semi once we update third_party/highway. - # -Wextra-semi - -Wfloat-overflow-conversion - -Wfloat-zero-conversion - -Wfor-loop-analysis - -Wgnu-redeclared-enum - -Winfinite-recursion - -Wliteral-conversion - -Wno-c++98-compat - -Wno-unused-command-line-argument - -Wprivate-header - -Wself-assign - -Wstring-conversion - -Wtautological-overlap-compare - -Wthread-safety-analysis - -Wundefined-func-template - -Wunreachable-code - -Wunused-comparison - ) - if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 5.0) - list(APPEND HWY_FLAGS -Wc++2a-extensions) - endif() -endif() # Clang - -if (WIN32) - list(APPEND JPEGXL_INTERNAL_FLAGS - -Wno-cast-align - -Wno-double-promotion - -Wno-float-equal - -Wno-format-nonliteral - -Wno-shadow - -Wno-sign-conversion - -Wno-zero-as-null-pointer-constant + # WARN_FLAGS + -Wall + -Wextra + -Wc++11-compat + -Warray-bounds + -Wformat-security + -Wimplicit-fallthrough + -Wno-register # Needed by public headers in lcms + -Wno-unused-function + -Wno-unused-parameter + -Wnon-virtual-dtor + -Woverloaded-virtual + -Wvla ) + # Warning flags supported by clang. if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") list(APPEND JPEGXL_INTERNAL_FLAGS - -Wno-used-but-marked-unused - -Wno-unused-template - -Wno-unused-member-function - -Wno-shadow-field-in-constructor - -Wno-language-extension-token - -Wno-global-constructors - -Wno-c++98-compat-pedantic + -Wdeprecated-increment-bool + # TODO(deymo): Add -Wextra-semi once we update third_party/highway. + # -Wextra-semi + -Wfloat-overflow-conversion + -Wfloat-zero-conversion + -Wfor-loop-analysis + -Wgnu-redeclared-enum + -Winfinite-recursion + -Wliteral-conversion + -Wno-c++98-compat + -Wno-unused-command-line-argument + -Wprivate-header + -Wself-assign + -Wstring-conversion + -Wtautological-overlap-compare + -Wthread-safety-analysis + -Wundefined-func-template + -Wunreachable-code + -Wunused-comparison ) + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 5.0) + list(APPEND HWY_FLAGS -Wc++2a-extensions) + endif() endif() # Clang -else() # WIN32 - list(APPEND JPEGXL_INTERNAL_FLAGS - -fsized-deallocation - -fno-exceptions - # Language flags - -fmath-errno - ) - - if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + if (WIN32) list(APPEND JPEGXL_INTERNAL_FLAGS - -fnew-alignment=8 - -fno-cxx-exceptions - -fno-slp-vectorize - -fno-vectorize - - -disable-free - -disable-llvm-verifier + -Wno-cast-align + -Wno-double-promotion + -Wno-float-equal + -Wno-format-nonliteral + -Wno-shadow + -Wno-sign-conversion + -Wno-zero-as-null-pointer-constant ) - endif() # Clang -endif() # WIN32 -# Internal flags for coverage builds: -if(JPEGXL_ENABLE_COVERAGE) -set(JPEGXL_COVERAGE_FLAGS - -g -O0 -fprofile-arcs -ftest-coverage - -DJXL_ENABLE_ASSERT=0 -DJXL_ENABLE_CHECK=0 -) -endif() # JPEGXL_ENABLE_COVERAGE + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + list(APPEND JPEGXL_INTERNAL_FLAGS + -Wno-used-but-marked-unused + -Wno-unused-template + -Wno-unused-member-function + -Wno-shadow-field-in-constructor + -Wno-language-extension-token + -Wno-global-constructors + -Wno-c++98-compat-pedantic + ) + endif() # Clang + else() # WIN32 + list(APPEND JPEGXL_INTERNAL_FLAGS + -fsized-deallocation + -fno-exceptions + + # Language flags + -fmath-errno + ) + + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + list(APPEND JPEGXL_INTERNAL_FLAGS + -fnew-alignment=8 + -fno-cxx-exceptions + -fno-slp-vectorize + -fno-vectorize + + -disable-free + -disable-llvm-verifier + ) + endif() # Clang + endif() # WIN32 endif() #!MSVC +if (JPEGXL_ENABLE_SKCMS) + list(APPEND JPEGXL_INTERNAL_FLAGS -DJPEGXL_ENABLE_SKCMS=1) +endif () + +# strips the -internal suffix from all the elements in LIST +function(strip_internal OUTPUT_VAR LIB_LIST) + foreach(lib IN LISTS ${LIB_LIST}) + string(REGEX REPLACE "-internal$" "" 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() + +include(CheckCXXSymbolExists) +set(PKGCONFIG_CXX_LIB "") +check_cxx_symbol_exists(__GLIBCXX__ iostream LIBSTDCXX) +check_cxx_symbol_exists(_LIBCPP_VERSION iostream LIBCXX) +if(LIBSTDCXX) + set(PKGCONFIG_CXX_LIB "-lstdc++") +elseif(LIBCXX) + set(PKGCONFIG_CXX_LIB "-lc++") +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) +if(JPEGXL_ENABLE_TOOLS OR BUILD_TESTING) include(jxl_extras.cmake) endif() include(jxl_threads.cmake) +if (JPEGXL_ENABLE_JPEGLI) + include(jpegli.cmake) +endif() -# Install all the library headers from the source and the generated ones. There -# is no distinction on which libraries use which header since it is expected -# that all developer libraries are available together at build time. -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/jxl - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +# For simplicity all the library headers, both source and generated ones, are +# gathered in the binary folder. There is no distinction on which libraries use +# which header since it is expected that all developer libraries are available +# together at build time. install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/jxl DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") -# Profiler for libjxl -include(jxl_profiler.cmake) - if(BUILD_TESTING) -# Unittests -cmake_policy(SET CMP0057 NEW) # https://gitlab.kitware.com/cmake/cmake/issues/18198 -include(GoogleTest) + include(GoogleTest) +endif() # Tests for the jxl library. include(jxl_tests.cmake) -# Google benchmark for the jxl library -include(jxl_benchmark.cmake) - -endif() # BUILD_TESTING +if(BUILD_TESTING) + # Google benchmark for the jxl library + include(jxl_benchmark.cmake) +endif() diff --git a/media/libjxl/src/lib/extras/alpha_blend.cc b/media/libjxl/src/lib/extras/alpha_blend.cc new file mode 100644 index 0000000000..8e34d56cec --- /dev/null +++ b/media/libjxl/src/lib/extras/alpha_blend.cc @@ -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. + +#include "lib/extras/alpha_blend.h" + +#include "lib/extras/packed_image.h" +#include "lib/jxl/base/status.h" + +namespace jxl { +namespace extras { + +namespace { + +Status AlphaBlend(PackedFrame* frame, const float background[3]) { + if (!frame) return true; + const PackedImage& im = frame->color; + JxlPixelFormat format = im.format; + if (format.num_channels != 2 && format.num_channels != 4) { + return true; + } + --format.num_channels; + JXL_ASSIGN_OR_RETURN(PackedImage blended, + PackedImage::Create(im.xsize, im.ysize, format)); + // TODO(szabadka) SIMDify this and make it work for float16. + for (size_t y = 0; y < im.ysize; ++y) { + for (size_t x = 0; x < im.xsize; ++x) { + if (format.num_channels == 2) { + float g = im.GetPixelValue(y, x, 0); + float a = im.GetPixelValue(y, x, 1); + float out = g * a + background[0] * (1 - a); + blended.SetPixelValue(y, x, 0, out); + } else { + float r = im.GetPixelValue(y, x, 0); + float g = im.GetPixelValue(y, x, 1); + float b = im.GetPixelValue(y, x, 2); + float a = im.GetPixelValue(y, x, 3); + float out_r = r * a + background[0] * (1 - a); + float out_g = g * a + background[1] * (1 - a); + float out_b = b * a + background[2] * (1 - a); + blended.SetPixelValue(y, x, 0, out_r); + blended.SetPixelValue(y, x, 1, out_g); + blended.SetPixelValue(y, x, 2, out_b); + } + } + } + frame->color = blended.Copy(); + return true; +} + +} // namespace + +Status AlphaBlend(PackedPixelFile* ppf, const float background[3]) { + if (!ppf || ppf->info.alpha_bits == 0) { + return true; + } + ppf->info.alpha_bits = 0; + JXL_RETURN_IF_ERROR(AlphaBlend(ppf->preview_frame.get(), background)); + for (auto& frame : ppf->frames) { + JXL_RETURN_IF_ERROR(AlphaBlend(&frame, background)); + } + return true; +} + +} // namespace extras +} // namespace jxl diff --git a/media/libjxl/src/lib/extras/alpha_blend.h b/media/libjxl/src/lib/extras/alpha_blend.h new file mode 100644 index 0000000000..b388a5e22a --- /dev/null +++ b/media/libjxl/src/lib/extras/alpha_blend.h @@ -0,0 +1,20 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_EXTRAS_ALPHA_BLEND_H_ +#define LIB_EXTRAS_ALPHA_BLEND_H_ + +#include "lib/extras/packed_image.h" +#include "lib/jxl/base/status.h" + +namespace jxl { +namespace extras { + +Status AlphaBlend(PackedPixelFile* ppf, const float background[3]); + +} // namespace extras +} // namespace jxl + +#endif // LIB_EXTRAS_ALPHA_BLEND_H_ diff --git a/media/libjxl/src/lib/extras/codec.cc b/media/libjxl/src/lib/extras/codec.cc index 774b4ccb6e..ba4bcfebbc 100644 --- a/media/libjxl/src/lib/extras/codec.cc +++ b/media/libjxl/src/lib/extras/codec.cc @@ -5,28 +5,18 @@ #include "lib/extras/codec.h" -#include "jxl/decode.h" -#include "jxl/types.h" -#include "lib/extras/packed_image.h" -#include "lib/jxl/base/padded_bytes.h" -#include "lib/jxl/base/status.h" - -#if JPEGXL_ENABLE_APNG -#include "lib/extras/enc/apng.h" -#endif -#if JPEGXL_ENABLE_JPEG -#include "lib/extras/enc/jpg.h" -#endif -#if JPEGXL_ENABLE_EXR -#include "lib/extras/enc/exr.h" -#endif +#include +#include #include "lib/extras/dec/decode.h" +#include "lib/extras/enc/apng.h" +#include "lib/extras/enc/exr.h" +#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/file_io.h" -#include "lib/jxl/image_bundle.h" +#include "lib/jxl/base/status.h" namespace jxl { namespace { @@ -38,78 +28,46 @@ constexpr size_t kMinBytes = 9; Status SetFromBytes(const Span bytes, const extras::ColorHints& color_hints, CodecInOut* io, - ThreadPool* pool, extras::Codec* orig_codec) { + ThreadPool* pool, const SizeConstraints* constraints, + extras::Codec* orig_codec) { if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes"); extras::PackedPixelFile ppf; - if (extras::DecodeBytes(bytes, color_hints, io->constraints, &ppf, - orig_codec)) { + if (extras::DecodeBytes(bytes, color_hints, &ppf, constraints, orig_codec)) { return ConvertPackedPixelFileToCodecInOut(ppf, pool, io); } return JXL_FAILURE("Codecs failed to decode"); } -Status SetFromFile(const std::string& pathname, - const extras::ColorHints& color_hints, CodecInOut* io, - ThreadPool* pool, extras::Codec* orig_codec) { - std::vector encoded; - JXL_RETURN_IF_ERROR(ReadFile(pathname, &encoded)); - JXL_RETURN_IF_ERROR(SetFromBytes(Span(encoded), color_hints, - io, pool, orig_codec)); - return true; -} - -Status Encode(const CodecInOut& io, const extras::Codec codec, - const ColorEncoding& c_desired, size_t bits_per_sample, +Status Encode(const extras::PackedPixelFile& ppf, const extras::Codec codec, std::vector* bytes, ThreadPool* pool) { - JXL_CHECK(!io.Main().c_current().ICC().empty()); - JXL_CHECK(!c_desired.ICC().empty()); - io.CheckMetadata(); - if (io.Main().IsJPEG()) { - JXL_WARNING("Writing JPEG data as pixels"); - } - JxlPixelFormat format = { - 0, // num_channels is ignored by the converter - bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16, JXL_BIG_ENDIAN, - 0}; - const bool floating_point = bits_per_sample > 16; + bytes->clear(); std::unique_ptr encoder; - std::ostringstream os; switch (codec) { case extras::Codec::kPNG: -#if JPEGXL_ENABLE_APNG encoder = extras::GetAPNGEncoder(); - break; -#else - return JXL_FAILURE("JPEG XL was built without (A)PNG support"); -#endif + if (encoder) { + break; + } else { + return JXL_FAILURE("JPEG XL was built without (A)PNG support"); + } case extras::Codec::kJPG: -#if JPEGXL_ENABLE_JPEG - format.data_type = JXL_TYPE_UINT8; encoder = extras::GetJPEGEncoder(); - os << io.jpeg_quality; - encoder->SetOption("q", os.str()); - break; -#else - return JXL_FAILURE("JPEG XL was built without JPEG support"); -#endif + if (encoder) { + break; + } else { + return JXL_FAILURE("JPEG XL was built without JPEG support"); + } case extras::Codec::kPNM: - if (io.Main().HasAlpha()) { + if (ppf.info.alpha_bits > 0) { encoder = extras::GetPAMEncoder(); - } else if (io.Main().IsGray()) { + } else if (ppf.info.num_color_channels == 1) { encoder = extras::GetPGMEncoder(); - } else if (!floating_point) { + } else if (ppf.info.bits_per_sample <= 16) { encoder = extras::GetPPMEncoder(); } else { - format.data_type = JXL_TYPE_FLOAT; - format.endianness = JXL_NATIVE_ENDIAN; encoder = extras::GetPFMEncoder(); } - if (!c_desired.IsSRGB()) { - JXL_WARNING( - "PNM encoder cannot store custom ICC profile; decoder " - "will need hint key=color_space to get the same values"); - } break; case extras::Codec::kPGX: encoder = extras::GetPGXEncoder(); @@ -117,13 +75,16 @@ Status Encode(const CodecInOut& io, const extras::Codec codec, case extras::Codec::kGIF: return JXL_FAILURE("Encoding to GIF is not implemented"); case extras::Codec::kEXR: -#if JPEGXL_ENABLE_EXR - format.data_type = JXL_TYPE_FLOAT; encoder = extras::GetEXREncoder(); - break; -#else - return JXL_FAILURE("JPEG XL was built without OpenEXR support"); -#endif + if (encoder) { + break; + } else { + return JXL_FAILURE("JPEG XL was built without OpenEXR support"); + } + case extras::Codec::kJXL: + // TODO(user): implement + return JXL_FAILURE("Codec::kJXL is not supported yet"); + case extras::Codec::kUnknown: return JXL_FAILURE("Cannot encode using Codec::kUnknown"); } @@ -131,59 +92,20 @@ Status Encode(const CodecInOut& io, const extras::Codec codec, if (!encoder) { return JXL_FAILURE("Invalid codec."); } - - extras::PackedPixelFile ppf; - JXL_RETURN_IF_ERROR( - ConvertCodecInOutToPackedPixelFile(io, format, c_desired, pool, &ppf)); extras::EncodedImage encoded_image; JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded_image, pool)); - JXL_ASSERT(encoded_image.bitstreams.size() == 1); + JXL_ENSURE(encoded_image.bitstreams.size() == 1); *bytes = encoded_image.bitstreams[0]; return true; } -Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired, - size_t bits_per_sample, const std::string& pathname, - ThreadPool* pool) { - const std::string extension = Extension(pathname); +Status Encode(const extras::PackedPixelFile& ppf, const std::string& pathname, + std::vector* bytes, ThreadPool* pool) { + std::string extension; const extras::Codec codec = - extras::CodecFromExtension(extension, &bits_per_sample); - - // Warn about incorrect usage of PGM/PGX/PPM - only the latter supports - // color, but CodecFromExtension lumps them all together. - if (codec == extras::Codec::kPNM && extension != ".pfm") { - if (io.Main().HasAlpha() && extension != ".pam") { - JXL_WARNING( - "For images with alpha, the filename should end with .pam.\n"); - } else if (!io.Main().IsGray() && extension == ".pgm") { - JXL_WARNING("For color images, the filename should end with .ppm.\n"); - } else if (io.Main().IsGray() && extension == ".ppm") { - JXL_WARNING( - "For grayscale images, the filename should not end with .ppm.\n"); - } - if (bits_per_sample > 16) { - JXL_WARNING("PPM only supports up to 16 bits per sample"); - bits_per_sample = 16; - } - } else if (codec == extras::Codec::kPGX && !io.Main().IsGray()) { - JXL_WARNING("Storing color image to PGX - use .ppm extension instead.\n"); - } - if (bits_per_sample > 16 && codec == extras::Codec::kPNG) { - JXL_WARNING("PNG only supports up to 16 bits per sample"); - bits_per_sample = 16; - } - - std::vector encoded; - return Encode(io, codec, c_desired, bits_per_sample, &encoded, pool) && - WriteFile(encoded, pathname); -} - -Status EncodeToFile(const CodecInOut& io, const std::string& pathname, - ThreadPool* pool) { - // TODO(lode): need to take the floating_point_sample field into account - return EncodeToFile(io, io.metadata.m.color_encoding, - io.metadata.m.bit_depth.bits_per_sample, pathname, pool); + extras::CodecFromPath(pathname, nullptr, &extension); + return Encode(ppf, codec, bytes, pool); } } // namespace jxl diff --git a/media/libjxl/src/lib/extras/codec.h b/media/libjxl/src/lib/extras/codec.h index 73fdc80be9..9fd61503cb 100644 --- a/media/libjxl/src/lib/extras/codec.h +++ b/media/libjxl/src/lib/extras/codec.h @@ -17,7 +17,6 @@ #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" @@ -26,39 +25,30 @@ namespace jxl { +struct SizeConstraints; + // Decodes "bytes" and sets io->metadata.m. // color_space_hint may specify the color space, otherwise, defaults to sRGB. Status SetFromBytes(Span bytes, const extras::ColorHints& color_hints, CodecInOut* io, ThreadPool* pool = nullptr, + const SizeConstraints* constraints = nullptr, extras::Codec* orig_codec = nullptr); // Helper function to use no color_space_hint. JXL_INLINE Status SetFromBytes(const Span bytes, CodecInOut* io, ThreadPool* pool = nullptr, + const SizeConstraints* constraints = nullptr, extras::Codec* orig_codec = nullptr) { - return SetFromBytes(bytes, extras::ColorHints(), io, pool, orig_codec); + return SetFromBytes(bytes, extras::ColorHints(), io, pool, constraints, + orig_codec); } -// Reads from file and calls SetFromBytes. -Status SetFromFile(const std::string& pathname, - const extras::ColorHints& color_hints, CodecInOut* io, - ThreadPool* pool = nullptr, - extras::Codec* orig_codec = nullptr); +Status Encode(const extras::PackedPixelFile& ppf, extras::Codec codec, + std::vector* bytes, ThreadPool* pool); -// Replaces "bytes" with an encoding of pixels transformed from c_current -// color space to c_desired. -Status Encode(const CodecInOut& io, extras::Codec codec, - const ColorEncoding& c_desired, size_t bits_per_sample, +Status Encode(const extras::PackedPixelFile& ppf, const std::string& pathname, std::vector* bytes, ThreadPool* pool = nullptr); -// Deduces codec, calls Encode and writes to file. -Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired, - size_t bits_per_sample, const std::string& pathname, - ThreadPool* pool = nullptr); -// Same, but defaults to metadata.original color_encoding and bits_per_sample. -Status EncodeToFile(const CodecInOut& io, const std::string& pathname, - ThreadPool* pool = nullptr); - } // namespace jxl #endif // LIB_EXTRAS_CODEC_H_ diff --git a/media/libjxl/src/lib/extras/codec_test.cc b/media/libjxl/src/lib/extras/codec_test.cc index 19cac39979..6cbed22097 100644 --- a/media/libjxl/src/lib/extras/codec_test.cc +++ b/media/libjxl/src/lib/extras/codec_test.cc @@ -3,42 +3,52 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -#include "lib/extras/codec.h" - -#include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include #include #include #include -#include "lib/extras/dec/pgx.h" -#include "lib/extras/dec/pnm.h" +#include "lib/extras/common.h" +#include "lib/extras/dec/color_hints.h" +#include "lib/extras/dec/decode.h" #include "lib/extras/enc/encode.h" -#include "lib/extras/packed_image_convert.h" -#include "lib/jxl/base/printf_macros.h" +#include "lib/extras/packed_image.h" +#include "lib/jxl/base/byte_order.h" +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/random.h" -#include "lib/jxl/base/thread_pool_internal.h" -#include "lib/jxl/color_management.h" -#include "lib/jxl/enc_color_management.h" -#include "lib/jxl/image.h" -#include "lib/jxl/image_bundle.h" -#include "lib/jxl/image_test_utils.h" +#include "lib/jxl/base/span.h" +#include "lib/jxl/base/status.h" +#include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/test_utils.h" -#include "lib/jxl/testdata.h" +#include "lib/jxl/testing.h" namespace jxl { + +using ::jxl::test::ThreadPoolForTests; + namespace extras { + +Status PnmParseSigned(Bytes str, double* v); +Status PnmParseUnsigned(Bytes str, size_t* v); + namespace { -using ::testing::AllOf; -using ::testing::Contains; -using ::testing::Field; -using ::testing::IsEmpty; -using ::testing::NotNull; -using ::testing::SizeIs; +Span MakeSpan(const char* str) { + return Bytes(reinterpret_cast(str), strlen(str)); +} std::string ExtensionFromCodec(Codec codec, const bool is_gray, const bool has_alpha, @@ -51,18 +61,14 @@ std::string ExtensionFromCodec(Codec codec, const bool is_gray, case Codec::kPNG: return ".png"; case Codec::kPNM: + if (bits_per_sample == 32) return ".pfm"; if (has_alpha) return ".pam"; - if (is_gray) return ".pgm"; - return (bits_per_sample == 32) ? ".pfm" : ".ppm"; - case Codec::kGIF: - return ".gif"; + return is_gray ? ".pgm" : ".ppm"; case Codec::kEXR: return ".exr"; - case Codec::kUnknown: + default: return std::string(); } - JXL_UNREACHABLE; - return std::string(); } void VerifySameImage(const PackedImage& im0, size_t bits_per_sample0, @@ -76,8 +82,8 @@ void VerifySameImage(const PackedImage& im0, size_t bits_per_sample0, }; double factor0 = get_factor(im0.format, bits_per_sample0); double factor1 = get_factor(im1.format, bits_per_sample1); - auto pixels0 = static_cast(im0.pixels()); - auto pixels1 = static_cast(im1.pixels()); + const auto* pixels0 = static_cast(im0.pixels()); + const auto* pixels1 = static_cast(im1.pixels()); auto rgba0 = test::ConvertToRGBA32(pixels0, im0.xsize, im0.ysize, im0.format, factor0); auto rgba1 = @@ -110,17 +116,16 @@ JxlColorEncoding CreateTestColorEncoding(bool is_gray) { // Roundtrip through internal color encoding to fill in primaries and white // point CIE xy coordinates. ColorEncoding c_internal; - JXL_CHECK(ConvertExternalToInternalColorEncoding(c, &c_internal)); - ConvertInternalToExternalColorEncoding(c_internal, &c); + EXPECT_TRUE(c_internal.FromExternal(c)); + c = c_internal.ToExternal(); return c; } std::vector GenerateICC(JxlColorEncoding color_encoding) { ColorEncoding c; - JXL_CHECK(ConvertExternalToInternalColorEncoding(color_encoding, &c)); - JXL_CHECK(c.CreateICC()); - PaddedBytes icc = c.ICC(); - return std::vector(icc.begin(), icc.end()); + EXPECT_TRUE(c.FromExternal(color_encoding)); + EXPECT_TRUE(!c.ICC().empty()); + return c.ICC(); } void StoreRandomValue(uint8_t* out, Rng* rng, JxlPixelFormat format, @@ -173,10 +178,11 @@ struct TestImageParams { bool is_gray; bool add_alpha; bool big_endian; + bool add_extra_channels; bool ShouldTestRoundtrip() const { if (codec == Codec::kPNG) { - return true; + return bits_per_sample <= 16; } else if (codec == Codec::kPNM) { // TODO(szabadka) Make PNM encoder endianness-aware. return ((bits_per_sample <= 16 && big_endian) || @@ -213,7 +219,7 @@ struct TestImageParams { std::string DebugString() const { std::ostringstream os; os << "bps:" << bits_per_sample << " gr:" << is_gray << " al:" << add_alpha - << " be: " << big_endian; + << " be: " << big_endian << " ec: " << add_extra_channels; return os.str(); } }; @@ -225,14 +231,31 @@ void CreateTestImage(const TestImageParams& params, PackedPixelFile* ppf) { ppf->info.exponent_bits_per_sample = params.bits_per_sample == 32 ? 8 : 0; ppf->info.num_color_channels = params.is_gray ? 1 : 3; ppf->info.alpha_bits = params.add_alpha ? params.bits_per_sample : 0; - ppf->info.alpha_premultiplied = (params.codec == Codec::kEXR); + ppf->info.alpha_premultiplied = TO_JXL_BOOL(params.codec == Codec::kEXR); JxlColorEncoding color_encoding = CreateTestColorEncoding(params.is_gray); ppf->icc = GenerateICC(color_encoding); ppf->color_encoding = color_encoding; - PackedFrame frame(params.xsize, params.ysize, params.PixelFormat()); + JXL_TEST_ASSIGN_OR_DIE( + PackedFrame frame, + PackedFrame::Create(params.xsize, params.ysize, params.PixelFormat())); FillPackedImage(params.bits_per_sample, &frame.color); + if (params.add_extra_channels) { + for (size_t i = 0; i < 7; ++i) { + JxlPixelFormat ec_format = params.PixelFormat(); + ec_format.num_channels = 1; + JXL_TEST_ASSIGN_OR_DIE( + PackedImage ec, + PackedImage::Create(params.xsize, params.ysize, ec_format)); + FillPackedImage(params.bits_per_sample, &ec); + frame.extra_channels.emplace_back(std::move(ec)); + PackedExtraChannel pec; + pec.ec_info.bits_per_sample = params.bits_per_sample; + pec.ec_info.type = static_cast(i); + ppf->extra_channels_info.emplace_back(std::move(pec)); + } + } ppf->frames.emplace_back(std::move(frame)); } @@ -249,33 +272,67 @@ void TestRoundTrip(const TestImageParams& params, ThreadPool* pool) { EncodedImage encoded; auto encoder = Encoder::FromExtension(extension); - ASSERT_TRUE(encoder.get()); + if (!encoder) { + fprintf(stderr, "Skipping test because of missing codec support.\n"); + return; + } ASSERT_TRUE(encoder->Encode(ppf_in, &encoded, pool)); ASSERT_EQ(encoded.bitstreams.size(), 1); PackedPixelFile ppf_out; - ASSERT_TRUE(DecodeBytes(Span(encoded.bitstreams[0]), - ColorHints(), SizeConstraints(), &ppf_out)); - - if (params.codec != Codec::kPNM && params.codec != Codec::kPGX && - params.codec != Codec::kEXR) { + 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(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); + EXPECT_EQ(ppf_in.color_encoding.white_point, + ppf_out.color_encoding.white_point); + if (ppf_in.color_encoding.color_space != JXL_COLOR_SPACE_GRAY) { + EXPECT_EQ(ppf_in.color_encoding.primaries, + ppf_out.color_encoding.primaries); + } + EXPECT_EQ(ppf_in.color_encoding.transfer_function, + ppf_out.color_encoding.transfer_function); + EXPECT_EQ(ppf_out.color_encoding.rendering_intent, + JXL_RENDERING_INTENT_RELATIVE); + } else if (params.codec != Codec::kPNM && params.codec != Codec::kPGX && + params.codec != Codec::kEXR) { EXPECT_EQ(ppf_in.icc, ppf_out.icc); } ASSERT_EQ(ppf_out.frames.size(), 1); - VerifySameImage(ppf_in.frames[0].color, ppf_in.info.bits_per_sample, - ppf_out.frames[0].color, ppf_out.info.bits_per_sample, + const auto& frame_in = ppf_in.frames[0]; + const auto& frame_out = ppf_out.frames[0]; + VerifySameImage(frame_in.color, ppf_in.info.bits_per_sample, frame_out.color, + ppf_out.info.bits_per_sample, /*lossless=*/params.codec != Codec::kJPG); + ASSERT_EQ(frame_in.extra_channels.size(), frame_out.extra_channels.size()); + ASSERT_EQ(ppf_out.extra_channels_info.size(), + frame_out.extra_channels.size()); + for (size_t i = 0; i < frame_in.extra_channels.size(); ++i) { + VerifySameImage(frame_in.extra_channels[i], ppf_in.info.bits_per_sample, + frame_out.extra_channels[i], ppf_out.info.bits_per_sample, + /*lossless=*/true); + EXPECT_EQ(ppf_out.extra_channels_info[i].ec_info.type, + ppf_in.extra_channels_info[i].ec_info.type); + } } TEST(CodecTest, TestRoundTrip) { - ThreadPoolInternal pool(12); + ThreadPoolForTests pool(12); TestImageParams params; params.xsize = 7; params.ysize = 4; - for (Codec codec : AvailableCodecs()) { + for (Codec codec : + {Codec::kPNG, Codec::kPNM, Codec::kPGX, Codec::kEXR, Codec::kJPG}) { for (int bits_per_sample : {4, 8, 10, 12, 16, 32}) { for (bool is_gray : {false, true}) { for (bool add_alpha : {false, true}) { @@ -285,7 +342,12 @@ TEST(CodecTest, TestRoundTrip) { params.is_gray = is_gray; params.add_alpha = add_alpha; params.big_endian = big_endian; - TestRoundTrip(params, &pool); + params.add_extra_channels = false; + TestRoundTrip(params, pool.get()); + if (codec == Codec::kPNM && add_alpha) { + params.add_extra_channels = true; + TestRoundTrip(params, pool.get()); + } } } } @@ -293,194 +355,74 @@ TEST(CodecTest, TestRoundTrip) { } } -CodecInOut DecodeRoundtrip(const std::string& pathname, ThreadPool* pool, - const ColorHints& color_hints = ColorHints()) { - CodecInOut io; - const PaddedBytes orig = ReadTestData(pathname); - JXL_CHECK( - SetFromBytes(Span(orig), color_hints, &io, pool, nullptr)); - const ImageBundle& ib1 = io.Main(); +TEST(CodecTest, LosslessPNMRoundtrip) { + ThreadPoolForTests pool(12); - // Encode/Decode again to make sure Encode carries through all metadata. - std::vector encoded; - JXL_CHECK(Encode(io, Codec::kPNG, io.metadata.m.color_encoding, - io.metadata.m.bit_depth.bits_per_sample, &encoded, pool)); + static const char* kChannels[] = {"", "g", "ga", "rgb", "rgba"}; + static const char* kExtension[] = {"", ".pgm", ".pam", ".ppm", ".pam"}; + for (size_t bit_depth = 1; bit_depth <= 16; ++bit_depth) { + for (size_t channels = 1; channels <= 4; ++channels) { + 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 std::vector orig = jxl::test::ReadTestData(filename); - CodecInOut io2; - JXL_CHECK(SetFromBytes(Span(encoded), color_hints, &io2, pool, - nullptr)); - const ImageBundle& ib2 = io2.Main(); - EXPECT_EQ(Description(ib1.metadata()->color_encoding), - Description(ib2.metadata()->color_encoding)); - EXPECT_EQ(Description(ib1.c_current()), Description(ib2.c_current())); + PackedPixelFile ppf; + ColorHints color_hints; + color_hints.Add("color_space", + channels < 3 ? "Gra_D65_Rel_SRG" : "RGB_D65_SRG_Rel_SRG"); + ASSERT_TRUE( + DecodeBytes(Bytes(orig.data(), orig.size()), color_hints, &ppf)); - size_t bits_per_sample = io2.metadata.m.bit_depth.bits_per_sample; - - // "Same" pixels? - double max_l1 = bits_per_sample <= 12 ? 1.3 : 2E-3; - double max_rel = bits_per_sample <= 12 ? 6E-3 : 1E-4; - if (ib1.metadata()->color_encoding.IsGray()) { - max_rel *= 2.0; - } else if (ib1.metadata()->color_encoding.primaries != Primaries::kSRGB) { - // Need more tolerance for large gamuts (anything but sRGB) - max_l1 *= 1.5; - max_rel *= 3.0; - } - VerifyRelativeError(ib1.color(), ib2.color(), max_l1, max_rel); - - // Simulate the encoder removing profile and decoder restoring it. - if (!ib2.metadata()->color_encoding.WantICC()) { - io2.metadata.m.color_encoding.InternalRemoveICC(); - EXPECT_TRUE(io2.metadata.m.color_encoding.CreateICC()); - } - - return io2; -} - -#if 0 -TEST(CodecTest, TestMetadataSRGB) { - ThreadPoolInternal pool(12); - - const char* paths[] = {"external/raw.pixls/DJI-FC6310-16bit_srgb8_v4_krita.png", - "external/raw.pixls/Google-Pixel2XL-16bit_srgb8_v4_krita.png", - "external/raw.pixls/HUAWEI-EVA-L09-16bit_srgb8_dt.png", - "external/raw.pixls/Nikon-D300-12bit_srgb8_dt.png", - "external/raw.pixls/Sony-DSC-RX1RM2-14bit_srgb8_v4_krita.png"}; - for (const char* relative_pathname : paths) { - const CodecInOut io = - DecodeRoundtrip(relative_pathname, Codec::kPNG, &pool); - EXPECT_EQ(8, io.metadata.m.bit_depth.bits_per_sample); - EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample); - EXPECT_EQ(0, io.metadata.m.bit_depth.exponent_bits_per_sample); - - EXPECT_EQ(64, io.xsize()); - EXPECT_EQ(64, io.ysize()); - EXPECT_FALSE(io.metadata.m.HasAlpha()); - - const ColorEncoding& c_original = io.metadata.m.color_encoding; - EXPECT_FALSE(c_original.ICC().empty()); - EXPECT_EQ(ColorSpace::kRGB, c_original.GetColorSpace()); - EXPECT_EQ(WhitePoint::kD65, c_original.white_point); - EXPECT_EQ(Primaries::kSRGB, c_original.primaries); - EXPECT_TRUE(c_original.tf.IsSRGB()); + EncodedImage encoded; + auto encoder = Encoder::FromExtension(extension); + ASSERT_TRUE(encoder.get()); + ASSERT_TRUE(encoder->Encode(ppf, &encoded, pool.get())); + ASSERT_EQ(encoded.bitstreams.size(), 1); + ASSERT_EQ(orig.size(), encoded.bitstreams[0].size()); + EXPECT_EQ(0, + memcmp(orig.data(), encoded.bitstreams[0].data(), orig.size())); + } } } -TEST(CodecTest, TestMetadataLinear) { - ThreadPoolInternal pool(12); +TEST(CodecTest, TestPNM) { + size_t u = 77777; // Initialized to wrong value. + double d = 77.77; +// Failing to parse invalid strings results in a crash if `JXL_CRASH_ON_ERROR` +// is defined and hence the tests fail. Therefore we only run these tests if +// `JXL_CRASH_ON_ERROR` is not defined. +#if (!JXL_CRASH_ON_ERROR) + ASSERT_FALSE(PnmParseUnsigned(MakeSpan(""), &u)); + ASSERT_FALSE(PnmParseUnsigned(MakeSpan("+"), &u)); + ASSERT_FALSE(PnmParseUnsigned(MakeSpan("-"), &u)); + ASSERT_FALSE(PnmParseUnsigned(MakeSpan("A"), &u)); - const char* paths[3] = { - "external/raw.pixls/Google-Pixel2XL-16bit_acescg_g1_v4_krita.png", - "external/raw.pixls/HUAWEI-EVA-L09-16bit_709_g1_dt.png", - "external/raw.pixls/Nikon-D300-12bit_2020_g1_dt.png", - }; - const WhitePoint white_points[3] = {WhitePoint::kCustom, WhitePoint::kD65, - WhitePoint::kD65}; - const Primaries primaries[3] = {Primaries::kCustom, Primaries::kSRGB, - Primaries::k2100}; - - for (size_t i = 0; i < 3; ++i) { - const CodecInOut io = DecodeRoundtrip(paths[i], Codec::kPNG, &pool); - EXPECT_EQ(16, io.metadata.m.bit_depth.bits_per_sample); - EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample); - EXPECT_EQ(0, io.metadata.m.bit_depth.exponent_bits_per_sample); - - EXPECT_EQ(64, io.xsize()); - EXPECT_EQ(64, io.ysize()); - EXPECT_FALSE(io.metadata.m.HasAlpha()); - - const ColorEncoding& c_original = io.metadata.m.color_encoding; - EXPECT_FALSE(c_original.ICC().empty()); - EXPECT_EQ(ColorSpace::kRGB, c_original.GetColorSpace()); - EXPECT_EQ(white_points[i], c_original.white_point); - EXPECT_EQ(primaries[i], c_original.primaries); - EXPECT_TRUE(c_original.tf.IsLinear()); - } -} - -TEST(CodecTest, TestMetadataICC) { - ThreadPoolInternal pool(12); - - const char* paths[] = { - "external/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png", - "external/raw.pixls/Sony-DSC-RX1RM2-14bit_709_v4_krita.png", - }; - for (const char* relative_pathname : paths) { - const CodecInOut io = - DecodeRoundtrip(relative_pathname, Codec::kPNG, &pool); - EXPECT_GE(16, io.metadata.m.bit_depth.bits_per_sample); - EXPECT_LE(14, io.metadata.m.bit_depth.bits_per_sample); - - EXPECT_EQ(64, io.xsize()); - EXPECT_EQ(64, io.ysize()); - EXPECT_FALSE(io.metadata.m.HasAlpha()); - - const ColorEncoding& c_original = io.metadata.m.color_encoding; - EXPECT_FALSE(c_original.ICC().empty()); - EXPECT_EQ(RenderingIntent::kPerceptual, c_original.rendering_intent); - EXPECT_EQ(ColorSpace::kRGB, c_original.GetColorSpace()); - EXPECT_EQ(WhitePoint::kD65, c_original.white_point); - EXPECT_EQ(Primaries::kSRGB, c_original.primaries); - EXPECT_EQ(TransferFunction::k709, c_original.tf.GetTransferFunction()); - } -} - -TEST(CodecTest, Testexternal/pngsuite) { - ThreadPoolInternal pool(12); - - // Ensure we can load PNG with text, japanese UTF-8, compressed text. - (void)DecodeRoundtrip("external/pngsuite/ct1n0g04.png", Codec::kPNG, &pool); - (void)DecodeRoundtrip("external/pngsuite/ctjn0g04.png", Codec::kPNG, &pool); - (void)DecodeRoundtrip("external/pngsuite/ctzn0g04.png", Codec::kPNG, &pool); - - // Extract gAMA - const CodecInOut b1 = - DecodeRoundtrip("external/pngsuite/g10n3p04.png", Codec::kPNG, &pool); - EXPECT_TRUE(b1.metadata.color_encoding.tf.IsLinear()); - - // Extract cHRM - const CodecInOut b_p = - DecodeRoundtrip("external/pngsuite/ccwn2c08.png", Codec::kPNG, &pool); - EXPECT_EQ(Primaries::kSRGB, b_p.metadata.color_encoding.primaries); - EXPECT_EQ(WhitePoint::kD65, b_p.metadata.color_encoding.white_point); - - // Extract EXIF from (new-style) dedicated chunk - const CodecInOut b_exif = - DecodeRoundtrip("external/pngsuite/exif2c08.png", Codec::kPNG, &pool); - EXPECT_EQ(978, b_exif.blobs.exif.size()); -} + ASSERT_FALSE(PnmParseSigned(MakeSpan(""), &d)); + ASSERT_FALSE(PnmParseSigned(MakeSpan("+"), &d)); + ASSERT_FALSE(PnmParseSigned(MakeSpan("-"), &d)); + ASSERT_FALSE(PnmParseSigned(MakeSpan("A"), &d)); #endif + ASSERT_TRUE(PnmParseUnsigned(MakeSpan("1"), &u)); + ASSERT_TRUE(u == 1); -void VerifyWideGamutMetadata(const std::string& relative_pathname, - const Primaries primaries, ThreadPool* pool) { - const CodecInOut io = DecodeRoundtrip(relative_pathname, pool); + ASSERT_TRUE(PnmParseUnsigned(MakeSpan("32"), &u)); + ASSERT_TRUE(u == 32); - 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); - - const ColorEncoding& c_original = io.metadata.m.color_encoding; - EXPECT_FALSE(c_original.ICC().empty()); - EXPECT_EQ(RenderingIntent::kAbsolute, c_original.rendering_intent); - EXPECT_EQ(ColorSpace::kRGB, c_original.GetColorSpace()); - EXPECT_EQ(WhitePoint::kD65, c_original.white_point); - EXPECT_EQ(primaries, c_original.primaries); + ASSERT_TRUE(PnmParseSigned(MakeSpan("1"), &d)); + ASSERT_TRUE(d == 1.0); + ASSERT_TRUE(PnmParseSigned(MakeSpan("+2"), &d)); + ASSERT_TRUE(d == 2.0); + ASSERT_TRUE(PnmParseSigned(MakeSpan("-3"), &d)); + ASSERT_TRUE(std::abs(d - -3.0) < 1E-15); + ASSERT_TRUE(PnmParseSigned(MakeSpan("3.141592"), &d)); + ASSERT_TRUE(std::abs(d - 3.141592) < 1E-15); + ASSERT_TRUE(PnmParseSigned(MakeSpan("-3.141592"), &d)); + ASSERT_TRUE(std::abs(d - -3.141592) < 1E-15); } -TEST(CodecTest, TestWideGamut) { - ThreadPoolInternal pool(12); - // VerifyWideGamutMetadata("external/wide-gamut-tests/P3-sRGB-color-bars.png", - // Primaries::kP3, &pool); - VerifyWideGamutMetadata("external/wide-gamut-tests/P3-sRGB-color-ring.png", - Primaries::kP3, &pool); - // VerifyWideGamutMetadata("external/wide-gamut-tests/R2020-sRGB-color-bars.png", - // Primaries::k2100, &pool); - // VerifyWideGamutMetadata("external/wide-gamut-tests/R2020-sRGB-color-ring.png", - // Primaries::k2100, &pool); -} - -TEST(CodecTest, TestPNM) { TestCodecPNM(); } - TEST(CodecTest, FormatNegotiation) { const std::vector accepted_formats = { {/*num_channels=*/4, @@ -520,29 +462,32 @@ TEST(CodecTest, EncodeToPNG) { ThreadPool* const pool = nullptr; std::unique_ptr png_encoder = Encoder::FromExtension(".png"); - ASSERT_THAT(png_encoder, NotNull()); + if (!png_encoder) { + fprintf(stderr, "Skipping test because of missing codec support.\n"); + return; + } - const PaddedBytes original_png = - ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); + 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(), SizeConstraints(), &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), - Field(&JxlPixelFormat::data_type, format.data_type), - Field(&JxlPixelFormat::endianness, format.endianness)))); + const auto& format_matcher = [&format](const JxlPixelFormat& candidate) { + return (candidate.num_channels == format.num_channels) && + (candidate.data_type == format.data_type) && + (candidate.endianness == format.endianness); + }; + const auto formats = png_encoder->AcceptedFormats(); + ASSERT_TRUE(std::any_of(formats.begin(), formats.end(), format_matcher)); EncodedImage encoded_png; ASSERT_TRUE(png_encoder->Encode(ppf, &encoded_png, pool)); - EXPECT_THAT(encoded_png.icc, IsEmpty()); - ASSERT_THAT(encoded_png.bitstreams, SizeIs(1)); + EXPECT_TRUE(encoded_png.icc.empty()); + ASSERT_EQ(encoded_png.bitstreams.size(), 1); PackedPixelFile decoded_ppf; - ASSERT_TRUE( - extras::DecodeBytes(Span(encoded_png.bitstreams.front()), - ColorHints(), SizeConstraints(), &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); diff --git a/media/libjxl/src/lib/extras/common.cc b/media/libjxl/src/lib/extras/common.cc new file mode 100644 index 0000000000..56abd13ea6 --- /dev/null +++ b/media/libjxl/src/lib/extras/common.cc @@ -0,0 +1,62 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "lib/extras/common.h" + +#include +#include + +#include +#include + +#include "lib/extras/packed_image.h" +#include "lib/jxl/base/printf_macros.h" +#include "lib/jxl/base/status.h" + +namespace jxl { +namespace extras { + +Status SelectFormat(const std::vector& accepted_formats, + const JxlBasicInfo& basic_info, JxlPixelFormat* format) { + const size_t original_bit_depth = basic_info.bits_per_sample; + size_t current_bit_depth = 0; + size_t num_alpha_channels = (basic_info.alpha_bits != 0 ? 1 : 0); + size_t num_channels = basic_info.num_color_channels + num_alpha_channels; + for (;;) { + for (const JxlPixelFormat& candidate : accepted_formats) { + if (candidate.num_channels != num_channels) continue; + JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(candidate.data_type)); + const size_t candidate_bit_depth = + PackedImage::BitsPerChannel(candidate.data_type); + if ( + // Candidate bit depth is less than what we have and still enough + (original_bit_depth <= candidate_bit_depth && + candidate_bit_depth < current_bit_depth) || + // Or larger than the too-small bit depth we currently have + (current_bit_depth < candidate_bit_depth && + current_bit_depth < original_bit_depth)) { + *format = candidate; + current_bit_depth = candidate_bit_depth; + } + } + if (current_bit_depth == 0) { + if (num_channels > basic_info.num_color_channels) { + // Try dropping the alpha channel. + --num_channels; + continue; + } + return JXL_FAILURE("no appropriate format found"); + } + break; + } + if (current_bit_depth < original_bit_depth) { + JXL_WARNING("encoding %" PRIuS "-bit original to %" PRIuS " bits", + original_bit_depth, current_bit_depth); + } + return true; +} + +} // namespace extras +} // namespace jxl diff --git a/media/libjxl/src/lib/extras/common.h b/media/libjxl/src/lib/extras/common.h new file mode 100644 index 0000000000..88ed581af3 --- /dev/null +++ b/media/libjxl/src/lib/extras/common.h @@ -0,0 +1,26 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_EXTRAS_COMMON_H_ +#define LIB_EXTRAS_COMMON_H_ + +#include +#include + +#include + +#include "lib/jxl/base/status.h" + +namespace jxl { +namespace extras { + +// TODO(sboukortt): consider exposing this as part of the C API. +Status SelectFormat(const std::vector& accepted_formats, + const JxlBasicInfo& basic_info, JxlPixelFormat* format); + +} // namespace extras +} // namespace jxl + +#endif // LIB_EXTRAS_COMMON_H_ diff --git a/media/libjxl/src/lib/extras/compressed_icc.cc b/media/libjxl/src/lib/extras/compressed_icc.cc new file mode 100644 index 0000000000..52d433d4f9 --- /dev/null +++ b/media/libjxl/src/lib/extras/compressed_icc.cc @@ -0,0 +1,53 @@ +// 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 "lib/jxl/base/span.h" +#include "lib/jxl/enc_aux_out.h" +#include "lib/jxl/enc_icc_codec.h" +#include "lib/jxl/icc_codec.h" + +JXL_BOOL JxlICCProfileEncode(const JxlMemoryManager* memory_manager, + const uint8_t* icc, size_t icc_size, + uint8_t** compressed_icc, + size_t* compressed_icc_size) { + JxlMemoryManager local_memory_manager; + if (!jxl::MemoryManagerInit(&local_memory_manager, memory_manager)) { + return JXL_FALSE; + } + jxl::BitWriter writer(&local_memory_manager); + JXL_RETURN_IF_ERROR(jxl::WriteICC(jxl::Span(icc, icc_size), + &writer, jxl::LayerType::Header, nullptr)); + writer.ZeroPadToByte(); + jxl::Bytes bytes = writer.GetSpan(); + *compressed_icc_size = bytes.size(); + *compressed_icc = static_cast( + jxl::MemoryManagerAlloc(&local_memory_manager, *compressed_icc_size)); + memcpy(*compressed_icc, bytes.data(), bytes.size()); + return JXL_TRUE; +} + +JXL_BOOL JxlICCProfileDecode(const JxlMemoryManager* memory_manager, + const uint8_t* compressed_icc, + size_t compressed_icc_size, uint8_t** icc, + size_t* icc_size) { + JxlMemoryManager local_memory_manager; + if (!jxl::MemoryManagerInit(&local_memory_manager, memory_manager)) { + return JXL_FALSE; + } + jxl::ICCReader icc_reader(&local_memory_manager); + jxl::PaddedBytes decompressed(&local_memory_manager); + jxl::BitReader bit_reader( + jxl::Span(compressed_icc, compressed_icc_size)); + JXL_RETURN_IF_ERROR(icc_reader.Init(&bit_reader)); + JXL_RETURN_IF_ERROR(icc_reader.Process(&bit_reader, &decompressed)); + JXL_RETURN_IF_ERROR(bit_reader.Close()); + *icc_size = decompressed.size(); + *icc = static_cast( + jxl::MemoryManagerAlloc(&local_memory_manager, *icc_size)); + memcpy(*icc, decompressed.data(), *icc_size); + return JXL_TRUE; +} diff --git a/media/libjxl/src/lib/extras/compressed_icc_test.cc b/media/libjxl/src/lib/extras/compressed_icc_test.cc new file mode 100644 index 0000000000..2aa994d6e6 --- /dev/null +++ b/media/libjxl/src/lib/extras/compressed_icc_test.cc @@ -0,0 +1,47 @@ +// 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 "jxl/compressed_icc.h" + +#include + +#include +#include +#include + +#include "lib/jxl/color_encoding_internal.h" +#include "lib/jxl/test_memory_manager.h" +#include "lib/jxl/test_utils.h" +#include "lib/jxl/testing.h" + +namespace jxl { +namespace { + +TEST(CompressedIccTest, Roundtrip) { + JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); + uint8_t* compressed_icc; + size_t compressed_icc_size; + const IccBytes icc = jxl::test::GetIccTestProfile(); + ASSERT_TRUE(JxlICCProfileEncode(memory_manager, icc.data(), icc.size(), + &compressed_icc, &compressed_icc_size)); + + EXPECT_LT(compressed_icc_size, icc.size()); + + uint8_t* decompressed_icc; + size_t decompressed_icc_size; + ASSERT_TRUE(JxlICCProfileDecode(memory_manager, compressed_icc, + compressed_icc_size, &decompressed_icc, + &decompressed_icc_size)); + + ASSERT_EQ(decompressed_icc_size, icc.size()); + + EXPECT_EQ(0, memcmp(decompressed_icc, icc.data(), decompressed_icc_size)); + + memory_manager->free(memory_manager->opaque, compressed_icc); + memory_manager->free(memory_manager->opaque, decompressed_icc); +} + +} // namespace +} // namespace jxl diff --git a/media/libjxl/src/lib/extras/dec/apng.cc b/media/libjxl/src/lib/extras/dec/apng.cc index 566746665d..24b4795d03 100644 --- a/media/libjxl/src/lib/extras/dec/apng.cc +++ b/media/libjxl/src/lib/extras/dec/apng.cc @@ -36,273 +36,490 @@ * */ -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include -#include "jxl/codestream_header.h" -#include "jxl/encode.h" +#include "lib/extras/packed_image.h" +#include "lib/extras/size_constraints.h" +#include "lib/jxl/base/byte_order.h" +#include "lib/jxl/base/common.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/printf_macros.h" -#include "lib/jxl/base/scope_guard.h" -#include "lib/jxl/common.h" -#include "lib/jxl/sanitizers.h" +#include "lib/jxl/base/rect.h" +#include "lib/jxl/base/sanitizers.h" +#include "lib/jxl/base/span.h" +#include "lib/jxl/base/status.h" +#if JPEGXL_ENABLE_APNG #include "png.h" /* original (unpatched) libpng is ok */ +#endif namespace jxl { namespace extras { +#if !JPEGXL_ENABLE_APNG + +bool CanDecodeAPNG() { return false; } +Status DecodeImageAPNG(const Span bytes, + const ColorHints& color_hints, PackedPixelFile* ppf, + const SizeConstraints* constraints) { + return false; +} + +#else // JPEGXL_ENABLE_APNG + namespace { -/* hIST chunk tail is not proccesed properly; skip this chunk completely; - see https://github.com/glennrp/libpng/pull/413 */ -const png_byte kIgnoredPngChunks[] = { - 104, 73, 83, 84, '\0' /* hIST */ -}; +constexpr std::array kPngSignature = {137, 'P', 'N', 'G', + '\r', '\n', 26, '\n'}; // Returns floating-point value from the PNG encoding (times 10^5). -static double F64FromU32(const uint32_t x) { - return static_cast(x) * 1E-5; -} +double F64FromU32(const uint32_t x) { return static_cast(x) * 1E-5; } -Status DecodeSRGB(const unsigned char* payload, const size_t payload_size, - JxlColorEncoding* color_encoding) { - if (payload_size != 1) return JXL_FAILURE("Wrong sRGB size"); +/** Extract information from 'sRGB' chunk. */ +Status DecodeSrgbChunk(const Bytes payload, JxlColorEncoding* color_encoding) { + if (payload.size() != 1) return JXL_FAILURE("Wrong sRGB size"); + uint8_t ri = payload[0]; // (PNG uses the same values as ICC.) - if (payload[0] >= 4) return JXL_FAILURE("Invalid Rendering Intent"); - color_encoding->rendering_intent = - static_cast(payload[0]); + if (ri >= 4) return JXL_FAILURE("Invalid Rendering Intent"); + color_encoding->white_point = JXL_WHITE_POINT_D65; + color_encoding->primaries = JXL_PRIMARIES_SRGB; + color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_SRGB; + color_encoding->rendering_intent = static_cast(ri); return true; } -Status DecodeGAMA(const unsigned char* payload, const size_t payload_size, - JxlColorEncoding* color_encoding) { - if (payload_size != 4) return JXL_FAILURE("Wrong gAMA size"); +/** + * Extract information from 'cICP' chunk. + * + * If the cICP profile is not fully supported, return `false` and leave + * `color_encoding` unmodified. + */ +Status DecodeCicpChunk(const Bytes payload, JxlColorEncoding* color_encoding) { + if (payload.size() != 4) return JXL_FAILURE("Wrong cICP size"); + JxlColorEncoding color_enc = *color_encoding; + + // From https://www.itu.int/rec/T-REC-H.273-202107-I/en + if (payload[0] == 1) { + // IEC 61966-2-1 sRGB + color_enc.primaries = JXL_PRIMARIES_SRGB; + color_enc.white_point = JXL_WHITE_POINT_D65; + } else if (payload[0] == 4) { + // Rec. ITU-R BT.470-6 System M + color_enc.primaries = JXL_PRIMARIES_CUSTOM; + color_enc.primaries_red_xy[0] = 0.67; + color_enc.primaries_red_xy[1] = 0.33; + color_enc.primaries_green_xy[0] = 0.21; + color_enc.primaries_green_xy[1] = 0.71; + color_enc.primaries_blue_xy[0] = 0.14; + color_enc.primaries_blue_xy[1] = 0.08; + color_enc.white_point = JXL_WHITE_POINT_CUSTOM; + color_enc.white_point_xy[0] = 0.310; + color_enc.white_point_xy[1] = 0.316; + } else if (payload[0] == 5) { + // Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM + color_enc.primaries = JXL_PRIMARIES_CUSTOM; + color_enc.primaries_red_xy[0] = 0.64; + color_enc.primaries_red_xy[1] = 0.33; + color_enc.primaries_green_xy[0] = 0.29; + color_enc.primaries_green_xy[1] = 0.60; + color_enc.primaries_blue_xy[0] = 0.15; + color_enc.primaries_blue_xy[1] = 0.06; + color_enc.white_point = JXL_WHITE_POINT_D65; + } else if (payload[0] == 6 || payload[0] == 7) { + // SMPTE ST 170 (2004) / SMPTE ST 240 (1999) + color_enc.primaries = JXL_PRIMARIES_CUSTOM; + color_enc.primaries_red_xy[0] = 0.630; + color_enc.primaries_red_xy[1] = 0.340; + color_enc.primaries_green_xy[0] = 0.310; + color_enc.primaries_green_xy[1] = 0.595; + color_enc.primaries_blue_xy[0] = 0.155; + color_enc.primaries_blue_xy[1] = 0.070; + color_enc.white_point = JXL_WHITE_POINT_D65; + } else if (payload[0] == 8) { + // Generic film (colour filters using Illuminant C) + color_enc.primaries = JXL_PRIMARIES_CUSTOM; + color_enc.primaries_red_xy[0] = 0.681; + color_enc.primaries_red_xy[1] = 0.319; + color_enc.primaries_green_xy[0] = 0.243; + color_enc.primaries_green_xy[1] = 0.692; + color_enc.primaries_blue_xy[0] = 0.145; + color_enc.primaries_blue_xy[1] = 0.049; + color_enc.white_point = JXL_WHITE_POINT_CUSTOM; + color_enc.white_point_xy[0] = 0.310; + color_enc.white_point_xy[1] = 0.316; + } else if (payload[0] == 9) { + // Rec. ITU-R BT.2100-2 + color_enc.primaries = JXL_PRIMARIES_2100; + color_enc.white_point = JXL_WHITE_POINT_D65; + } else if (payload[0] == 10) { + // CIE 1931 XYZ + color_enc.primaries = JXL_PRIMARIES_CUSTOM; + color_enc.primaries_red_xy[0] = 1; + color_enc.primaries_red_xy[1] = 0; + color_enc.primaries_green_xy[0] = 0; + color_enc.primaries_green_xy[1] = 1; + color_enc.primaries_blue_xy[0] = 0; + color_enc.primaries_blue_xy[1] = 0; + color_enc.white_point = JXL_WHITE_POINT_E; + } else if (payload[0] == 11) { + // SMPTE RP 431-2 (2011) + color_enc.primaries = JXL_PRIMARIES_P3; + color_enc.white_point = JXL_WHITE_POINT_DCI; + } else if (payload[0] == 12) { + // SMPTE EG 432-1 (2010) + color_enc.primaries = JXL_PRIMARIES_P3; + color_enc.white_point = JXL_WHITE_POINT_D65; + } else if (payload[0] == 22) { + color_enc.primaries = JXL_PRIMARIES_CUSTOM; + color_enc.primaries_red_xy[0] = 0.630; + color_enc.primaries_red_xy[1] = 0.340; + color_enc.primaries_green_xy[0] = 0.295; + color_enc.primaries_green_xy[1] = 0.605; + color_enc.primaries_blue_xy[0] = 0.155; + color_enc.primaries_blue_xy[1] = 0.077; + color_enc.white_point = JXL_WHITE_POINT_D65; + } else { + JXL_WARNING("Unsupported primaries specified in cICP chunk: %d", + static_cast(payload[0])); + return false; + } + + if (payload[1] == 1 || payload[1] == 6 || payload[1] == 14 || + payload[1] == 15) { + // Rec. ITU-R BT.709-6 + color_enc.transfer_function = JXL_TRANSFER_FUNCTION_709; + } else if (payload[1] == 4) { + // Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM + color_enc.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA; + color_enc.gamma = 1 / 2.2; + } else if (payload[1] == 5) { + // Rec. ITU-R BT.470-6 System B, G + color_enc.transfer_function = JXL_TRANSFER_FUNCTION_GAMMA; + color_enc.gamma = 1 / 2.8; + } else if (payload[1] == 8 || payload[1] == 13 || payload[1] == 16 || + payload[1] == 17 || payload[1] == 18) { + // These codes all match the corresponding JXL enum values + color_enc.transfer_function = static_cast(payload[1]); + } else { + JXL_WARNING("Unsupported transfer function specified in cICP chunk: %d", + static_cast(payload[1])); + return false; + } + + if (payload[2] != 0) { + JXL_WARNING("Unsupported color space specified in cICP chunk: %d", + static_cast(payload[2])); + return false; + } + if (payload[3] != 1) { + JXL_WARNING("Unsupported full-range flag specified in cICP chunk: %d", + static_cast(payload[3])); + return false; + } + // cICP has no rendering intent, so use the default + color_enc.rendering_intent = JXL_RENDERING_INTENT_RELATIVE; + *color_encoding = color_enc; + return true; +} + +/** Extract information from 'gAMA' chunk. */ +Status DecodeGamaChunk(Bytes payload, JxlColorEncoding* color_encoding) { + if (payload.size() != 4) return JXL_FAILURE("Wrong gAMA size"); color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA; - color_encoding->gamma = F64FromU32(LoadBE32(payload)); + color_encoding->gamma = F64FromU32(LoadBE32(payload.data())); return true; } -Status DecodeCHRM(const unsigned char* payload, const size_t payload_size, - JxlColorEncoding* color_encoding) { - if (payload_size != 32) return JXL_FAILURE("Wrong cHRM size"); - +/** Extract information from 'cHTM' chunk. */ +Status DecodeChrmChunk(Bytes payload, JxlColorEncoding* color_encoding) { + if (payload.size() != 32) return JXL_FAILURE("Wrong cHRM size"); + const uint8_t* data = payload.data(); color_encoding->white_point = JXL_WHITE_POINT_CUSTOM; - color_encoding->white_point_xy[0] = F64FromU32(LoadBE32(payload + 0)); - color_encoding->white_point_xy[1] = F64FromU32(LoadBE32(payload + 4)); + color_encoding->white_point_xy[0] = F64FromU32(LoadBE32(data + 0)); + color_encoding->white_point_xy[1] = F64FromU32(LoadBE32(data + 4)); color_encoding->primaries = JXL_PRIMARIES_CUSTOM; - color_encoding->primaries_red_xy[0] = F64FromU32(LoadBE32(payload + 8)); - color_encoding->primaries_red_xy[1] = F64FromU32(LoadBE32(payload + 12)); - color_encoding->primaries_green_xy[0] = F64FromU32(LoadBE32(payload + 16)); - color_encoding->primaries_green_xy[1] = F64FromU32(LoadBE32(payload + 20)); - color_encoding->primaries_blue_xy[0] = F64FromU32(LoadBE32(payload + 24)); - color_encoding->primaries_blue_xy[1] = F64FromU32(LoadBE32(payload + 28)); + color_encoding->primaries_red_xy[0] = F64FromU32(LoadBE32(data + 8)); + color_encoding->primaries_red_xy[1] = F64FromU32(LoadBE32(data + 12)); + color_encoding->primaries_green_xy[0] = F64FromU32(LoadBE32(data + 16)); + color_encoding->primaries_green_xy[1] = F64FromU32(LoadBE32(data + 20)); + color_encoding->primaries_blue_xy[0] = F64FromU32(LoadBE32(data + 24)); + color_encoding->primaries_blue_xy[1] = F64FromU32(LoadBE32(data + 28)); return true; } -// Retrieves XMP and EXIF/IPTC from itext and text. -class BlobsReaderPNG { - public: - static Status Decode(const png_text_struct& info, PackedMetadata* metadata) { - // We trust these are properly null-terminated by libpng. - const char* key = info.key; - const char* value = info.text; - if (strstr(key, "XML:com.adobe.xmp")) { - metadata->xmp.resize(strlen(value)); // safe, see above - memcpy(metadata->xmp.data(), value, metadata->xmp.size()); - } +/** Extracts information from 'cLLi' chunk. */ +Status DecodeClliChunk(Bytes payload, float* max_content_light_level) { + if (payload.size() != 8) return JXL_FAILURE("Wrong cLLi size"); + const uint8_t* data = payload.data(); + const uint32_t maxcll_png = + Clamp1(png_get_uint_32(data), uint32_t{0}, uint32_t{10000 * 10000}); + // Ignore MaxFALL value. + *max_content_light_level = static_cast(maxcll_png) / 10000.f; + return true; +} - std::string type; - std::vector bytes; - - // Handle text chunks annotated with key "Raw profile type ####", with - // #### a type, which may contain metadata. - const char* kKey = "Raw profile type "; - if (strncmp(key, kKey, strlen(kKey)) != 0) return false; - - if (!MaybeDecodeBase16(key, value, &type, &bytes)) { - JXL_WARNING("Couldn't parse 'Raw format type' text chunk"); - return false; - } - if (type == "exif") { - if (!metadata->exif.empty()) { - JXL_WARNING("overwriting EXIF (%" PRIuS " bytes) with base16 (%" PRIuS - " bytes)", - metadata->exif.size(), bytes.size()); - } - metadata->exif = std::move(bytes); - } else if (type == "iptc") { - // TODO (jon): Deal with IPTC in some way - } else if (type == "8bim") { - // TODO (jon): Deal with 8bim in some way - } else if (type == "xmp") { - if (!metadata->xmp.empty()) { - JXL_WARNING("overwriting XMP (%" PRIuS " bytes) with base16 (%" PRIuS - " bytes)", - metadata->xmp.size(), bytes.size()); - } - metadata->xmp = std::move(bytes); - } else { - JXL_WARNING("Unknown type in 'Raw format type' text chunk: %s: %" PRIuS - " bytes", - type.c_str(), bytes.size()); - } - return true; +/** Returns false if invalid. */ +JXL_INLINE Status DecodeHexNibble(const char c, uint32_t* JXL_RESTRICT nibble) { + if ('a' <= c && c <= 'f') { + *nibble = 10 + c - 'a'; + } else if ('0' <= c && c <= '9') { + *nibble = c - '0'; + } else { + *nibble = 0; + return JXL_FAILURE("Invalid metadata nibble"); } + JXL_ENSURE(*nibble < 16); + return true; +} - private: - // Returns false if invalid. - static JXL_INLINE Status DecodeNibble(const char c, - uint32_t* JXL_RESTRICT nibble) { - if ('a' <= c && c <= 'f') { - *nibble = 10 + c - 'a'; - } else if ('0' <= c && c <= '9') { - *nibble = c - '0'; - } else { - *nibble = 0; - return JXL_FAILURE("Invalid metadata nibble"); - } - JXL_ASSERT(*nibble < 16); - return true; - } - - // Returns false if invalid. - static JXL_INLINE Status DecodeDecimal(const char** pos, const char* end, - uint32_t* JXL_RESTRICT value) { - size_t len = 0; - *value = 0; - while (*pos < end) { - char next = **pos; - if (next >= '0' && next <= '9') { - *value = (*value * 10) + static_cast(next - '0'); - len++; - if (len > 8) { - break; - } - } else { - // Do not consume terminator (non-decimal digit). +/** Returns false if invalid. */ +JXL_INLINE Status DecodeDecimal(const char** pos, const char* end, + uint32_t* JXL_RESTRICT value) { + size_t len = 0; + *value = 0; + while (*pos < end) { + char next = **pos; + if (next >= '0' && next <= '9') { + *value = (*value * 10) + static_cast(next - '0'); + len++; + if (len > 8) { break; } - (*pos)++; + } else { + // Do not consume terminator (non-decimal digit). + break; } - if (len == 0 || len > 8) { - return JXL_FAILURE("Failed to parse decimal"); + (*pos)++; + } + if (len == 0 || len > 8) { + return JXL_FAILURE("Failed to parse decimal"); + } + return true; +} + +/** + * Parses a PNG text chunk with key of the form "Raw profile type ####", with + * #### a type. + * + * Returns whether it could successfully parse the content. + * We trust key and encoded are null-terminated because they come from + * libpng. + */ +Status MaybeDecodeBase16(const char* key, const char* encoded, + std::string* type, std::vector* bytes) { + const char* encoded_end = encoded + strlen(encoded); + + const char* kKey = "Raw profile type "; + if (strncmp(key, kKey, strlen(kKey)) != 0) return false; + *type = key + strlen(kKey); + const size_t kMaxTypeLen = 20; + if (type->length() > kMaxTypeLen) return false; // Type too long + + // Header: freeform string and number of bytes + // Expected format is: + // \n + // profile name/description\n + // 40\n (the number of bytes after hex-decoding) + // 01234566789abcdef....\n (72 bytes per line max). + // 012345667\n (last line) + const char* pos = encoded; + + if (*(pos++) != '\n') return false; + while (pos < encoded_end && *pos != '\n') { + pos++; + } + if (pos == encoded_end) return false; + // We parsed so far a \n, some number of non \n characters and are now + // pointing at a \n. + if (*(pos++) != '\n') return false; + // Skip leading spaces + while (pos < encoded_end && *pos == ' ') { + pos++; + } + uint32_t bytes_to_decode = 0; + JXL_RETURN_IF_ERROR(DecodeDecimal(&pos, encoded_end, &bytes_to_decode)); + + // We need 2*bytes for the hex values plus 1 byte every 36 values, + // plus terminal \n for length. + size_t tail = static_cast(encoded_end - pos); + bool ok = ((tail / 2) >= bytes_to_decode); + if (ok) tail -= 2 * static_cast(bytes_to_decode); + ok = ok && (tail == 1 + DivCeil(bytes_to_decode, 36)); + if (!ok) { + return JXL_FAILURE("Not enough bytes to parse %d bytes in hex", + bytes_to_decode); + } + JXL_ENSURE(bytes->empty()); + bytes->reserve(bytes_to_decode); + + // Encoding: base16 with newline after 72 chars. + // pos points to the \n before the first line of hex values. + for (size_t i = 0; i < bytes_to_decode; ++i) { + if (i % 36 == 0) { + if (pos + 1 >= encoded_end) return false; // Truncated base16 1 + if (*pos != '\n') return false; // Expected newline + ++pos; } - return true; + + if (pos + 2 >= encoded_end) return false; // Truncated base16 2; + uint32_t nibble0; + uint32_t nibble1; + JXL_RETURN_IF_ERROR(DecodeHexNibble(pos[0], &nibble0)); + JXL_RETURN_IF_ERROR(DecodeHexNibble(pos[1], &nibble1)); + bytes->push_back(static_cast((nibble0 << 4) + nibble1)); + pos += 2; + } + if (pos + 1 != encoded_end) return false; // Too many encoded bytes + if (pos[0] != '\n') return false; // Incorrect metadata terminator + return true; +} + +/** Retrieves XMP and EXIF/IPTC from itext and text. */ +Status DecodeBlob(const png_text_struct& info, PackedMetadata* metadata) { + // We trust these are properly null-terminated by libpng. + const char* key = info.key; + const char* value = info.text; + if (strstr(key, "XML:com.adobe.xmp")) { + metadata->xmp.resize(strlen(value)); // safe, see above + memcpy(metadata->xmp.data(), value, metadata->xmp.size()); } - // Parses a PNG text chunk with key of the form "Raw profile type ####", with - // #### a type. - // Returns whether it could successfully parse the content. - // We trust key and encoded are null-terminated because they come from - // libpng. - static Status MaybeDecodeBase16(const char* key, const char* encoded, - std::string* type, - std::vector* bytes) { - const char* encoded_end = encoded + strlen(encoded); + std::string type; + std::vector bytes; - const char* kKey = "Raw profile type "; - if (strncmp(key, kKey, strlen(kKey)) != 0) return false; - *type = key + strlen(kKey); - const size_t kMaxTypeLen = 20; - if (type->length() > kMaxTypeLen) return false; // Type too long + // Handle text chunks annotated with key "Raw profile type ####", with + // #### a type, which may contain metadata. + const char* kKey = "Raw profile type "; + if (strncmp(key, kKey, strlen(kKey)) != 0) return false; - // Header: freeform string and number of bytes - // Expected format is: - // \n - // profile name/description\n - // 40\n (the number of bytes after hex-decoding) - // 01234566789abcdef....\n (72 bytes per line max). - // 012345667\n (last line) - const char* pos = encoded; - - if (*(pos++) != '\n') return false; - while (pos < encoded_end && *pos != '\n') { - pos++; - } - if (pos == encoded_end) return false; - // We parsed so far a \n, some number of non \n characters and are now - // pointing at a \n. - if (*(pos++) != '\n') return false; - uint32_t bytes_to_decode = 0; - JXL_RETURN_IF_ERROR(DecodeDecimal(&pos, encoded_end, &bytes_to_decode)); - - // We need 2*bytes for the hex values plus 1 byte every 36 values, - // plus terminal \n for length. - const unsigned long needed_bytes = - bytes_to_decode * 2 + 1 + DivCeil(bytes_to_decode, 36); - if (needed_bytes != static_cast(encoded_end - pos)) { - return JXL_FAILURE("Not enough bytes to parse %d bytes in hex", - bytes_to_decode); - } - JXL_ASSERT(bytes->empty()); - bytes->reserve(bytes_to_decode); - - // Encoding: base16 with newline after 72 chars. - // pos points to the \n before the first line of hex values. - for (size_t i = 0; i < bytes_to_decode; ++i) { - if (i % 36 == 0) { - if (pos + 1 >= encoded_end) return false; // Truncated base16 1 - if (*pos != '\n') return false; // Expected newline - ++pos; - } - - if (pos + 2 >= encoded_end) return false; // Truncated base16 2; - uint32_t nibble0, nibble1; - JXL_RETURN_IF_ERROR(DecodeNibble(pos[0], &nibble0)); - JXL_RETURN_IF_ERROR(DecodeNibble(pos[1], &nibble1)); - bytes->push_back(static_cast((nibble0 << 4) + nibble1)); - pos += 2; - } - if (pos + 1 != encoded_end) return false; // Too many encoded bytes - if (pos[0] != '\n') return false; // Incorrect metadata terminator - return true; + if (!MaybeDecodeBase16(key, value, &type, &bytes)) { + JXL_WARNING("Couldn't parse 'Raw format type' text chunk"); + return false; } -}; + if (type == "exif") { + // Remove prefix if present. + constexpr std::array kExifPrefix = {'E', 'x', 'i', 'f', 0, 0}; + if (bytes.size() >= kExifPrefix.size() && + memcmp(bytes.data(), kExifPrefix.data(), kExifPrefix.size()) == 0) { + bytes.erase(bytes.begin(), bytes.begin() + kExifPrefix.size()); + } + if (!metadata->exif.empty()) { + JXL_DEBUG_V(2, + "overwriting EXIF (%" PRIuS " bytes) with base16 (%" PRIuS + " bytes)", + metadata->exif.size(), bytes.size()); + } + metadata->exif = std::move(bytes); + } else if (type == "iptc") { + // TODO(jon): Deal with IPTC in some way + } else if (type == "8bim") { + // TODO(jon): Deal with 8bim in some way + } else if (type == "xmp") { + if (!metadata->xmp.empty()) { + JXL_DEBUG_V(2, + "overwriting XMP (%" PRIuS " bytes) with base16 (%" PRIuS + " bytes)", + metadata->xmp.size(), bytes.size()); + } + metadata->xmp = std::move(bytes); + } else { + JXL_DEBUG_V( + 2, "Unknown type in 'Raw format type' text chunk: %s: %" PRIuS " bytes", + type.c_str(), bytes.size()); + } + return true; +} constexpr bool isAbc(char c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } -constexpr uint32_t kId_IHDR = 0x52444849; -constexpr uint32_t kId_acTL = 0x4C546361; -constexpr uint32_t kId_fcTL = 0x4C546366; -constexpr uint32_t kId_IDAT = 0x54414449; -constexpr uint32_t kId_fdAT = 0x54416466; -constexpr uint32_t kId_IEND = 0x444E4549; -constexpr uint32_t kId_iCCP = 0x50434369; -constexpr uint32_t kId_sRGB = 0x42475273; -constexpr uint32_t kId_gAMA = 0x414D4167; -constexpr uint32_t kId_cHRM = 0x4D524863; -constexpr uint32_t kId_eXIf = 0x66495865; +/** Wrap 4-char tag name into ID. */ +constexpr uint32_t MakeTag(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { + return a | (b << 8) | (c << 16) | (d << 24); +} -struct APNGFrame { - std::vector pixels; +/** Reusable image data container. */ +struct Pixels { + // Use array instead of vector to avoid memory initialization. + std::unique_ptr pixels; + size_t pixels_size = 0; std::vector rows; - unsigned int w, h, delay_num, delay_den; -}; + std::atomic has_error{false}; -struct Reader { - const uint8_t* next; - const uint8_t* last; - bool Read(void* data, size_t len) { - size_t cap = last - next; - size_t to_copy = std::min(cap, len); - memcpy(data, next, to_copy); - next += to_copy; - return (len == to_copy); + Status Resize(size_t row_bytes, size_t num_rows) { + size_t new_size = row_bytes * num_rows; // it is assumed size is sane + if (new_size > pixels_size) { + pixels.reset(new uint8_t[new_size]); + if (!pixels) { + // TODO(szabadka): use specialized OOM error code + return JXL_FAILURE("Failed to allocate memory for image buffer"); + } + pixels_size = new_size; + } + rows.resize(num_rows); + for (size_t y = 0; y < num_rows; y++) { + rows[y] = pixels.get() + y * row_bytes; + } + return true; } - bool Eof() { return next == last; } }; -const unsigned long cMaxPNGSize = 1000000UL; -const size_t kMaxPNGChunkSize = 1lu << 30; // 1 GB +/** + * Helper that chunks in-memory input. + */ +struct Reader { + explicit Reader(Span data) : data_(data) {} -void info_fn(png_structp png_ptr, png_infop info_ptr) { + const Span data_; + size_t offset_ = 0; + + Bytes Peek(size_t len) const { + size_t cap = data_.size() - offset_; + size_t to_copy = std::min(cap, len); + return {data_.data() + offset_, to_copy}; + } + + Bytes Read(size_t len) { + Bytes result = Peek(len); + offset_ += result.size(); + return result; + } + + /* Returns empty Span on error. */ + Bytes ReadChunk() { + Bytes len = Peek(4); + if (len.size() != 4) { + return Bytes(); + } + const auto size = png_get_uint_32(len.data()); + // NB: specification allows 2^31 - 1 + constexpr size_t kMaxPNGChunkSize = 1u << 30; // 1 GB + // Check first, to avoid overflow. + if (size > kMaxPNGChunkSize) { + JXL_WARNING("APNG chunk size is too big"); + return Bytes(); + } + size_t full_size = size + 12; // size does not include itself, tag and CRC. + Bytes result = Read(full_size); + return (result.size() == full_size) ? result : Bytes(); + } + + bool Eof() const { return offset_ == data_.size(); } +}; + +void ProgressiveRead_OnInfo(png_structp png_ptr, png_infop info_ptr) { png_set_expand(png_ptr); png_set_palette_to_rgb(png_ptr); png_set_tRNS_to_alpha(png_ptr); @@ -310,281 +527,486 @@ void info_fn(png_structp png_ptr, png_infop info_ptr) { png_read_update_info(png_ptr, info_ptr); } -void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, - int pass) { - APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr); - JXL_CHECK(frame); - JXL_CHECK(row_num < frame->rows.size()); - JXL_CHECK(frame->rows[row_num] < frame->pixels.data() + frame->pixels.size()); +void ProgressiveRead_OnRow(png_structp png_ptr, png_bytep new_row, + png_uint_32 row_num, int pass) { + Pixels* frame = reinterpret_cast(png_get_progressive_ptr(png_ptr)); + if (!frame) { + JXL_DEBUG_ABORT("Internal logic error"); + return; + } + if (row_num >= frame->rows.size()) { + frame->has_error = true; + return; + } png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row); } -inline unsigned int read_chunk(Reader* r, std::vector* pChunk) { - unsigned char len[4]; - if (r->Read(&len, 4)) { - const auto size = png_get_uint_32(len); - // Check first, to avoid overflow. - if (size > kMaxPNGChunkSize) { - JXL_WARNING("APNG chunk size is too big"); - return 0; - } - pChunk->resize(size + 12); - memcpy(pChunk->data(), len, 4); - if (r->Read(pChunk->data() + 4, pChunk->size() - 4)) { - return LoadLE32(pChunk->data() + 4); - } - } - return 0; -} - -int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr, - bool hasInfo, std::vector& chunkIHDR, - std::vector>& chunksInfo) { - unsigned char header[8] = {137, 80, 78, 71, 13, 10, 26, 10}; - - png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - info_ptr = png_create_info_struct(png_ptr); - if (!png_ptr || !info_ptr) return 1; - - if (setjmp(png_jmpbuf(png_ptr))) { - return 1; +// Holds intermediate state during parsing APNG file. +struct Context { + ~Context() { + // Make sure png memory is released in any case. + ResetPngDecoder(); } - png_set_keep_unknown_chunks(png_ptr, 1, kIgnoredPngChunks, - (int)sizeof(kIgnoredPngChunks) / 5); - - png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); - png_set_progressive_read_fn(png_ptr, frame_ptr, info_fn, row_fn, NULL); - - png_process_data(png_ptr, info_ptr, header, 8); - png_process_data(png_ptr, info_ptr, chunkIHDR.data(), chunkIHDR.size()); - - if (hasInfo) { - for (unsigned int i = 0; i < chunksInfo.size(); i++) { - png_process_data(png_ptr, info_ptr, chunksInfo[i].data(), - chunksInfo[i].size()); - } - } - return 0; -} - -int processing_data(png_structp png_ptr, png_infop info_ptr, unsigned char* p, - unsigned int size) { - if (!png_ptr || !info_ptr) return 1; - - if (setjmp(png_jmpbuf(png_ptr))) { - return 1; + bool CreatePngDecoder() { + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, + nullptr); + info_ptr = png_create_info_struct(png_ptr); + return (png_ptr != nullptr && info_ptr != nullptr); } - png_process_data(png_ptr, info_ptr, p, size); - return 0; -} + /** + * Initialize PNG decoder. + * + * TODO(eustas): add details + */ + bool InitPngDecoder(const std::vector& chunksInfo, + const RectT& viewport) { + ResetPngDecoder(); -int processing_finish(png_structp png_ptr, png_infop info_ptr, - PackedMetadata* metadata) { - unsigned char footer[12] = {0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130}; - - if (!png_ptr || !info_ptr) return 1; - - if (setjmp(png_jmpbuf(png_ptr))) { - return 1; - } - - png_process_data(png_ptr, info_ptr, footer, 12); - // before destroying: check if we encountered any metadata chunks - png_textp text_ptr; - int num_text; - png_get_text(png_ptr, info_ptr, &text_ptr, &num_text); - for (int i = 0; i < num_text; i++) { - (void)BlobsReaderPNG::Decode(text_ptr[i], metadata); - } - - return 0; -} - -} // namespace - -Status DecodeImageAPNG(const Span bytes, - const ColorHints& color_hints, - const SizeConstraints& constraints, - PackedPixelFile* ppf) { - Reader r; - unsigned int id, j, w, h, w0, h0, x0, y0; - unsigned int delay_num, delay_den, dop, bop, rowbytes, imagesize; - unsigned char sig[8]; - png_structp png_ptr = nullptr; - png_infop info_ptr = nullptr; - std::vector chunk; - std::vector chunkIHDR; - std::vector> chunksInfo; - bool isAnimated = false; - bool hasInfo = false; - APNGFrame frameRaw = {}; - uint32_t num_channels; - JxlPixelFormat format; - unsigned int bytes_per_pixel = 0; - - struct FrameInfo { - PackedImage data; - uint32_t duration; - size_t x0, xsize; - size_t y0, ysize; - uint32_t dispose_op; - uint32_t blend_op; - }; - - std::vector frames; - - // Make sure png memory is released in any case. - auto scope_guard = MakeScopeGuard([&]() { - png_destroy_read_struct(&png_ptr, &info_ptr, 0); - // Just in case. Not all versions on libpng wipe-out the pointers. - png_ptr = nullptr; - info_ptr = nullptr; - }); - - r = {bytes.data(), bytes.data() + bytes.size()}; - // Not a PNG => not an error - unsigned char png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10}; - if (!r.Read(sig, 8) || memcmp(sig, png_signature, 8) != 0) { - return false; - } - id = read_chunk(&r, &chunkIHDR); - - ppf->info.exponent_bits_per_sample = 0; - ppf->info.alpha_exponent_bits = 0; - ppf->info.orientation = JXL_ORIENT_IDENTITY; - - ppf->frames.clear(); - - bool have_color = false, have_srgb = false; - bool errorstate = true; - if (id == kId_IHDR && chunkIHDR.size() == 25) { - x0 = 0; - y0 = 0; - delay_num = 1; - delay_den = 10; - dop = 0; - bop = 0; - - w0 = w = png_get_uint_32(chunkIHDR.data() + 8); - h0 = h = png_get_uint_32(chunkIHDR.data() + 12); - if (w > cMaxPNGSize || h > cMaxPNGSize) { + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, + nullptr); + info_ptr = png_create_info_struct(png_ptr); + if (png_ptr == nullptr || info_ptr == nullptr) { return false; } - // default settings in case e.g. only gAMA is given + if (setjmp(png_jmpbuf(png_ptr))) { + return false; + } + + /* hIST chunk tail is not processed properly; skip this chunk completely; + see https://github.com/glennrp/libpng/pull/413 */ + constexpr std::array kIgnoredChunks = {'h', 'I', 'S', 'T', 0}; + png_set_keep_unknown_chunks(png_ptr, 1, kIgnoredChunks.data(), + static_cast(kIgnoredChunks.size() / 5)); + + png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); + png_set_progressive_read_fn(png_ptr, static_cast(&frameRaw), + ProgressiveRead_OnInfo, ProgressiveRead_OnRow, + nullptr); + + png_process_data(png_ptr, info_ptr, + const_cast(kPngSignature.data()), + kPngSignature.size()); + + // Patch dimensions. + png_save_uint_32(ihdr.data() + 8, static_cast(viewport.xsize())); + png_save_uint_32(ihdr.data() + 12, static_cast(viewport.ysize())); + png_process_data(png_ptr, info_ptr, ihdr.data(), ihdr.size()); + + for (const auto& chunk : chunksInfo) { + png_process_data(png_ptr, info_ptr, const_cast(chunk.data()), + chunk.size()); + } + + return true; + } + + /** + * Pass chunk to PNG decoder. + */ + bool FeedChunks(const Bytes& chunk1, const Bytes& chunk2 = Bytes()) { + // TODO(eustas): turn to DCHECK + if (!png_ptr || !info_ptr) return false; + + if (setjmp(png_jmpbuf(png_ptr))) { + return false; + } + + for (const auto& chunk : {chunk1, chunk2}) { + if (!chunk.empty()) { + png_process_data(png_ptr, info_ptr, const_cast(chunk.data()), + chunk.size()); + } + } + return true; + } + + bool FinalizeStream(PackedMetadata* metadata) { + // TODO(eustas): turn to DCHECK + if (!png_ptr || !info_ptr) return false; + + if (setjmp(png_jmpbuf(png_ptr))) { + return false; + } + + const std::array kFooter = {0, 0, 0, 0, 73, 69, + 78, 68, 174, 66, 96, 130}; + png_process_data(png_ptr, info_ptr, const_cast(kFooter.data()), + kFooter.size()); + // before destroying: check if we encountered any metadata chunks + png_textp text_ptr = nullptr; + int num_text = 0; + if (png_get_text(png_ptr, info_ptr, &text_ptr, &num_text) != 0) { + msan::UnpoisonMemory(text_ptr, sizeof(png_text_struct) * num_text); + for (int i = 0; i < num_text; i++) { + Status result = DecodeBlob(text_ptr[i], metadata); + // Ignore unknown / malformed blob. + (void)result; + } + } + + return true; + } + + void ResetPngDecoder() { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + // Just in case. Not all versions on libpng wipe-out the pointers. + png_ptr = nullptr; + info_ptr = nullptr; + } + + std::array ihdr; // (modified) copy of file IHDR chunk + png_structp png_ptr = nullptr; + png_infop info_ptr = nullptr; + Pixels frameRaw = {}; +}; + +enum class DisposeOp : uint8_t { NONE = 0, BACKGROUND = 1, PREVIOUS = 2 }; + +constexpr uint8_t kLastDisposeOp = static_cast(DisposeOp::PREVIOUS); + +enum class BlendOp : uint8_t { SOURCE = 0, OVER = 1 }; + +constexpr uint8_t kLastBlendOp = static_cast(BlendOp::OVER); + +// fcTL +struct FrameControl { + uint32_t delay_num; + uint32_t delay_den; + RectT viewport; + DisposeOp dispose_op; + BlendOp blend_op; +}; + +struct Frame { + PackedImage pixels; + FrameControl metadata; +}; + +bool ValidateViewport(const RectT& r) { + constexpr uint32_t kMaxPngDim = 1000000UL; + return (r.xsize() <= kMaxPngDim) && (r.ysize() <= kMaxPngDim); +} + +/** + * Setup #channels, bpp, colorspace, etc. from PNG values. + */ +void SetColorData(PackedPixelFile* ppf, uint8_t color_type, uint8_t bit_depth, + png_color_8p sig_bits, uint32_t has_transparency) { + bool palette_used = ((color_type & 1) != 0); + bool color_used = ((color_type & 2) != 0); + bool alpha_channel_used = ((color_type & 4) != 0); + if (palette_used) { + if (!color_used || alpha_channel_used) { + JXL_DEBUG_V(2, "Unexpected PNG color type"); + } + } + + ppf->info.bits_per_sample = bit_depth; + + if (palette_used) { + // palette will actually be 8-bit regardless of the index bitdepth + ppf->info.bits_per_sample = 8; + } + if (color_used) { + ppf->info.num_color_channels = 3; ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB; - ppf->color_encoding.white_point = JXL_WHITE_POINT_D65; - ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB; - ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB; + if (sig_bits) { + if (sig_bits->red == sig_bits->green && + sig_bits->green == sig_bits->blue) { + ppf->info.bits_per_sample = sig_bits->red; + } else { + int maxbps = + std::max(sig_bits->red, std::max(sig_bits->green, sig_bits->blue)); + JXL_DEBUG_V(2, + "sBIT chunk: bit depths for R, G, and B are not the same " + "(%i %i %i), while in JPEG XL they have to be the same. " + "Setting RGB bit depth to %i.", + sig_bits->red, sig_bits->green, sig_bits->blue, maxbps); + ppf->info.bits_per_sample = maxbps; + } + } + } else { + ppf->info.num_color_channels = 1; + ppf->color_encoding.color_space = JXL_COLOR_SPACE_GRAY; + if (sig_bits) ppf->info.bits_per_sample = sig_bits->gray; + } + if (alpha_channel_used || has_transparency) { + ppf->info.alpha_bits = ppf->info.bits_per_sample; + if (sig_bits && sig_bits->alpha != ppf->info.bits_per_sample) { + JXL_DEBUG_V(2, + "sBIT chunk: bit depths for RGBA are inconsistent " + "(%i %i %i %i). Setting A bitdepth to %i.", + sig_bits->red, sig_bits->green, sig_bits->blue, + sig_bits->alpha, ppf->info.bits_per_sample); + } + } else { + ppf->info.alpha_bits = 0; + } + ppf->color_encoding.color_space = (ppf->info.num_color_channels == 1) + ? JXL_COLOR_SPACE_GRAY + : JXL_COLOR_SPACE_RGB; +} - if (!processing_start(png_ptr, info_ptr, (void*)&frameRaw, hasInfo, - chunkIHDR, chunksInfo)) { - while (!r.Eof()) { - id = read_chunk(&r, &chunk); - if (!id) break; +// Color profile chunks: cICP has the highest priority, followed by +// iCCP and sRGB (which shouldn't co-exist, but if they do, we use +// iCCP), followed finally by gAMA and cHRM. +enum class ColorInfoType { + NONE = 0, + GAMA_OR_CHRM = 1, + ICCP_OR_SRGB = 2, + CICP = 3 +}; - if (id == kId_acTL && !hasInfo && !isAnimated) { - isAnimated = true; - ppf->info.have_animation = true; - ppf->info.animation.tps_numerator = 1000; - ppf->info.animation.tps_denominator = 1; - } else if (id == kId_IEND || - (id == kId_fcTL && (!hasInfo || isAnimated))) { - if (hasInfo) { - if (!processing_finish(png_ptr, info_ptr, &ppf->metadata)) { - // Allocates the frame buffer. - uint32_t duration = delay_num * 1000 / delay_den; - frames.push_back(FrameInfo{PackedImage(w0, h0, format), duration, - x0, w0, y0, h0, dop, bop}); - auto& frame = frames.back().data; - for (size_t y = 0; y < h0; ++y) { - memcpy(static_cast(frame.pixels()) + frame.stride * y, - frameRaw.rows[y], bytes_per_pixel * w0); - } - } else { - break; - } - } +} // namespace - if (id == kId_IEND) { - errorstate = false; - break; - } - if (chunk.size() < 34) { - return JXL_FAILURE("Received a chunk that is too small (%" PRIuS - "B)", - chunk.size()); - } - // At this point the old frame is done. Let's start a new one. - w0 = png_get_uint_32(chunk.data() + 12); - h0 = png_get_uint_32(chunk.data() + 16); - x0 = png_get_uint_32(chunk.data() + 20); - y0 = png_get_uint_32(chunk.data() + 24); - delay_num = png_get_uint_16(chunk.data() + 28); - delay_den = png_get_uint_16(chunk.data() + 30); - dop = chunk[32]; - bop = chunk[33]; +bool CanDecodeAPNG() { return true; } - if (!delay_den) delay_den = 100; +/** + * Parse and decode PNG file. + * + * Useful PNG chunks: + * acTL : animation control (#frames, loop count) + * fcTL : frame control (seq#, viewport, delay, disposal blending) + * bKGD : preferable background + * IDAT : "default image" + * if single fcTL goes before IDAT, then it is also first frame + * fdAT : seq# + IDAT-like content + * PLTE : palette + * cICP : coding-independent code points for video signal type identification + * iCCP : embedded ICC profile + * sRGB : standard RGB colour space + * eXIf : exchangeable image file profile + * gAMA : image gamma + * cHRM : primary chromaticities and white point + * tRNS : transparency + * + * PNG chunk ordering: + * - IHDR first + * - IEND last + * - acTL, cHRM, cICP, gAMA, iCCP, sRGB, bKGD, eXIf, PLTE before IDAT + * - fdAT after IDAT + * + * More rules: + * - iCCP and sRGB are exclusive + * - fcTL and fdAT seq# must be in order fro 0, with no gaps or duplicates + * - fcTL before corresponding IDAT / fdAT + */ +Status DecodeImageAPNG(const Span bytes, + const ColorHints& color_hints, PackedPixelFile* ppf, + const SizeConstraints* constraints) { + // Initialize output (default settings in case e.g. only gAMA is given). + ppf->frames.clear(); + ppf->info.exponent_bits_per_sample = 0; + ppf->info.alpha_exponent_bits = 0; + ppf->info.orientation = JXL_ORIENT_IDENTITY; + ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB; + ppf->color_encoding.white_point = JXL_WHITE_POINT_D65; + ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB; + ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB; + ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_RELATIVE; - if (w0 > cMaxPNGSize || h0 > cMaxPNGSize || x0 > cMaxPNGSize || - y0 > cMaxPNGSize || x0 + w0 > w || y0 + h0 > h || dop > 2 || - bop > 1) { - break; - } + Reader input(bytes); - if (hasInfo) { - memcpy(chunkIHDR.data() + 8, chunk.data() + 12, 8); - if (processing_start(png_ptr, info_ptr, (void*)&frameRaw, hasInfo, - chunkIHDR, chunksInfo)) { - break; - } + // Check signature. + Bytes sig = input.Read(kPngSignature.size()); + if (sig.size() != 8 || + memcmp(sig.data(), kPngSignature.data(), kPngSignature.size()) != 0) { + return false; // Return silently if it is not a PNG + } + + // Check IHDR chunk. + Context ctx; + Bytes ihdr = input.ReadChunk(); + if (ihdr.size() != ctx.ihdr.size()) { + return JXL_FAILURE("Unexpected first chunk payload size"); + } + memcpy(ctx.ihdr.data(), ihdr.data(), ihdr.size()); + uint32_t id = LoadLE32(ihdr.data() + 4); + if (id != MakeTag('I', 'H', 'D', 'R')) { + return JXL_FAILURE("First chunk is not IHDR"); + } + const RectT image_rect(0, 0, png_get_uint_32(ihdr.data() + 8), + png_get_uint_32(ihdr.data() + 12)); + if (!ValidateViewport(image_rect)) { + return JXL_FAILURE("PNG image dimensions are too large"); + } + + // Chunks we supply to PNG decoder for every animation frame. + std::vector passthrough_chunks; + if (!ctx.InitPngDecoder(passthrough_chunks, image_rect)) { + return JXL_FAILURE("Failed to initialize PNG decoder"); + } + + // Marker that this PNG is animated. + bool seen_actl = false; + // First IDAT is a very important milestone; at this moment we freeze + // gathered metadata. + bool seen_idat = false; + // fCTL can occur multiple times, but only once before IDAT. + bool seen_fctl = false; + // Logical EOF. + bool seen_iend = false; + + ColorInfoType color_info_type = ColorInfoType::NONE; + + // Flag that we processed some IDAT / fDAT after image / frame start. + bool seen_pixel_data = false; + + uint32_t num_channels; + JxlPixelFormat format = {}; + size_t bytes_per_pixel = 0; + std::vector frames; + FrameControl current_frame = {/*delay_num=*/1, /*delay_den=*/10, image_rect, + DisposeOp::NONE, BlendOp::SOURCE}; + + // Copies frame pixels / metadata from temporary storage. + // TODO(eustas): avoid copying. + const auto finalize_frame = [&]() -> Status { + if (!seen_pixel_data) { + return JXL_FAILURE("Frame / image without fdAT / IDAT chunks"); + } + if (!ctx.FinalizeStream(&ppf->metadata)) { + return JXL_FAILURE("Failed to finalize PNG substream"); + } + if (ctx.frameRaw.has_error) { + return JXL_FAILURE("Internal error"); + } + // Allocates the frame buffer. + const RectT& vp = current_frame.viewport; + size_t xsize = static_cast(vp.xsize()); + size_t ysize = static_cast(vp.ysize()); + JXL_ASSIGN_OR_RETURN(PackedImage image, + PackedImage::Create(xsize, ysize, format)); + for (size_t y = 0; y < ysize; ++y) { + // TODO(eustas): ensure multiplication is safe + memcpy(static_cast(image.pixels()) + image.stride * y, + ctx.frameRaw.rows[y], bytes_per_pixel * xsize); + } + frames.push_back(Frame{std::move(image), current_frame}); + seen_pixel_data = false; + return true; + }; + + while (!input.Eof()) { + if (seen_iend) { + return JXL_FAILURE("Exuberant input after IEND chunk"); + } + Bytes chunk = input.ReadChunk(); + if (chunk.empty()) { + return JXL_FAILURE("Malformed chunk"); + } + Bytes type(chunk.data() + 4, 4); + id = LoadLE32(type.data()); + // Cut 'size' and 'type' at front and 'CRC' at the end. + Bytes payload(chunk.data() + 8, chunk.size() - 12); + + if (!isAbc(type[0]) || !isAbc(type[1]) || !isAbc(type[2]) || + !isAbc(type[3])) { + return JXL_FAILURE("Exotic PNG chunk"); + } + + switch (id) { + case MakeTag('a', 'c', 'T', 'L'): + if (seen_idat) { + JXL_DEBUG_V(2, "aCTL after IDAT ignored"); + continue; + } + if (seen_actl) { + JXL_DEBUG_V(2, "Duplicate aCTL chunk ignored"); + continue; + } + seen_actl = true; + ppf->info.have_animation = JXL_TRUE; + // TODO(eustas): decode from chunk? + ppf->info.animation.tps_numerator = 1000; + ppf->info.animation.tps_denominator = 1; + continue; + + case MakeTag('I', 'E', 'N', 'D'): + seen_iend = true; + JXL_RETURN_IF_ERROR(finalize_frame()); + continue; + + case MakeTag('f', 'c', 'T', 'L'): { + if (payload.size() != 26) { + return JXL_FAILURE("Unexpected fcTL payload size: %u", + static_cast(payload.size())); + } + if (seen_fctl && !seen_idat) { + return JXL_FAILURE("More than one fcTL before IDAT"); + } + if (seen_idat && !seen_actl) { + return JXL_FAILURE("fcTL after IDAT, but without acTL"); + } + seen_fctl = true; + + // TODO(eustas): check order? + // sequence_number = png_get_uint_32(payload.data()); + RectT raw_viewport(png_get_uint_32(payload.data() + 12), + png_get_uint_32(payload.data() + 16), + png_get_uint_32(payload.data() + 4), + png_get_uint_32(payload.data() + 8)); + uint8_t dispose_op = payload[24]; + if (dispose_op > kLastDisposeOp) { + return JXL_FAILURE("Invalid DisposeOp"); + } + uint8_t blend_op = payload[25]; + if (blend_op > kLastBlendOp) { + return JXL_FAILURE("Invalid BlendOp"); + } + FrameControl next_frame = { + /*delay_num=*/png_get_uint_16(payload.data() + 20), + /*delay_den=*/png_get_uint_16(payload.data() + 22), raw_viewport, + static_cast(dispose_op), static_cast(blend_op)}; + + if (!raw_viewport.Intersection(image_rect).IsSame(raw_viewport)) { + // Cropping happened. + return JXL_FAILURE("PNG frame is outside of image rect"); + } + + if (!seen_idat) { + // "Default" image is the first animation frame. Its viewport must + // cover the whole image area. + if (!raw_viewport.IsSame(image_rect)) { + return JXL_FAILURE( + "If the first animation frame is default image, its viewport " + "must cover full image"); } - } else if (id == kId_IDAT) { - // First IDAT chunk means we now have all header info - hasInfo = true; - JXL_CHECK(w == png_get_image_width(png_ptr, info_ptr)); - JXL_CHECK(h == png_get_image_height(png_ptr, info_ptr)); - int colortype = png_get_color_type(png_ptr, info_ptr); - ppf->info.bits_per_sample = png_get_bit_depth(png_ptr, info_ptr); - png_color_8p sigbits = NULL; - png_get_sBIT(png_ptr, info_ptr, &sigbits); - if (colortype & 1) { - // palette will actually be 8-bit regardless of the index bitdepth - ppf->info.bits_per_sample = 8; + } else { + JXL_RETURN_IF_ERROR(finalize_frame()); + if (!ctx.InitPngDecoder(passthrough_chunks, next_frame.viewport)) { + return JXL_FAILURE("Failed to initialize PNG decoder"); } - if (colortype & 2) { - ppf->info.num_color_channels = 3; - ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB; - if (sigbits && sigbits->red == sigbits->green && - sigbits->green == sigbits->blue) - ppf->info.bits_per_sample = sigbits->red; - } else { - ppf->info.num_color_channels = 1; - ppf->color_encoding.color_space = JXL_COLOR_SPACE_GRAY; - if (sigbits) ppf->info.bits_per_sample = sigbits->gray; - } - if (colortype & 4 || - png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { - ppf->info.alpha_bits = ppf->info.bits_per_sample; - if (sigbits) { - if (sigbits->alpha && - sigbits->alpha != ppf->info.bits_per_sample) { - return JXL_FAILURE("Unsupported alpha bit-depth"); - } - ppf->info.alpha_bits = sigbits->alpha; - } - } else { - ppf->info.alpha_bits = 0; - } - ppf->color_encoding.color_space = - (ppf->info.num_color_channels == 1 ? JXL_COLOR_SPACE_GRAY - : JXL_COLOR_SPACE_RGB); - ppf->info.xsize = w; - ppf->info.ysize = h; - JXL_RETURN_IF_ERROR(VerifyDimensions(&constraints, w, h)); + } + current_frame = next_frame; + continue; + } + + case MakeTag('I', 'D', 'A', 'T'): { + if (!frames.empty()) { + return JXL_FAILURE("IDAT after default image is over"); + } + if (!seen_idat) { + // First IDAT means that all metadata is ready. + seen_idat = true; + JXL_ENSURE(image_rect.xsize() == + png_get_image_width(ctx.png_ptr, ctx.info_ptr)); + JXL_ENSURE(image_rect.ysize() == + png_get_image_height(ctx.png_ptr, ctx.info_ptr)); + JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, image_rect.xsize(), + image_rect.ysize())); + ppf->info.xsize = image_rect.xsize(); + ppf->info.ysize = image_rect.ysize(); + + png_color_8p sig_bits = nullptr; + // Error is OK -> sig_bits remains nullptr. + png_get_sBIT(ctx.png_ptr, ctx.info_ptr, &sig_bits); + SetColorData(ppf, png_get_color_type(ctx.png_ptr, ctx.info_ptr), + png_get_bit_depth(ctx.png_ptr, ctx.info_ptr), sig_bits, + png_get_valid(ctx.png_ptr, ctx.info_ptr, PNG_INFO_tRNS)); num_channels = ppf->info.num_color_channels + (ppf->info.alpha_bits ? 1 : 0); format = { @@ -596,150 +1018,217 @@ Status DecodeImageAPNG(const Span bytes, }; bytes_per_pixel = num_channels * (format.data_type == JXL_TYPE_UINT16 ? 2 : 1); - rowbytes = w * bytes_per_pixel; - imagesize = h * rowbytes; - frameRaw.pixels.resize(imagesize); - frameRaw.rows.resize(h); - for (j = 0; j < h; j++) - frameRaw.rows[j] = frameRaw.pixels.data() + j * rowbytes; - - if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) { - break; - } - } else if (id == kId_fdAT && isAnimated) { - png_save_uint_32(chunk.data() + 4, chunk.size() - 16); - memcpy(chunk.data() + 8, "IDAT", 4); - if (processing_data(png_ptr, info_ptr, chunk.data() + 4, - chunk.size() - 4)) { - break; - } - } else if (id == kId_iCCP) { - if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) { - JXL_WARNING("Corrupt iCCP chunk"); - break; - } - - // TODO(jon): catch special case of PQ and synthesize color encoding - // in that case - int compression_type; - png_bytep profile; - png_charp name; - png_uint_32 proflen = 0; - auto ok = png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, - &profile, &proflen); - if (ok && proflen) { - ppf->icc.assign(profile, profile + proflen); - have_color = true; - } else { - // TODO(eustas): JXL_WARNING? - } - } else if (id == kId_sRGB) { - JXL_RETURN_IF_ERROR(DecodeSRGB(chunk.data() + 8, chunk.size() - 12, - &ppf->color_encoding)); - have_srgb = true; - have_color = true; - } else if (id == kId_gAMA) { - JXL_RETURN_IF_ERROR(DecodeGAMA(chunk.data() + 8, chunk.size() - 12, - &ppf->color_encoding)); - have_color = true; - } else if (id == kId_cHRM) { - JXL_RETURN_IF_ERROR(DecodeCHRM(chunk.data() + 8, chunk.size() - 12, - &ppf->color_encoding)); - have_color = true; - } else if (id == kId_eXIf) { - ppf->metadata.exif.resize(chunk.size() - 12); - memcpy(ppf->metadata.exif.data(), chunk.data() + 8, - chunk.size() - 12); - } else if (!isAbc(chunk[4]) || !isAbc(chunk[5]) || !isAbc(chunk[6]) || - !isAbc(chunk[7])) { - break; - } else { - if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) { - break; - } - if (!hasInfo) { - chunksInfo.push_back(chunk); - continue; + // TODO(eustas): ensure multiplication is safe + uint64_t row_bytes = + static_cast(image_rect.xsize()) * bytes_per_pixel; + uint64_t max_rows = std::numeric_limits::max() / row_bytes; + if (image_rect.ysize() > max_rows) { + return JXL_FAILURE("Image too big."); } + // TODO(eustas): drop frameRaw + JXL_RETURN_IF_ERROR( + ctx.frameRaw.Resize(row_bytes, image_rect.ysize())); } - } - } - if (have_srgb) { - ppf->color_encoding.white_point = JXL_WHITE_POINT_D65; - ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB; - ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB; - ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL; + if (!ctx.FeedChunks(chunk)) { + return JXL_FAILURE("Decoding IDAT failed"); + } + seen_pixel_data = true; + continue; + } + + case MakeTag('f', 'd', 'A', 'T'): { + if (!seen_idat) { + return JXL_FAILURE("fdAT chunk before IDAT"); + } + if (!seen_actl) { + return JXL_FAILURE("fdAT chunk before acTL"); + } + /* The 'fdAT' chunk has... the same structure as an 'IDAT' chunk, + * except preceded by a sequence number. */ + if (payload.size() < 4) { + return JXL_FAILURE("Corrupted fdAT chunk"); + } + // Turn 'fdAT' to 'IDAT' by cutting sequence number and replacing tag. + std::array preamble; + png_save_uint_32(preamble.data(), payload.size() - 4); + memcpy(preamble.data() + 4, "IDAT", 4); + // Cut-off 'size', 'type' and 'sequence_number' + Bytes chunk_tail(chunk.data() + 12, chunk.size() - 12); + if (!ctx.FeedChunks(Bytes(preamble), chunk_tail)) { + return JXL_FAILURE("Decoding fdAT failed"); + } + seen_pixel_data = true; + continue; + } + + case MakeTag('c', 'I', 'C', 'P'): + if (color_info_type == ColorInfoType::CICP) { + JXL_DEBUG_V(2, "Excessive colorspace definition; cICP chunk ignored"); + continue; + } + JXL_RETURN_IF_ERROR(DecodeCicpChunk(payload, &ppf->color_encoding)); + ppf->icc.clear(); + ppf->primary_color_representation = + PackedPixelFile::kColorEncodingIsPrimary; + color_info_type = ColorInfoType::CICP; + continue; + + case MakeTag('i', 'C', 'C', 'P'): { + if (color_info_type == ColorInfoType::ICCP_OR_SRGB) { + return JXL_FAILURE("Repeated iCCP / sRGB chunk"); + } + if (color_info_type > ColorInfoType::ICCP_OR_SRGB) { + JXL_DEBUG_V(2, "Excessive colorspace definition; iCCP chunk ignored"); + continue; + } + // Let PNG decoder deal with chunk processing. + if (!ctx.FeedChunks(chunk)) { + return JXL_FAILURE("Corrupt iCCP chunk"); + } + + // TODO(jon): catch special case of PQ and synthesize color encoding + // in that case + int compression_type = 0; + png_bytep profile = nullptr; + png_charp name = nullptr; + png_uint_32 profile_len = 0; + png_uint_32 ok = + png_get_iCCP(ctx.png_ptr, ctx.info_ptr, &name, &compression_type, + &profile, &profile_len); + if (!ok || !profile_len) { + return JXL_FAILURE("Malformed / incomplete iCCP chunk"); + } + ppf->icc.assign(profile, profile + profile_len); + ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary; + color_info_type = ColorInfoType::ICCP_OR_SRGB; + continue; + } + + case MakeTag('s', 'R', 'G', 'B'): + if (color_info_type == ColorInfoType::ICCP_OR_SRGB) { + return JXL_FAILURE("Repeated iCCP / sRGB chunk"); + } + if (color_info_type > ColorInfoType::ICCP_OR_SRGB) { + JXL_DEBUG_V(2, "Excessive colorspace definition; sRGB chunk ignored"); + continue; + } + JXL_RETURN_IF_ERROR(DecodeSrgbChunk(payload, &ppf->color_encoding)); + color_info_type = ColorInfoType::ICCP_OR_SRGB; + continue; + + case MakeTag('g', 'A', 'M', 'A'): + if (color_info_type >= ColorInfoType::GAMA_OR_CHRM) { + JXL_DEBUG_V(2, "Excessive colorspace definition; gAMA chunk ignored"); + continue; + } + JXL_RETURN_IF_ERROR(DecodeGamaChunk(payload, &ppf->color_encoding)); + color_info_type = ColorInfoType::GAMA_OR_CHRM; + continue; + + case MakeTag('c', 'H', 'R', 'M'): + if (color_info_type >= ColorInfoType::GAMA_OR_CHRM) { + JXL_DEBUG_V(2, "Excessive colorspace definition; cHRM chunk ignored"); + continue; + } + JXL_RETURN_IF_ERROR(DecodeChrmChunk(payload, &ppf->color_encoding)); + color_info_type = ColorInfoType::GAMA_OR_CHRM; + continue; + + case MakeTag('c', 'L', 'L', 'i'): + JXL_RETURN_IF_ERROR( + DecodeClliChunk(payload, &ppf->info.intensity_target)); + continue; + + case MakeTag('e', 'X', 'I', 'f'): + // TODO(eustas): next eXIF chunk overwrites current; is it ok? + ppf->metadata.exif.resize(payload.size()); + memcpy(ppf->metadata.exif.data(), payload.data(), payload.size()); + continue; + + default: + // We don't know what is that, just pass through. + if (!ctx.FeedChunks(chunk)) { + return JXL_FAILURE("PNG decoder failed to process chunk"); + } + // If it happens before IDAT, we consider it metadata and pass to all + // sub-decoders. + if (!seen_idat) { + passthrough_chunks.push_back(chunk); + } + continue; } - JXL_RETURN_IF_ERROR(ApplyColorHints( - color_hints, have_color, ppf->info.num_color_channels == 1, ppf)); } - if (errorstate) return false; + bool color_is_already_set = (color_info_type != ColorInfoType::NONE); + bool is_gray = (ppf->info.num_color_channels == 1); + JXL_RETURN_IF_ERROR( + ApplyColorHints(color_hints, color_is_already_set, is_gray, ppf)); + + if (ppf->color_encoding.transfer_function != JXL_TRANSFER_FUNCTION_PQ) { + // Reset intensity target, in case we set it from cLLi but TF is not PQ. + ppf->info.intensity_target = 0.f; + } bool has_nontrivial_background = false; bool previous_frame_should_be_cleared = false; - enum { - DISPOSE_OP_NONE = 0, - DISPOSE_OP_BACKGROUND = 1, - DISPOSE_OP_PREVIOUS = 2, - }; - enum { - BLEND_OP_SOURCE = 0, - BLEND_OP_OVER = 1, - }; for (size_t i = 0; i < frames.size(); i++) { - auto& frame = frames[i]; - JXL_ASSERT(frame.data.xsize == frame.xsize); - JXL_ASSERT(frame.data.ysize == frame.ysize); + Frame& frame = frames[i]; + const FrameControl& fc = frame.metadata; + const RectT vp = fc.viewport; + const auto& pixels = frame.pixels; + size_t xsize = pixels.xsize; + size_t ysize = pixels.ysize; + JXL_ENSURE(xsize == vp.xsize()); + JXL_ENSURE(ysize == vp.ysize()); - // Before encountering a DISPOSE_OP_NONE frame, the canvas is filled with 0, - // so DISPOSE_OP_BACKGROUND and DISPOSE_OP_PREVIOUS are equivalent. - if (frame.dispose_op == DISPOSE_OP_NONE) { + // Before encountering a DISPOSE_OP_NONE frame, the canvas is filled with + // 0, so DISPOSE_OP_BACKGROUND and DISPOSE_OP_PREVIOUS are equivalent. + if (fc.dispose_op == DisposeOp::NONE) { has_nontrivial_background = true; } - bool should_blend = frame.blend_op == BLEND_OP_OVER; + bool should_blend = fc.blend_op == BlendOp::OVER; bool use_for_next_frame = - has_nontrivial_background && frame.dispose_op != DISPOSE_OP_PREVIOUS; - size_t x0 = frame.x0; - size_t y0 = frame.y0; - size_t xsize = frame.data.xsize; - size_t ysize = frame.data.ysize; + has_nontrivial_background && fc.dispose_op != DisposeOp::PREVIOUS; + size_t x0 = vp.x0(); + size_t y0 = vp.y0(); if (previous_frame_should_be_cleared) { - size_t xs = frame.data.xsize; - size_t ys = frame.data.ysize; - size_t px0 = frames[i - 1].x0; - size_t py0 = frames[i - 1].y0; - size_t pxs = frames[i - 1].xsize; - size_t pys = frames[i - 1].ysize; - if (px0 >= x0 && py0 >= y0 && px0 + pxs <= x0 + xs && - py0 + pys <= y0 + ys && frame.blend_op == BLEND_OP_SOURCE && + const auto& pvp = frames[i - 1].metadata.viewport; + size_t px0 = pvp.x0(); + size_t py0 = pvp.y0(); + size_t pxs = pvp.xsize(); + size_t pys = pvp.ysize(); + if (px0 >= x0 && py0 >= y0 && px0 + pxs <= x0 + xsize && + py0 + pys <= y0 + ysize && fc.blend_op == BlendOp::SOURCE && use_for_next_frame) { - // If the previous frame is entirely contained in the current frame and - // we are using BLEND_OP_SOURCE, nothing special needs to be done. - ppf->frames.emplace_back(std::move(frame.data)); - } else if (px0 == x0 && py0 == y0 && px0 + pxs == x0 + xs && - py0 + pys == y0 + ys && use_for_next_frame) { + // If the previous frame is entirely contained in the current frame + // and we are using BLEND_OP_SOURCE, nothing special needs to be done. + ppf->frames.emplace_back(std::move(frame.pixels)); + } else if (px0 == x0 && py0 == y0 && px0 + pxs == x0 + xsize && + py0 + pys == y0 + ysize && use_for_next_frame) { // If the new frame has the same size as the old one, but we are // blending, we can instead just not blend. should_blend = false; - ppf->frames.emplace_back(std::move(frame.data)); - } else if (px0 <= x0 && py0 <= y0 && px0 + pxs >= x0 + xs && - py0 + pys >= y0 + ys && use_for_next_frame) { + ppf->frames.emplace_back(std::move(frame.pixels)); + } else if (px0 <= x0 && py0 <= y0 && px0 + pxs >= x0 + xsize && + py0 + pys >= y0 + ysize && use_for_next_frame) { // If the new frame is contained within the old frame, we can pad the // new frame with zeros and not blend. - PackedImage new_data(pxs, pys, frame.data.format); + JXL_ASSIGN_OR_RETURN(PackedImage new_data, + PackedImage::Create(pxs, pys, pixels.format)); memset(new_data.pixels(), 0, new_data.pixels_size); - for (size_t y = 0; y < ys; y++) { + for (size_t y = 0; y < ysize; y++) { + JXL_RETURN_IF_ERROR( + PackedImage::ValidateDataType(new_data.format.data_type)); size_t bytes_per_pixel = PackedImage::BitsPerChannel(new_data.format.data_type) * new_data.format.num_channels / 8; - memcpy(static_cast(new_data.pixels()) + - new_data.stride * (y + y0 - py0) + - bytes_per_pixel * (x0 - px0), - static_cast(frame.data.pixels()) + - frame.data.stride * y, - xs * bytes_per_pixel); + memcpy( + static_cast(new_data.pixels()) + + new_data.stride * (y + y0 - py0) + + bytes_per_pixel * (x0 - px0), + static_cast(pixels.pixels()) + pixels.stride * y, + xsize * bytes_per_pixel); } x0 = px0; @@ -749,24 +1238,27 @@ Status DecodeImageAPNG(const Span bytes, should_blend = false; ppf->frames.emplace_back(std::move(new_data)); } else { - // If all else fails, insert a dummy blank frame with kReplace. - PackedImage blank(pxs, pys, frame.data.format); + // If all else fails, insert a placeholder blank frame with kReplace. + JXL_ASSIGN_OR_RETURN(PackedImage blank, + PackedImage::Create(pxs, pys, pixels.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; pframe.frame_info.layer_info.crop_y0 = py0; - pframe.frame_info.layer_info.xsize = frame.xsize; - pframe.frame_info.layer_info.ysize = frame.ysize; + pframe.frame_info.layer_info.xsize = pxs; + pframe.frame_info.layer_info.ysize = pys; pframe.frame_info.duration = 0; - pframe.frame_info.layer_info.have_crop = 0; + bool is_full_size = px0 == 0 && py0 == 0 && pxs == ppf->info.xsize && + pys == ppf->info.ysize; + pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1; pframe.frame_info.layer_info.blend_info.blendmode = JXL_BLEND_REPLACE; - pframe.frame_info.layer_info.blend_info.source = 0; + pframe.frame_info.layer_info.blend_info.source = 1; pframe.frame_info.layer_info.save_as_reference = 1; - ppf->frames.emplace_back(std::move(frame.data)); + ppf->frames.emplace_back(std::move(frame.pixels)); } } else { - ppf->frames.emplace_back(std::move(frame.data)); + ppf->frames.emplace_back(std::move(frame.pixels)); } auto& pframe = ppf->frames.back(); @@ -774,24 +1266,28 @@ Status DecodeImageAPNG(const Span bytes, pframe.frame_info.layer_info.crop_y0 = y0; pframe.frame_info.layer_info.xsize = xsize; pframe.frame_info.layer_info.ysize = ysize; - pframe.frame_info.duration = frame.duration; + pframe.frame_info.duration = + fc.delay_num * 1000 / (fc.delay_den ? fc.delay_den : 100); pframe.frame_info.layer_info.blend_info.blendmode = should_blend ? JXL_BLEND_BLEND : JXL_BLEND_REPLACE; bool is_full_size = x0 == 0 && y0 == 0 && xsize == ppf->info.xsize && ysize == ppf->info.ysize; pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1; - pframe.frame_info.layer_info.blend_info.source = should_blend ? 1 : 0; + pframe.frame_info.layer_info.blend_info.source = 1; pframe.frame_info.layer_info.blend_info.alpha = 0; pframe.frame_info.layer_info.save_as_reference = use_for_next_frame ? 1 : 0; previous_frame_should_be_cleared = - has_nontrivial_background && frame.dispose_op == DISPOSE_OP_BACKGROUND; + has_nontrivial_background && (fc.dispose_op == DisposeOp::BACKGROUND); } + if (ppf->frames.empty()) return JXL_FAILURE("No frames decoded"); - ppf->frames.back().frame_info.is_last = true; + ppf->frames.back().frame_info.is_last = JXL_TRUE; return true; } +#endif // JPEGXL_ENABLE_APNG + } // namespace extras } // namespace jxl diff --git a/media/libjxl/src/lib/extras/dec/apng.h b/media/libjxl/src/lib/extras/dec/apng.h index a68f6f8ec7..7ebc2ee7c8 100644 --- a/media/libjxl/src/lib/extras/dec/apng.h +++ b/media/libjxl/src/lib/extras/dec/apng.h @@ -8,23 +8,25 @@ // Decodes APNG images in memory. -#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" -#include "lib/jxl/codec_in_out.h" namespace jxl { + +struct SizeConstraints; + namespace extras { +bool CanDecodeAPNG(); + // Decodes `bytes` into `ppf`. Status DecodeImageAPNG(Span bytes, const ColorHints& color_hints, - const SizeConstraints& constraints, - PackedPixelFile* ppf); + PackedPixelFile* ppf, + const SizeConstraints* constraints = nullptr); } // namespace extras } // namespace jxl diff --git a/media/libjxl/src/lib/extras/dec/color_description.cc b/media/libjxl/src/lib/extras/dec/color_description.cc index 2325b50f3b..dd84455c41 100644 --- a/media/libjxl/src/lib/extras/dec/color_description.cc +++ b/media/libjxl/src/lib/extras/dec/color_description.cc @@ -9,6 +9,8 @@ #include +#include "lib/jxl/base/common.h" + namespace jxl { namespace { @@ -19,49 +21,46 @@ struct EnumName { T value; }; -const EnumName kJxlColorSpaceNames[] = { - {"RGB", JXL_COLOR_SPACE_RGB}, - {"Gra", JXL_COLOR_SPACE_GRAY}, - {"XYB", JXL_COLOR_SPACE_XYB}, - {"CS?", JXL_COLOR_SPACE_UNKNOWN}, -}; +constexpr auto kJxlColorSpaceNames = + to_array>({{"RGB", JXL_COLOR_SPACE_RGB}, + {"Gra", JXL_COLOR_SPACE_GRAY}, + {"XYB", JXL_COLOR_SPACE_XYB}, + {"CS?", JXL_COLOR_SPACE_UNKNOWN}}); -const EnumName kJxlWhitePointNames[] = { - {"D65", JXL_WHITE_POINT_D65}, - {"Cst", JXL_WHITE_POINT_CUSTOM}, - {"EER", JXL_WHITE_POINT_E}, - {"DCI", JXL_WHITE_POINT_DCI}, -}; +constexpr auto kJxlWhitePointNames = + to_array>({{"D65", JXL_WHITE_POINT_D65}, + {"Cst", JXL_WHITE_POINT_CUSTOM}, + {"EER", JXL_WHITE_POINT_E}, + {"DCI", JXL_WHITE_POINT_DCI}}); -const EnumName kJxlPrimariesNames[] = { - {"SRG", JXL_PRIMARIES_SRGB}, - {"Cst", JXL_PRIMARIES_CUSTOM}, - {"202", JXL_PRIMARIES_2100}, - {"DCI", JXL_PRIMARIES_P3}, -}; +constexpr auto kJxlPrimariesNames = + to_array>({{"SRG", JXL_PRIMARIES_SRGB}, + {"Cst", JXL_PRIMARIES_CUSTOM}, + {"202", JXL_PRIMARIES_2100}, + {"DCI", JXL_PRIMARIES_P3}}); -const EnumName kJxlTransferFunctionNames[] = { - {"709", JXL_TRANSFER_FUNCTION_709}, - {"TF?", JXL_TRANSFER_FUNCTION_UNKNOWN}, - {"Lin", JXL_TRANSFER_FUNCTION_LINEAR}, - {"SRG", JXL_TRANSFER_FUNCTION_SRGB}, - {"PeQ", JXL_TRANSFER_FUNCTION_PQ}, - {"DCI", JXL_TRANSFER_FUNCTION_DCI}, - {"HLG", JXL_TRANSFER_FUNCTION_HLG}, - {"", JXL_TRANSFER_FUNCTION_GAMMA}, -}; +constexpr auto kJxlRenderingIntentNames = + to_array>( + {{"Per", JXL_RENDERING_INTENT_PERCEPTUAL}, + {"Rel", JXL_RENDERING_INTENT_RELATIVE}, + {"Sat", JXL_RENDERING_INTENT_SATURATION}, + {"Abs", JXL_RENDERING_INTENT_ABSOLUTE}}); -const EnumName kJxlRenderingIntentNames[] = { - {"Per", JXL_RENDERING_INTENT_PERCEPTUAL}, - {"Rel", JXL_RENDERING_INTENT_RELATIVE}, - {"Sat", JXL_RENDERING_INTENT_SATURATION}, - {"Abs", JXL_RENDERING_INTENT_ABSOLUTE}, -}; +constexpr auto kJxlTransferFunctionNames = + to_array>( + {{"709", JXL_TRANSFER_FUNCTION_709}, + {"TF?", JXL_TRANSFER_FUNCTION_UNKNOWN}, + {"Lin", JXL_TRANSFER_FUNCTION_LINEAR}, + {"SRG", JXL_TRANSFER_FUNCTION_SRGB}, + {"PeQ", JXL_TRANSFER_FUNCTION_PQ}, + {"DCI", JXL_TRANSFER_FUNCTION_DCI}, + {"HLG", JXL_TRANSFER_FUNCTION_HLG}, + {"", JXL_TRANSFER_FUNCTION_GAMMA}}); -template -Status ParseEnum(const std::string& token, const EnumName* enum_values, - size_t enum_len, T* value) { - for (size_t i = 0; i < enum_len; i++) { +template +Status ParseEnum(const std::string& token, + const std::array, N>& enum_values, T* value) { + for (size_t i = 0; i < enum_values.size(); i++) { if (enum_values[i].name == token) { *value = enum_values[i].value; return true; @@ -69,9 +68,6 @@ Status ParseEnum(const std::string& token, const EnumName* enum_values, } return false; } -#define ARRAYSIZE(X) (sizeof(X) / sizeof((X)[0])) -#define PARSE_ENUM(type, token, value) \ - ParseEnum(token, k##type##Names, ARRAYSIZE(k##type##Names), value) class Tokenizer { public: @@ -122,7 +118,7 @@ Status ParseColorSpace(Tokenizer* tokenizer, JxlColorEncoding* c) { std::string str; JXL_RETURN_IF_ERROR(tokenizer->Next(&str)); JxlColorSpace cs; - if (PARSE_ENUM(JxlColorSpace, str, &cs)) { + if (ParseEnum(str, kJxlColorSpaceNames, &cs)) { c->color_space = cs; return true; } @@ -139,7 +135,7 @@ Status ParseWhitePoint(Tokenizer* tokenizer, JxlColorEncoding* c) { std::string str; JXL_RETURN_IF_ERROR(tokenizer->Next(&str)); - if (PARSE_ENUM(JxlWhitePoint, str, &c->white_point)) return true; + if (ParseEnum(str, kJxlWhitePointNames, &c->white_point)) return true; Tokenizer xy_tokenizer(&str, ';'); c->white_point = JXL_WHITE_POINT_CUSTOM; @@ -157,7 +153,7 @@ Status ParsePrimaries(Tokenizer* tokenizer, JxlColorEncoding* c) { std::string str; JXL_RETURN_IF_ERROR(tokenizer->Next(&str)); - if (PARSE_ENUM(JxlPrimaries, str, &c->primaries)) return true; + if (ParseEnum(str, kJxlPrimariesNames, &c->primaries)) return true; Tokenizer xy_tokenizer(&str, ';'); JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_red_xy + 0)); @@ -168,13 +164,14 @@ Status ParsePrimaries(Tokenizer* tokenizer, JxlColorEncoding* c) { JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_blue_xy + 1)); c->primaries = JXL_PRIMARIES_CUSTOM; - return JXL_FAILURE("Invalid primaries %s", str.c_str()); + return true; } Status ParseRenderingIntent(Tokenizer* tokenizer, JxlColorEncoding* c) { std::string str; JXL_RETURN_IF_ERROR(tokenizer->Next(&str)); - if (PARSE_ENUM(JxlRenderingIntent, str, &c->rendering_intent)) return true; + if (ParseEnum(str, kJxlRenderingIntentNames, &c->rendering_intent)) + return true; return JXL_FAILURE("Invalid RenderingIntent %s\n", str.c_str()); } @@ -189,7 +186,7 @@ Status ParseTransferFunction(Tokenizer* tokenizer, JxlColorEncoding* c) { std::string str; JXL_RETURN_IF_ERROR(tokenizer->Next(&str)); - if (PARSE_ENUM(JxlTransferFunction, str, &c->transfer_function)) { + if (ParseEnum(str, kJxlTransferFunctionNames, &c->transfer_function)) { return true; } @@ -206,12 +203,38 @@ Status ParseTransferFunction(Tokenizer* tokenizer, JxlColorEncoding* c) { Status ParseDescription(const std::string& description, JxlColorEncoding* c) { *c = {}; - Tokenizer tokenizer(&description, '_'); - JXL_RETURN_IF_ERROR(ParseColorSpace(&tokenizer, c)); - JXL_RETURN_IF_ERROR(ParseWhitePoint(&tokenizer, c)); - JXL_RETURN_IF_ERROR(ParsePrimaries(&tokenizer, c)); - JXL_RETURN_IF_ERROR(ParseRenderingIntent(&tokenizer, c)); - JXL_RETURN_IF_ERROR(ParseTransferFunction(&tokenizer, c)); + if (description == "sRGB") { + c->color_space = JXL_COLOR_SPACE_RGB; + c->white_point = JXL_WHITE_POINT_D65; + c->primaries = JXL_PRIMARIES_SRGB; + c->transfer_function = JXL_TRANSFER_FUNCTION_SRGB; + c->rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL; + } else if (description == "DisplayP3") { + c->color_space = JXL_COLOR_SPACE_RGB; + c->white_point = JXL_WHITE_POINT_D65; + c->primaries = JXL_PRIMARIES_P3; + c->transfer_function = JXL_TRANSFER_FUNCTION_SRGB; + c->rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL; + } else if (description == "Rec2100PQ") { + c->color_space = JXL_COLOR_SPACE_RGB; + c->white_point = JXL_WHITE_POINT_D65; + c->primaries = JXL_PRIMARIES_2100; + c->transfer_function = JXL_TRANSFER_FUNCTION_PQ; + c->rendering_intent = JXL_RENDERING_INTENT_RELATIVE; + } else if (description == "Rec2100HLG") { + c->color_space = JXL_COLOR_SPACE_RGB; + c->white_point = JXL_WHITE_POINT_D65; + c->primaries = JXL_PRIMARIES_2100; + c->transfer_function = JXL_TRANSFER_FUNCTION_HLG; + c->rendering_intent = JXL_RENDERING_INTENT_RELATIVE; + } else { + Tokenizer tokenizer(&description, '_'); + JXL_RETURN_IF_ERROR(ParseColorSpace(&tokenizer, c)); + JXL_RETURN_IF_ERROR(ParseWhitePoint(&tokenizer, c)); + JXL_RETURN_IF_ERROR(ParsePrimaries(&tokenizer, c)); + JXL_RETURN_IF_ERROR(ParseRenderingIntent(&tokenizer, c)); + JXL_RETURN_IF_ERROR(ParseTransferFunction(&tokenizer, c)); + } return true; } diff --git a/media/libjxl/src/lib/extras/dec/color_description.h b/media/libjxl/src/lib/extras/dec/color_description.h index 989d5910c3..23680ff7c6 100644 --- a/media/libjxl/src/lib/extras/dec/color_description.h +++ b/media/libjxl/src/lib/extras/dec/color_description.h @@ -6,9 +6,10 @@ #ifndef LIB_EXTRAS_COLOR_DESCRIPTION_H_ #define LIB_EXTRAS_COLOR_DESCRIPTION_H_ +#include + #include -#include "jxl/color_encoding.h" #include "lib/jxl/base/status.h" namespace jxl { diff --git a/media/libjxl/src/lib/extras/dec/color_description_test.cc b/media/libjxl/src/lib/extras/dec/color_description_test.cc index 8ae9e5dce3..e6e34f0edf 100644 --- a/media/libjxl/src/lib/extras/dec/color_description_test.cc +++ b/media/libjxl/src/lib/extras/dec/color_description_test.cc @@ -5,9 +5,9 @@ #include "lib/extras/dec/color_description.h" -#include "gtest/gtest.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/test_utils.h" +#include "lib/jxl/testing.h" namespace jxl { @@ -21,8 +21,7 @@ TEST(ColorDescriptionTest, RoundTripAll) { JxlColorEncoding c_external = {}; EXPECT_TRUE(ParseDescription(description, &c_external)); ColorEncoding c_internal; - EXPECT_TRUE( - ConvertExternalToInternalColorEncoding(c_external, &c_internal)); + EXPECT_TRUE(c_internal.FromExternal(c_external)); EXPECT_TRUE(c_original.SameColorEncoding(c_internal)) << "Where c_original=" << c_original << " and c_internal=" << c_internal; diff --git a/media/libjxl/src/lib/extras/dec/color_hints.cc b/media/libjxl/src/lib/extras/dec/color_hints.cc index cf7d3e31f0..b1edd9f6ea 100644 --- a/media/libjxl/src/lib/extras/dec/color_hints.cc +++ b/media/libjxl/src/lib/extras/dec/color_hints.cc @@ -5,9 +5,12 @@ #include "lib/extras/dec/color_hints.h" -#include "jxl/encode.h" +#include + +#include + #include "lib/extras/dec/color_description.h" -#include "lib/jxl/base/file_io.h" +#include "lib/jxl/base/status.h" namespace jxl { namespace extras { @@ -15,19 +18,15 @@ namespace extras { Status ApplyColorHints(const ColorHints& color_hints, const bool color_already_set, const bool is_gray, PackedPixelFile* ppf) { - if (color_already_set) { - return color_hints.Foreach( - [](const std::string& key, const std::string& /*value*/) { - JXL_WARNING("Decoder ignoring %s hint", key.c_str()); - return true; - }); - } - - bool got_color_space = false; + bool got_color_space = color_already_set; JXL_RETURN_IF_ERROR(color_hints.Foreach( - [is_gray, ppf, &got_color_space](const std::string& key, - const std::string& value) -> Status { + [color_already_set, is_gray, ppf, &got_color_space]( + const std::string& key, const std::string& value) -> Status { + if (color_already_set && (key == "color_space" || key == "icc")) { + JXL_WARNING("Decoder ignoring %s hint", key.c_str()); + return true; + } if (key == "color_space") { JxlColorEncoding c_original_external; if (!ParseDescription(value, &c_original_external)) { @@ -41,9 +40,24 @@ Status ApplyColorHints(const ColorHints& color_hints, } got_color_space = true; - } else if (key == "icc_pathname") { - JXL_RETURN_IF_ERROR(ReadFile(value, &ppf->icc)); + } else if (key == "icc") { + const uint8_t* data = reinterpret_cast(value.data()); + std::vector icc(data, data + value.size()); + ppf->icc = std::move(icc); + ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary; got_color_space = true; + } else if (key == "exif") { + const uint8_t* data = reinterpret_cast(value.data()); + std::vector blob(data, data + value.size()); + ppf->metadata.exif = std::move(blob); + } else if (key == "xmp") { + const uint8_t* data = reinterpret_cast(value.data()); + std::vector blob(data, data + value.size()); + ppf->metadata.xmp = std::move(blob); + } else if (key == "jumbf") { + const uint8_t* data = reinterpret_cast(value.data()); + std::vector blob(data, data + value.size()); + ppf->metadata.jumbf = std::move(blob); } else { JXL_WARNING("Ignoring %s hint", key.c_str()); } @@ -51,7 +65,6 @@ Status ApplyColorHints(const ColorHints& color_hints, })); if (!got_color_space) { - JXL_WARNING("No color_space/icc_pathname given, assuming sRGB"); ppf->color_encoding.color_space = is_gray ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB; ppf->color_encoding.white_point = JXL_WHITE_POINT_D65; diff --git a/media/libjxl/src/lib/extras/dec/color_hints.h b/media/libjxl/src/lib/extras/dec/color_hints.h index 9c7de884f9..036f203e26 100644 --- a/media/libjxl/src/lib/extras/dec/color_hints.h +++ b/media/libjxl/src/lib/extras/dec/color_hints.h @@ -10,6 +10,8 @@ // information into the file, and those that support it may not have it. // To allow attaching color information to those file formats the caller can // define these color hints. +// Besides color space information, 'ColorHints' may also include other +// additional information such as Exif, XMP and JUMBF metadata. #include #include diff --git a/media/libjxl/src/lib/extras/dec/decode.cc b/media/libjxl/src/lib/extras/dec/decode.cc index 8712e03aac..2581d53f63 100644 --- a/media/libjxl/src/lib/extras/dec/decode.cc +++ b/media/libjxl/src/lib/extras/dec/decode.cc @@ -7,18 +7,11 @@ #include -#if JPEGXL_ENABLE_APNG #include "lib/extras/dec/apng.h" -#endif -#if JPEGXL_ENABLE_EXR #include "lib/extras/dec/exr.h" -#endif -#if JPEGXL_ENABLE_GIF #include "lib/extras/dec/gif.h" -#endif -#if JPEGXL_ENABLE_JPEG #include "lib/extras/dec/jpg.h" -#endif +#include "lib/extras/dec/jxl.h" #include "lib/extras/dec/pgx.h" #include "lib/extras/dec/pnm.h" @@ -29,94 +22,132 @@ namespace { // Any valid encoding is larger (ensures codecs can read the first few bytes) constexpr size_t kMinBytes = 9; -} // namespace +std::string GetExtension(const std::string& path) { + // Pattern: "name.png" + size_t pos = path.find_last_of('.'); + if (pos != std::string::npos) { + return path.substr(pos); + } -std::vector AvailableCodecs() { - std::vector out; -#if JPEGXL_ENABLE_APNG - out.push_back(Codec::kPNG); -#endif -#if JPEGXL_ENABLE_EXR - out.push_back(Codec::kEXR); -#endif -#if JPEGXL_ENABLE_GIF - out.push_back(Codec::kGIF); -#endif -#if JPEGXL_ENABLE_JPEG - out.push_back(Codec::kJPG); -#endif - out.push_back(Codec::kPGX); - out.push_back(Codec::kPNM); - return out; + // Extension not found + return ""; } -Codec CodecFromExtension(std::string extension, - size_t* JXL_RESTRICT bits_per_sample) { - std::transform( - extension.begin(), extension.end(), extension.begin(), - [](char c) { return std::tolower(c, std::locale::classic()); }); - if (extension == ".png") return Codec::kPNG; +} // namespace - if (extension == ".jpg") return Codec::kJPG; - if (extension == ".jpeg") return Codec::kJPG; +Codec CodecFromPath(const std::string& path, + size_t* JXL_RESTRICT bits_per_sample, + std::string* extension) { + std::string ext = GetExtension(path); + if (extension) { + if (extension->empty()) { + *extension = ext; + } else { + ext = *extension; + } + } + std::transform(ext.begin(), ext.end(), ext.begin(), [](char c) { + return std::tolower(c, std::locale::classic()); + }); + if (ext == ".png") return Codec::kPNG; - if (extension == ".pgx") return Codec::kPGX; + if (ext == ".jpg") return Codec::kJPG; + if (ext == ".jpeg") return Codec::kJPG; - if (extension == ".pam") return Codec::kPNM; - if (extension == ".pnm") return Codec::kPNM; - if (extension == ".pgm") return Codec::kPNM; - if (extension == ".ppm") return Codec::kPNM; - if (extension == ".pfm") { + if (ext == ".pgx") return Codec::kPGX; + + if (ext == ".pam") return Codec::kPNM; + if (ext == ".pnm") return Codec::kPNM; + if (ext == ".pgm") return Codec::kPNM; + if (ext == ".ppm") return Codec::kPNM; + if (ext == ".pfm") { if (bits_per_sample != nullptr) *bits_per_sample = 32; return Codec::kPNM; } - if (extension == ".gif") return Codec::kGIF; + if (ext == ".gif") return Codec::kGIF; - if (extension == ".exr") return Codec::kEXR; + if (ext == ".exr") return Codec::kEXR; return Codec::kUnknown; } +bool CanDecode(Codec codec) { + switch (codec) { + case Codec::kEXR: + return CanDecodeEXR(); + case Codec::kGIF: + return CanDecodeGIF(); + case Codec::kJPG: + return CanDecodeJPG(); + case Codec::kPNG: + return CanDecodeAPNG(); + case Codec::kPNM: + case Codec::kPGX: + case Codec::kJXL: + return true; + default: + return false; + } +} + +std::string ListOfDecodeCodecs() { + std::string list_of_codecs("JXL, PPM, PNM, PFM, PAM, PGX"); + if (CanDecode(Codec::kPNG)) list_of_codecs.append(", PNG, APNG"); + if (CanDecode(Codec::kGIF)) list_of_codecs.append(", GIF"); + if (CanDecode(Codec::kJPG)) list_of_codecs.append(", JPEG"); + if (CanDecode(Codec::kEXR)) list_of_codecs.append(", EXR"); + return list_of_codecs; +} + Status DecodeBytes(const Span bytes, - const ColorHints& color_hints, - const SizeConstraints& constraints, - extras::PackedPixelFile* ppf, Codec* orig_codec) { + const ColorHints& color_hints, extras::PackedPixelFile* ppf, + const SizeConstraints* constraints, Codec* orig_codec) { if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes"); *ppf = extras::PackedPixelFile(); // Default values when not set by decoders. - ppf->info.uses_original_profile = true; + ppf->info.uses_original_profile = JXL_TRUE; ppf->info.orientation = JXL_ORIENT_IDENTITY; - Codec codec; -#if JPEGXL_ENABLE_APNG - if (DecodeImageAPNG(bytes, color_hints, constraints, ppf)) { - codec = Codec::kPNG; - } else -#endif - if (DecodeImagePGX(bytes, color_hints, constraints, ppf)) { - codec = Codec::kPGX; - } else if (DecodeImagePNM(bytes, color_hints, constraints, ppf)) { - codec = Codec::kPNM; - } -#if JPEGXL_ENABLE_GIF - else if (DecodeImageGIF(bytes, color_hints, constraints, ppf)) { - codec = Codec::kGIF; - } -#endif -#if JPEGXL_ENABLE_JPEG - else if (DecodeImageJPG(bytes, color_hints, constraints, ppf)) { - codec = Codec::kJPG; - } -#endif -#if JPEGXL_ENABLE_EXR - else if (DecodeImageEXR(bytes, color_hints, constraints, ppf)) { - codec = Codec::kEXR; - } -#endif - else { + const auto choose_codec = [&]() -> Codec { + if (DecodeImageAPNG(bytes, color_hints, ppf, constraints)) { + return Codec::kPNG; + } + if (DecodeImagePGX(bytes, color_hints, ppf, constraints)) { + return Codec::kPGX; + } + if (DecodeImagePNM(bytes, color_hints, ppf, constraints)) { + return Codec::kPNM; + } + JXLDecompressParams dparams = {}; + for (const uint32_t num_channels : {1, 2, 3, 4}) { + dparams.accepted_formats.push_back( + {num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0}); + } + dparams.output_bitdepth.type = JXL_BIT_DEPTH_FROM_CODESTREAM; + size_t decoded_bytes; + if (DecodeImageJXL(bytes.data(), bytes.size(), dparams, &decoded_bytes, + ppf) && + ApplyColorHints(color_hints, true, ppf->info.num_color_channels == 1, + ppf)) { + return Codec::kJXL; + } + if (DecodeImageGIF(bytes, color_hints, ppf, constraints)) { + return Codec::kGIF; + } + if (DecodeImageJPG(bytes, color_hints, ppf, constraints)) { + return Codec::kJPG; + } + if (DecodeImageEXR(bytes, color_hints, ppf, constraints)) { + return Codec::kEXR; + } + return Codec::kUnknown; + }; + + Codec codec = choose_codec(); + if (codec == Codec::kUnknown) { return JXL_FAILURE("Codecs failed to decode"); } if (orig_codec) *orig_codec = codec; diff --git a/media/libjxl/src/lib/extras/dec/decode.h b/media/libjxl/src/lib/extras/dec/decode.h index 7f0ff70aa8..bbffc7e0f0 100644 --- a/media/libjxl/src/lib/extras/dec/decode.h +++ b/media/libjxl/src/lib/extras/dec/decode.h @@ -8,43 +8,48 @@ // Facade for image decoders (PNG, PNM, ...). -#include -#include - +#include +#include #include -#include #include "lib/extras/dec/color_hints.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" -#include "lib/jxl/codec_in_out.h" namespace jxl { + +struct SizeConstraints; + namespace extras { -// Codecs supported by CodecInOut::Encode. +// Codecs supported by DecodeBytes. enum class Codec : uint32_t { - kUnknown, // for CodecFromExtension + kUnknown, // for CodecFromPath kPNG, kPNM, kPGX, kJPG, kGIF, - kEXR + kEXR, + kJXL }; -std::vector AvailableCodecs(); +bool CanDecode(Codec codec); + +std::string ListOfDecodeCodecs(); // If and only if extension is ".pfm", *bits_per_sample is updated to 32 so // that Encode() would encode to PFM instead of PPM. -Codec CodecFromExtension(std::string extension, - size_t* JXL_RESTRICT bits_per_sample = nullptr); +Codec CodecFromPath(const std::string& path, + size_t* JXL_RESTRICT bits_per_sample = nullptr, + std::string* extension = nullptr); // Decodes "bytes" info *ppf. // color_space_hint may specify the color space, otherwise, defaults to sRGB. Status DecodeBytes(Span bytes, const ColorHints& color_hints, - const SizeConstraints& constraints, - extras::PackedPixelFile* ppf, Codec* orig_codec = nullptr); + extras::PackedPixelFile* ppf, + const SizeConstraints* constraints = nullptr, + Codec* orig_codec = nullptr); } // namespace extras } // namespace jxl diff --git a/media/libjxl/src/lib/extras/dec/exr.cc b/media/libjxl/src/lib/extras/dec/exr.cc index ddb6d534e5..59edd63eb8 100644 --- a/media/libjxl/src/lib/extras/dec/exr.cc +++ b/media/libjxl/src/lib/extras/dec/exr.cc @@ -5,6 +5,34 @@ #include "lib/extras/dec/exr.h" +#include + +#include "lib/extras/dec/color_hints.h" +#include "lib/extras/packed_image.h" +#include "lib/jxl/base/common.h" +#include "lib/jxl/base/span.h" +#include "lib/jxl/base/status.h" + +#if !JPEGXL_ENABLE_EXR + +namespace jxl { +namespace extras { +bool CanDecodeEXR() { return false; } + +Status DecodeImageEXR(Span bytes, const ColorHints& color_hints, + PackedPixelFile* ppf, + const SizeConstraints* constraints) { + (void)bytes; + (void)color_hints; + (void)ppf; + (void)constraints; + return JXL_FAILURE("EXR is not supported"); +} +} // namespace extras +} // namespace jxl + +#else // JPEGXL_ENABLE_EXR + #include #include #include @@ -12,13 +40,19 @@ #include +#ifdef __EXCEPTIONS +#include +#define JXL_EXR_THROW_LENGTH_ERROR() throw std::length_error(""); +#else // __EXCEPTIONS +#define JXL_EXR_THROW_LENGTH_ERROR() JXL_CRASH() +#endif // __EXCEPTIONS + namespace jxl { namespace extras { namespace { namespace OpenEXR = OPENEXR_IMF_NAMESPACE; -namespace Imath = IMATH_NAMESPACE; // OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using // uint64_t as recommended causes build failures with previous OpenEXR versions @@ -37,7 +71,9 @@ class InMemoryIStream : public OpenEXR::IStream { bool isMemoryMapped() const override { return true; } char* readMemoryMapped(const int n) override { - JXL_ASSERT(pos_ + n <= bytes_.size()); + if (pos_ + n < pos_ || pos_ + n > bytes_.size()) { + JXL_EXR_THROW_LENGTH_ERROR(); + } char* const result = const_cast(reinterpret_cast(bytes_.data() + pos_)); pos_ += n; @@ -50,7 +86,9 @@ class InMemoryIStream : public OpenEXR::IStream { ExrInt64 tellg() override { return pos_; } void seekg(const ExrInt64 pos) override { - JXL_ASSERT(pos + 1 <= bytes_.size()); + if (pos >= bytes_.size()) { + JXL_EXR_THROW_LENGTH_ERROR(); + } pos_ = pos; } @@ -61,17 +99,20 @@ class InMemoryIStream : public OpenEXR::IStream { } // namespace +bool CanDecodeEXR() { return true; } + Status DecodeImageEXR(Span bytes, const ColorHints& color_hints, - const SizeConstraints& constraints, - PackedPixelFile* ppf) { + PackedPixelFile* ppf, + const SizeConstraints* constraints) { InMemoryIStream is(bytes); #ifdef __EXCEPTIONS std::unique_ptr input_ptr; try { - input_ptr.reset(new OpenEXR::RgbaInputFile(is)); + input_ptr = jxl::make_unique(is); } catch (...) { - return JXL_FAILURE("OpenEXR failed to parse input"); + // silently return false if it is not an EXR file + return false; } OpenEXR::RgbaInputFile& input = *input_ptr; #else @@ -87,7 +128,7 @@ Status DecodeImageEXR(Span bytes, const ColorHints& color_hints, const float intensity_target = OpenEXR::hasWhiteLuminance(input.header()) ? OpenEXR::whiteLuminance(input.header()) - : kDefaultIntensityTarget; + : 0; auto image_size = input.displayWindow().size(); // Size is computed as max - min, but both bounds are inclusive. @@ -108,7 +149,12 @@ Status DecodeImageEXR(Span bytes, const ColorHints& color_hints, }; ppf->frames.clear(); // Allocates the frame buffer. - ppf->frames.emplace_back(image_size.x, image_size.y, format); + { + JXL_ASSIGN_OR_RETURN( + PackedFrame frame, + PackedFrame::Create(image_size.x, image_size.y, format)); + ppf->frames.emplace_back(std::move(frame)); + } const auto& frame = ppf->frames.back(); const int row_size = input.dataWindow().size().x + 1; @@ -144,6 +190,7 @@ Status DecodeImageEXR(Span bytes, const ColorHints& color_hints, std::min(input.dataWindow().max.x, input.displayWindow().max.x); ++exr_x) { const int image_x = exr_x - input.displayWindow().min.x; + // TODO(eustas): UB: OpenEXR::Rgba is not TriviallyCopyable memcpy(row + image_x * pixel_size, input_row + (exr_x - input.dataWindow().min.x), pixel_size); } @@ -174,7 +221,7 @@ Status DecodeImageEXR(Span bytes, const ColorHints& color_hints, if (has_alpha) { ppf->info.alpha_bits = kExrAlphaBits; ppf->info.alpha_exponent_bits = ppf->info.exponent_bits_per_sample; - ppf->info.alpha_premultiplied = true; + ppf->info.alpha_premultiplied = JXL_TRUE; } ppf->info.intensity_target = intensity_target; return true; @@ -182,3 +229,5 @@ Status DecodeImageEXR(Span bytes, const ColorHints& color_hints, } // namespace extras } // namespace jxl + +#endif // JPEGXL_ENABLE_EXR diff --git a/media/libjxl/src/lib/extras/dec/exr.h b/media/libjxl/src/lib/extras/dec/exr.h index 6af4e6becf..65078d030f 100644 --- a/media/libjxl/src/lib/extras/dec/exr.h +++ b/media/libjxl/src/lib/extras/dec/exr.h @@ -8,20 +8,26 @@ // Decodes OpenEXR images in memory. +#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" -#include "lib/jxl/codec_in_out.h" namespace jxl { + +struct SizeConstraints; + namespace extras { +bool CanDecodeEXR(); + // Decodes `bytes` into `ppf`. color_hints are ignored. Status DecodeImageEXR(Span bytes, const ColorHints& color_hints, - const SizeConstraints& constraints, PackedPixelFile* ppf); + PackedPixelFile* ppf, + const SizeConstraints* constraints = nullptr); } // namespace extras } // namespace jxl diff --git a/media/libjxl/src/lib/extras/dec/gif.cc b/media/libjxl/src/lib/extras/dec/gif.cc index 5167bf5fa0..a87beb901a 100644 --- a/media/libjxl/src/lib/extras/dec/gif.cc +++ b/media/libjxl/src/lib/extras/dec/gif.cc @@ -5,20 +5,27 @@ #include "lib/extras/dec/gif.h" -#include -#include +#include "lib/jxl/base/status.h" +#if JPEGXL_ENABLE_GIF +#include +#endif +#include + +#include #include #include #include -#include "jxl/codestream_header.h" +#include "lib/extras/size_constraints.h" #include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/sanitizers.h" +#include "lib/jxl/base/rect.h" +#include "lib/jxl/base/sanitizers.h" namespace jxl { namespace extras { +#if JPEGXL_ENABLE_GIF namespace { struct ReadState { @@ -38,41 +45,38 @@ struct PackedRgb { uint8_t r, g, b; }; -// Gif does not support partial transparency, so this considers any nonzero -// alpha channel value as opaque. -bool AllOpaque(const PackedImage& color) { - for (size_t y = 0; y < color.ysize; ++y) { - const PackedRgba* const JXL_RESTRICT row = - static_cast(color.pixels()) + y * color.xsize; - for (size_t x = 0; x < color.xsize; ++x) { - if (row[x].a == 0) { - return false; - } - } - } - return true; -} - -void ensure_have_alpha(PackedFrame* frame) { - if (!frame->extra_channels.empty()) return; +Status ensure_have_alpha(PackedFrame* frame) { + if (!frame->extra_channels.empty()) return true; const JxlPixelFormat alpha_format{ /*num_channels=*/1u, /*data_type=*/JXL_TYPE_UINT8, /*endianness=*/JXL_NATIVE_ENDIAN, /*align=*/0, }; - frame->extra_channels.emplace_back(frame->color.xsize, frame->color.ysize, - alpha_format); + JXL_ASSIGN_OR_RETURN(PackedImage image, + PackedImage::Create(frame->color.xsize, + frame->color.ysize, alpha_format)); + frame->extra_channels.emplace_back(std::move(image)); // We need to set opaque-by-default. std::fill_n(static_cast(frame->extra_channels[0].pixels()), frame->color.xsize * frame->color.ysize, 255u); + return true; +} +} // namespace +#endif + +bool CanDecodeGIF() { +#if JPEGXL_ENABLE_GIF + return true; +#else + return false; +#endif } -} // namespace - Status DecodeImageGIF(Span bytes, const ColorHints& color_hints, - const SizeConstraints& constraints, - PackedPixelFile* ppf) { + PackedPixelFile* ppf, + const SizeConstraints* constraints) { +#if JPEGXL_ENABLE_GIF int error = GIF_OK; ReadState state = {bytes}; const auto ReadFromSpan = [](GifFileType* const gif, GifByteType* const bytes, @@ -83,7 +87,7 @@ Status DecodeImageGIF(Span bytes, const ColorHints& color_hints, n = state->bytes.size(); } memcpy(bytes, state->bytes.data(), n); - state->bytes.remove_prefix(n); + if (!state->bytes.remove_prefix(n)) return 0; return n; }; GifUniquePtr gif(DGifOpen(&state, ReadFromSpan, &error)); @@ -111,20 +115,20 @@ Status DecodeImageGIF(Span bytes, const ColorHints& color_hints, sizeof(*gif->SavedImages) * gif->ImageCount); JXL_RETURN_IF_ERROR( - VerifyDimensions(&constraints, gif->SWidth, gif->SHeight)); + VerifyDimensions(constraints, gif->SWidth, gif->SHeight)); uint64_t total_pixel_count = static_cast(gif->SWidth) * gif->SHeight; for (int i = 0; i < gif->ImageCount; ++i) { const SavedImage& image = gif->SavedImages[i]; uint32_t w = image.ImageDesc.Width; uint32_t h = image.ImageDesc.Height; - JXL_RETURN_IF_ERROR(VerifyDimensions(&constraints, w, h)); + JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, w, h)); uint64_t pixel_count = static_cast(w) * h; if (total_pixel_count + pixel_count < total_pixel_count) { return JXL_FAILURE("Image too big"); } total_pixel_count += pixel_count; - if (total_pixel_count > constraints.dec_max_pixels) { + if (constraints && (total_pixel_count > constraints->dec_max_pixels)) { return JXL_FAILURE("Image too big"); } } @@ -138,8 +142,8 @@ Status DecodeImageGIF(Span bytes, const ColorHints& color_hints, } if (gif->ImageCount > 1) { - ppf->info.have_animation = true; - // Delays in GIF are specified in 100ths of a second. + ppf->info.have_animation = JXL_TRUE; + // Delays in GIF are specified in censiseconds. ppf->info.animation.tps_numerator = 100; ppf->info.animation.tps_denominator = 1; } @@ -188,7 +192,9 @@ Status DecodeImageGIF(Span bytes, const ColorHints& color_hints, } const PackedRgba background_rgba{background_color.Red, background_color.Green, background_color.Blue, 0}; - PackedFrame canvas(gif->SWidth, gif->SHeight, canvas_format); + JXL_ASSIGN_OR_RETURN( + PackedFrame canvas, + PackedFrame::Create(gif->SWidth, gif->SHeight, canvas_format)); std::fill_n(static_cast(canvas.color.pixels()), canvas.color.xsize * canvas.color.ysize, background_rgba); Rect canvas_rect{0, 0, canvas.color.xsize, canvas.color.ysize}; @@ -232,26 +238,33 @@ Status DecodeImageGIF(Span bytes, const ColorHints& color_hints, } // Allocates the frame buffer. - ppf->frames.emplace_back(total_rect.xsize(), total_rect.ysize(), - packed_frame_format); + { + JXL_ASSIGN_OR_RETURN( + PackedFrame frame, + PackedFrame::Create(total_rect.xsize(), total_rect.ysize(), + packed_frame_format)); + ppf->frames.emplace_back(std::move(frame)); + } + PackedFrame* frame = &ppf->frames.back(); // We cannot tell right from the start whether there will be a // need for an alpha channel. This is discovered only as soon as // we see a transparent pixel. We hence initialize alpha lazily. - auto set_pixel_alpha = [&frame](size_t x, size_t y, uint8_t a) { + auto set_pixel_alpha = [&frame](size_t x, size_t y, uint8_t a) -> Status { // If we do not have an alpha-channel and a==255 (fully opaque), // we can skip setting this pixel-value and rely on // "no alpha channel = no transparency". - if (a == 255 && !frame->extra_channels.empty()) return; - ensure_have_alpha(frame); + if (a == 255 && !frame->extra_channels.empty()) return true; + JXL_RETURN_IF_ERROR(ensure_have_alpha(frame)); static_cast( frame->extra_channels[0].pixels())[y * frame->color.xsize + x] = a; + return true; }; const ColorMapObject* const color_map = image.ImageDesc.ColorMap ? image.ImageDesc.ColorMap : gif->SColorMap; - JXL_CHECK(color_map); + JXL_ENSURE(color_map); msan::UnpoisonMemory(color_map, sizeof(*color_map)); msan::UnpoisonMemory(color_map->Colors, sizeof(*color_map->Colors) * color_map->ColorCount); @@ -303,8 +316,10 @@ Status DecodeImageGIF(Span bytes, const ColorHints& color_hints, } // Update the canvas by creating a copy first. - PackedImage new_canvas_image(canvas.color.xsize, canvas.color.ysize, - canvas.color.format); + JXL_ASSIGN_OR_RETURN( + PackedImage new_canvas_image, + PackedImage::Create(canvas.color.xsize, canvas.color.ysize, + canvas.color.format)); memcpy(new_canvas_image.pixels(), canvas.color.pixels(), new_canvas_image.pixels_size); for (size_t y = 0, byte_index = 0; y < image_rect.ysize(); ++y) { @@ -340,7 +355,7 @@ Status DecodeImageGIF(Span bytes, const ColorHints& color_hints, row_out[x].r = row_in[x].r; row_out[x].g = row_in[x].g; row_out[x].b = row_in[x].b; - set_pixel_alpha(x, y, row_in[x].a); + JXL_RETURN_IF_ERROR(set_pixel_alpha(x, y, row_in[x].a)); } } } else { @@ -357,14 +372,14 @@ Status DecodeImageGIF(Span bytes, const ColorHints& color_hints, row[x].r = 0; row[x].g = 0; row[x].b = 0; - set_pixel_alpha(x, y, 0); + JXL_RETURN_IF_ERROR(set_pixel_alpha(x, y, 0)); continue; } GifColorType color = color_map->Colors[byte]; row[x].r = color.Red; row[x].g = color.Green; row[x].b = color.Blue; - set_pixel_alpha(x, y, 255); + JXL_RETURN_IF_ERROR(set_pixel_alpha(x, y, 255)); } } } @@ -404,10 +419,13 @@ Status DecodeImageGIF(Span bytes, const ColorHints& color_hints, } if (seen_alpha) { for (PackedFrame& frame : ppf->frames) { - ensure_have_alpha(&frame); + JXL_RETURN_IF_ERROR(ensure_have_alpha(&frame)); } } return true; +#else + return false; +#endif } } // namespace extras diff --git a/media/libjxl/src/lib/extras/dec/gif.h b/media/libjxl/src/lib/extras/dec/gif.h index b359517288..4d5be8664e 100644 --- a/media/libjxl/src/lib/extras/dec/gif.h +++ b/media/libjxl/src/lib/extras/dec/gif.h @@ -15,14 +15,19 @@ #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" -#include "lib/jxl/codec_in_out.h" namespace jxl { + +struct SizeConstraints; + namespace extras { +bool CanDecodeGIF(); + // Decodes `bytes` into `ppf`. color_hints are ignored. Status DecodeImageGIF(Span bytes, const ColorHints& color_hints, - const SizeConstraints& constraints, PackedPixelFile* ppf); + PackedPixelFile* ppf, + const SizeConstraints* constraints = nullptr); } // namespace extras } // namespace jxl diff --git a/media/libjxl/src/lib/extras/dec/jpegli.cc b/media/libjxl/src/lib/extras/dec/jpegli.cc new file mode 100644 index 0000000000..c307105139 --- /dev/null +++ b/media/libjxl/src/lib/extras/dec/jpegli.cc @@ -0,0 +1,286 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "lib/extras/dec/jpegli.h" + +#include + +#include +#include +#include +#include +#include + +#include "lib/jpegli/decode.h" +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/sanitizers.h" +#include "lib/jxl/base/status.h" + +namespace jxl { +namespace extras { + +namespace { + +constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69, + 0x66, 0x00, 0x00}; +constexpr int kExifMarker = JPEG_APP0 + 1; +constexpr int kICCMarker = JPEG_APP0 + 2; + +inline bool IsJPG(const std::vector& bytes) { + if (bytes.size() < 2) return false; + if (bytes[0] != 0xFF || bytes[1] != 0xD8) return false; + return true; +} + +bool MarkerIsExif(const jpeg_saved_marker_ptr marker) { + return marker->marker == kExifMarker && + marker->data_length >= sizeof kExifSignature + 2 && + std::equal(std::begin(kExifSignature), std::end(kExifSignature), + marker->data); +} + +Status ReadICCProfile(jpeg_decompress_struct* const cinfo, + std::vector* const icc) { + uint8_t* icc_data_ptr; + unsigned int icc_data_len; + if (jpegli_read_icc_profile(cinfo, &icc_data_ptr, &icc_data_len)) { + icc->assign(icc_data_ptr, icc_data_ptr + icc_data_len); + free(icc_data_ptr); + return true; + } + return false; +} + +void ReadExif(jpeg_decompress_struct* const cinfo, + std::vector* const exif) { + constexpr size_t kExifSignatureSize = sizeof kExifSignature; + for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr; + marker = marker->next) { + // marker is initialized by libjpeg, which we are not instrumenting with + // msan. + msan::UnpoisonMemory(marker, sizeof(*marker)); + msan::UnpoisonMemory(marker->data, marker->data_length); + if (!MarkerIsExif(marker)) continue; + size_t marker_length = marker->data_length - kExifSignatureSize; + exif->resize(marker_length); + std::copy_n(marker->data + kExifSignatureSize, marker_length, exif->data()); + return; + } +} + +JpegliDataType ConvertDataType(JxlDataType type) { + switch (type) { + case JXL_TYPE_UINT8: + return JPEGLI_TYPE_UINT8; + case JXL_TYPE_UINT16: + return JPEGLI_TYPE_UINT16; + case JXL_TYPE_FLOAT: + return JPEGLI_TYPE_FLOAT; + default: + return JPEGLI_TYPE_UINT8; + } +} + +JpegliEndianness ConvertEndianness(JxlEndianness type) { + switch (type) { + case JXL_NATIVE_ENDIAN: + return JPEGLI_NATIVE_ENDIAN; + case JXL_BIG_ENDIAN: + return JPEGLI_BIG_ENDIAN; + case JXL_LITTLE_ENDIAN: + return JPEGLI_LITTLE_ENDIAN; + default: + return JPEGLI_NATIVE_ENDIAN; + } +} + +JxlColorSpace ConvertColorSpace(J_COLOR_SPACE colorspace) { + switch (colorspace) { + case JCS_GRAYSCALE: + return JXL_COLOR_SPACE_GRAY; + case JCS_RGB: + return JXL_COLOR_SPACE_RGB; + default: + return JXL_COLOR_SPACE_UNKNOWN; + } +} + +void MyErrorExit(j_common_ptr cinfo) { + jmp_buf* env = static_cast(cinfo->client_data); + (*cinfo->err->output_message)(cinfo); + jpegli_destroy_decompress(reinterpret_cast(cinfo)); + longjmp(*env, 1); +} + +void MyOutputMessage(j_common_ptr cinfo) { + if (JXL_IS_DEBUG_BUILD) { + char buf[JMSG_LENGTH_MAX + 1]; + (*cinfo->err->format_message)(cinfo, buf); + buf[JMSG_LENGTH_MAX] = 0; + JXL_WARNING("%s", buf); + } +} + +Status UnmapColors(uint8_t* row, size_t xsize, int components, + JSAMPARRAY colormap, size_t num_colors) { + JXL_ENSURE(colormap != nullptr); + std::vector tmp(xsize * components); + for (size_t x = 0; x < xsize; ++x) { + JXL_ENSURE(row[x] < num_colors); + for (int c = 0; c < components; ++c) { + tmp[x * components + c] = colormap[c][row[x]]; + } + } + memcpy(row, tmp.data(), tmp.size()); + return true; +} + +} // namespace + +Status DecodeJpeg(const std::vector& compressed, + const JpegDecompressParams& dparams, ThreadPool* pool, + PackedPixelFile* ppf) { + // Don't do anything for non-JPEG files (no need to report an error) + if (!IsJPG(compressed)) return false; + + // TODO(veluca): use JPEGData also for pixels? + + // We need to declare all the non-trivial destructor local variables before + // the call to setjmp(). + std::unique_ptr row; + + jpeg_decompress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + // Setup error handling in jpeg library so we can deal with broken jpegs in + // the fuzzer. + jpeg_error_mgr jerr; + jmp_buf env; + cinfo.err = jpegli_std_error(&jerr); + jerr.error_exit = &MyErrorExit; + jerr.output_message = &MyOutputMessage; + if (setjmp(env)) { + return false; + } + cinfo.client_data = static_cast(&env); + + jpegli_create_decompress(&cinfo); + jpegli_mem_src(&cinfo, + reinterpret_cast(compressed.data()), + compressed.size()); + jpegli_save_markers(&cinfo, kICCMarker, 0xFFFF); + jpegli_save_markers(&cinfo, kExifMarker, 0xFFFF); + const auto failure = [&cinfo](const char* str) -> Status { + jpegli_abort_decompress(&cinfo); + jpegli_destroy_decompress(&cinfo); + return JXL_FAILURE("%s", str); + }; + jpegli_read_header(&cinfo, TRUE); + // Might cause CPU-zip bomb. + if (cinfo.arith_code) { + return failure("arithmetic code JPEGs are not supported"); + } + int nbcomp = cinfo.num_components; + if (nbcomp != 1 && nbcomp != 3) { + std::string msg = + "unsupported number of components in JPEG: " + std::to_string(nbcomp); + return failure(msg.c_str()); + } + if (dparams.force_rgb) { + cinfo.out_color_space = JCS_RGB; + } else if (dparams.force_grayscale) { + cinfo.out_color_space = JCS_GRAYSCALE; + } + if (ReadICCProfile(&cinfo, &ppf->icc)) { + ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary; + } else { + ppf->primary_color_representation = + PackedPixelFile::kColorEncodingIsPrimary; + ppf->icc.clear(); + // Default to SRGB + ppf->color_encoding.color_space = + ConvertColorSpace(cinfo.out_color_space); + ppf->color_encoding.white_point = JXL_WHITE_POINT_D65; + ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB; + ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB; + ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL; + } + ReadExif(&cinfo, &ppf->metadata.exif); + + ppf->info.xsize = cinfo.image_width; + ppf->info.ysize = cinfo.image_height; + if (dparams.output_data_type == JXL_TYPE_UINT8) { + ppf->info.bits_per_sample = 8; + ppf->info.exponent_bits_per_sample = 0; + } else if (dparams.output_data_type == JXL_TYPE_UINT16) { + ppf->info.bits_per_sample = 16; + ppf->info.exponent_bits_per_sample = 0; + } else if (dparams.output_data_type == JXL_TYPE_FLOAT) { + ppf->info.bits_per_sample = 32; + ppf->info.exponent_bits_per_sample = 8; + } else { + return failure("unsupported data type"); + } + ppf->info.uses_original_profile = JXL_TRUE; + + // No alpha in JPG + ppf->info.alpha_bits = 0; + ppf->info.alpha_exponent_bits = 0; + ppf->info.orientation = JXL_ORIENT_IDENTITY; + + jpegli_set_output_format(&cinfo, ConvertDataType(dparams.output_data_type), + ConvertEndianness(dparams.output_endianness)); + + if (dparams.num_colors > 0) { + cinfo.quantize_colors = TRUE; + cinfo.desired_number_of_colors = dparams.num_colors; + cinfo.two_pass_quantize = static_cast(dparams.two_pass_quant); + cinfo.dither_mode = static_cast(dparams.dither_mode); + } + + jpegli_start_decompress(&cinfo); + + ppf->info.num_color_channels = cinfo.out_color_components; + const JxlPixelFormat format{ + /*num_channels=*/static_cast(cinfo.out_color_components), + dparams.output_data_type, + dparams.output_endianness, + /*align=*/0, + }; + ppf->frames.clear(); + // Allocates the frame buffer. + { + JXL_ASSIGN_OR_RETURN( + PackedFrame frame, + PackedFrame::Create(cinfo.image_width, cinfo.image_height, format)); + ppf->frames.emplace_back(std::move(frame)); + } + const auto& frame = ppf->frames.back(); + JXL_ENSURE(sizeof(JSAMPLE) * cinfo.out_color_components * + cinfo.image_width <= + frame.color.stride); + if (dparams.num_colors > 0) JXL_ENSURE(cinfo.colormap != nullptr); + + for (size_t y = 0; y < cinfo.image_height; ++y) { + JSAMPROW rows[] = {reinterpret_cast( + static_cast(frame.color.pixels()) + + frame.color.stride * y)}; + jpegli_read_scanlines(&cinfo, rows, 1); + if (dparams.num_colors > 0) { + JXL_RETURN_IF_ERROR( + UnmapColors(rows[0], cinfo.output_width, cinfo.out_color_components, + cinfo.colormap, cinfo.actual_number_of_colors)); + } + } + + jpegli_finish_decompress(&cinfo); + return true; + }; + bool success = try_catch_block(); + jpegli_destroy_decompress(&cinfo); + return success; +} + +} // namespace extras +} // namespace jxl diff --git a/media/libjxl/src/lib/extras/dec/jpegli.h b/media/libjxl/src/lib/extras/dec/jpegli.h new file mode 100644 index 0000000000..574df54c8e --- /dev/null +++ b/media/libjxl/src/lib/extras/dec/jpegli.h @@ -0,0 +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. + +#ifndef LIB_EXTRAS_DEC_JPEGLI_H_ +#define LIB_EXTRAS_DEC_JPEGLI_H_ + +// Decodes JPG pixels and metadata in memory using the libjpegli library. + +#include +#include + +#include + +#include "lib/extras/packed_image.h" +#include "lib/jxl/base/data_parallel.h" +#include "lib/jxl/base/status.h" + +namespace jxl { +namespace extras { + +struct JpegDecompressParams { + JxlDataType output_data_type = JXL_TYPE_UINT8; + JxlEndianness output_endianness = JXL_NATIVE_ENDIAN; + bool force_rgb = false; + bool force_grayscale = false; + int num_colors = 0; + bool two_pass_quant = true; + // 0 = none, 1 = ordered, 2 = Floyd-Steinberg + int dither_mode = 2; +}; + +Status DecodeJpeg(const std::vector& compressed, + const JpegDecompressParams& dparams, ThreadPool* pool, + PackedPixelFile* ppf); + +} // namespace extras +} // namespace jxl + +#endif // LIB_EXTRAS_DEC_JPEGLI_H_ diff --git a/media/libjxl/src/lib/extras/dec/jpg.cc b/media/libjxl/src/lib/extras/dec/jpg.cc index 6b92f4a8a6..35f7b51e0c 100644 --- a/media/libjxl/src/lib/extras/dec/jpg.cc +++ b/media/libjxl/src/lib/extras/dec/jpg.cc @@ -5,21 +5,25 @@ #include "lib/extras/dec/jpg.h" -#include -#include -#include +#if JPEGXL_ENABLE_JPEG +#include "lib/jxl/base/include_jpeglib.h" // NOLINT +#endif #include +#include #include #include #include +#include "lib/extras/size_constraints.h" +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/sanitizers.h" #include "lib/jxl/base/status.h" -#include "lib/jxl/sanitizers.h" namespace jxl { namespace extras { +#if JPEGXL_ENABLE_JPEG namespace { constexpr unsigned char kICCSignature[12] = { @@ -30,7 +34,7 @@ constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; constexpr int kExifMarker = JPEG_APP0 + 1; -static inline bool IsJPG(const Span bytes) { +inline bool IsJPG(const Span bytes) { if (bytes.size() < 2) return false; if (bytes[0] != 0xFF || bytes[1] != 0xD8) return false; return true; @@ -106,7 +110,7 @@ Status ReadICCProfile(jpeg_decompress_struct* const cinfo, } if (seen_markers_count != num_markers) { - JXL_DASSERT(has_num_markers); + JXL_ENSURE(has_num_markers); return JXL_FAILURE("Incomplete set of ICC chunks"); } @@ -152,20 +156,44 @@ void MyErrorExit(j_common_ptr cinfo) { } void MyOutputMessage(j_common_ptr cinfo) { -#if JXL_DEBUG_WARNING == 1 - char buf[JMSG_LENGTH_MAX + 1]; - (*cinfo->err->format_message)(cinfo, buf); - buf[JMSG_LENGTH_MAX] = 0; - JXL_WARNING("%s", buf); -#endif + if (JXL_IS_DEBUG_BUILD) { + char buf[JMSG_LENGTH_MAX + 1]; + (*cinfo->err->format_message)(cinfo, buf); + buf[JMSG_LENGTH_MAX] = 0; + JXL_WARNING("%s", buf); + } +} + +Status UnmapColors(uint8_t* row, size_t xsize, int components, + JSAMPARRAY colormap, size_t num_colors) { + JXL_ENSURE(colormap != nullptr); + std::vector tmp(xsize * components); + for (size_t x = 0; x < xsize; ++x) { + JXL_ENSURE(row[x] < num_colors); + for (int c = 0; c < components; ++c) { + tmp[x * components + c] = colormap[c][row[x]]; + } + } + memcpy(row, tmp.data(), tmp.size()); + return true; } } // namespace +#endif + +bool CanDecodeJPG() { +#if JPEGXL_ENABLE_JPEG + return true; +#else + return false; +#endif +} Status DecodeImageJPG(const Span bytes, - const ColorHints& color_hints, - const SizeConstraints& constraints, - PackedPixelFile* ppf) { + const ColorHints& color_hints, PackedPixelFile* ppf, + const SizeConstraints* constraints, + const JPGDecompressParams* dparams) { +#if JPEGXL_ENABLE_JPEG // Don't do anything for non-JPEG files (no need to report an error) if (!IsJPG(bytes)) return false; @@ -176,10 +204,7 @@ Status DecodeImageJPG(const Span bytes, std::unique_ptr row; const auto try_catch_block = [&]() -> bool { - jpeg_decompress_struct cinfo; - // cinfo is initialized by libjpeg, which we are not instrumenting with - // msan, therefore we need to initialize cinfo here. - msan::UnpoisonMemory(&cinfo, sizeof(cinfo)); + jpeg_decompress_struct cinfo = {}; // Setup error handling in jpeg library so we can deal with broken jpegs in // the fuzzer. jpeg_error_mgr jerr; @@ -207,8 +232,7 @@ Status DecodeImageJPG(const Span bytes, if (read_header_result == JPEG_SUSPENDED) { return failure("truncated JPEG input"); } - if (!VerifyDimensions(&constraints, cinfo.image_width, - cinfo.image_height)) { + if (!VerifyDimensions(constraints, cinfo.image_width, cinfo.image_height)) { return failure("image too big"); } // Might cause CPU-zip bomb. @@ -219,7 +243,11 @@ Status DecodeImageJPG(const Span bytes, if (nbcomp != 1 && nbcomp != 3) { return failure("unsupported number of components in JPEG"); } - if (!ReadICCProfile(&cinfo, &ppf->icc)) { + if (ReadICCProfile(&cinfo, &ppf->icc)) { + ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary; + } else { + ppf->primary_color_representation = + PackedPixelFile::kColorEncodingIsPrimary; ppf->icc.clear(); // Default to SRGB // Actually, (cinfo.output_components == nbcomp) will be checked after @@ -241,9 +269,9 @@ Status DecodeImageJPG(const Span bytes, ppf->info.ysize = cinfo.image_height; // Original data is uint, so exponent_bits_per_sample = 0. ppf->info.bits_per_sample = BITS_IN_JSAMPLE; - JXL_ASSERT(BITS_IN_JSAMPLE == 8 || BITS_IN_JSAMPLE == 16); + static_assert(BITS_IN_JSAMPLE == 8 || BITS_IN_JSAMPLE == 16); ppf->info.exponent_bits_per_sample = 0; - ppf->info.uses_original_profile = true; + ppf->info.uses_original_profile = JXL_TRUE; // No alpha in JPG ppf->info.alpha_bits = 0; @@ -252,22 +280,50 @@ Status DecodeImageJPG(const Span bytes, ppf->info.num_color_channels = nbcomp; ppf->info.orientation = JXL_ORIENT_IDENTITY; + if (dparams && dparams->num_colors > 0) { + cinfo.quantize_colors = TRUE; + cinfo.desired_number_of_colors = dparams->num_colors; + cinfo.two_pass_quantize = static_cast(dparams->two_pass_quant); + cinfo.dither_mode = static_cast(dparams->dither_mode); + } + jpeg_start_decompress(&cinfo); - JXL_ASSERT(cinfo.output_components == nbcomp); + JXL_ENSURE(cinfo.out_color_components == nbcomp); + JxlDataType data_type = + ppf->info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16; const JxlPixelFormat format{ /*num_channels=*/static_cast(nbcomp), - /*data_type=*/BITS_IN_JSAMPLE == 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16, + data_type, /*endianness=*/JXL_NATIVE_ENDIAN, /*align=*/0, }; ppf->frames.clear(); // Allocates the frame buffer. - ppf->frames.emplace_back(cinfo.image_width, cinfo.image_height, format); + { + JXL_ASSIGN_OR_RETURN( + PackedFrame frame, + PackedFrame::Create(cinfo.image_width, cinfo.image_height, format)); + ppf->frames.emplace_back(std::move(frame)); + } const auto& frame = ppf->frames.back(); - JXL_ASSERT(sizeof(JSAMPLE) * cinfo.output_components * cinfo.image_width <= + JXL_ENSURE(sizeof(JSAMPLE) * cinfo.out_color_components * + cinfo.image_width <= frame.color.stride); + if (cinfo.quantize_colors) { + JSAMPLE** colormap = cinfo.colormap; + jxl::msan::UnpoisonMemory(reinterpret_cast(colormap), + cinfo.out_color_components * sizeof(JSAMPLE*)); + for (int c = 0; c < cinfo.out_color_components; ++c) { + jxl::msan::UnpoisonMemory( + reinterpret_cast(colormap[c]), + cinfo.actual_number_of_colors * sizeof(JSAMPLE)); + } + } + if (dparams && dparams->num_colors > 0) { + JXL_ENSURE(cinfo.colormap != nullptr); + } for (size_t y = 0; y < cinfo.image_height; ++y) { JSAMPROW rows[] = {reinterpret_cast( static_cast(frame.color.pixels()) + @@ -275,6 +331,11 @@ Status DecodeImageJPG(const Span bytes, jpeg_read_scanlines(&cinfo, rows, 1); msan::UnpoisonMemory(rows[0], sizeof(JSAMPLE) * cinfo.output_components * cinfo.image_width); + if (dparams && dparams->num_colors > 0) { + JXL_RETURN_IF_ERROR( + UnmapColors(rows[0], cinfo.output_width, cinfo.out_color_components, + cinfo.colormap, cinfo.actual_number_of_colors)); + } } jpeg_finish_decompress(&cinfo); @@ -283,6 +344,9 @@ Status DecodeImageJPG(const Span bytes, }; return try_catch_block(); +#else + return false; +#endif } } // namespace extras diff --git a/media/libjxl/src/lib/extras/dec/jpg.h b/media/libjxl/src/lib/extras/dec/jpg.h index 66b3452888..6e7b2f786b 100644 --- a/media/libjxl/src/lib/extras/dec/jpg.h +++ b/media/libjxl/src/lib/extras/dec/jpg.h @@ -13,19 +13,31 @@ #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" -#include "lib/jxl/codec_in_out.h" namespace jxl { + +struct SizeConstraints; + namespace extras { +bool CanDecodeJPG(); + +struct JPGDecompressParams { + int num_colors = 0; + bool two_pass_quant = false; + // 0 = none, 1 = ordered, 2 = Floyd-Steinberg + int dither_mode = 0; +}; + // Decodes `bytes` into `ppf`. color_hints are ignored. // `elapsed_deinterleave`, if non-null, will be set to the time (in seconds) // that it took to deinterleave the raw JSAMPLEs to planar floats. Status DecodeImageJPG(Span bytes, const ColorHints& color_hints, - const SizeConstraints& constraints, PackedPixelFile* ppf); + PackedPixelFile* ppf, + const SizeConstraints* constraints = nullptr, + const JPGDecompressParams* dparams = nullptr); } // namespace extras } // namespace jxl diff --git a/media/libjxl/src/lib/extras/dec/jxl.cc b/media/libjxl/src/lib/extras/dec/jxl.cc index 0e1035646d..83170b2b9f 100644 --- a/media/libjxl/src/lib/extras/dec/jxl.cc +++ b/media/libjxl/src/lib/extras/dec/jxl.cc @@ -5,26 +5,51 @@ #include "lib/extras/dec/jxl.h" -#include "jxl/decode.h" -#include "jxl/decode_cxx.h" -#include "jxl/types.h" +#include +#include +#include +#include +#include + +#include // PRIu32 +#include +#include +#include +#include +#include + +#include "lib/extras/common.h" #include "lib/extras/dec/color_description.h" -#include "lib/extras/enc/encode.h" +#include "lib/jxl/base/common.h" +#include "lib/jxl/base/exif.h" #include "lib/jxl/base/printf_macros.h" +#include "lib/jxl/base/status.h" namespace jxl { namespace extras { namespace { -struct BoxProcessor { - BoxProcessor(JxlDecoder* dec) : dec_(dec) { Reset(); } +#define QUIT(M) \ + fprintf(stderr, "%s\n", M); \ + return false; - void InitializeOutput(std::vector* out) { +struct BoxProcessor { + explicit BoxProcessor(JxlDecoder* dec) : dec_(dec) { Reset(); } + + bool InitializeOutput(std::vector* out) { + if (out == nullptr) { + fprintf(stderr, "internal: out == nullptr\n"); + return false; + } box_data_ = out; - AddMoreOutput(); + return AddMoreOutput(); } bool AddMoreOutput() { + if (box_data_ == nullptr) { + fprintf(stderr, "internal: box_data_ == nullptr\n"); + return false; + } Flush(); static const size_t kBoxOutputChunkSize = 1 << 16; box_data_->resize(box_data_->size() + kBoxOutputChunkSize); @@ -68,12 +93,49 @@ struct BoxProcessor { } }; +void SetBitDepthFromDataType(JxlDataType data_type, uint32_t* bits_per_sample, + uint32_t* exponent_bits_per_sample) { + switch (data_type) { + case JXL_TYPE_UINT8: + *bits_per_sample = 8; + *exponent_bits_per_sample = 0; + break; + case JXL_TYPE_UINT16: + *bits_per_sample = 16; + *exponent_bits_per_sample = 0; + break; + case JXL_TYPE_FLOAT16: + *bits_per_sample = 16; + *exponent_bits_per_sample = 5; + break; + case JXL_TYPE_FLOAT: + *bits_per_sample = 32; + *exponent_bits_per_sample = 8; + break; + } +} + +template +void UpdateBitDepth(JxlBitDepth bit_depth, JxlDataType data_type, T* info) { + if (bit_depth.type == JXL_BIT_DEPTH_FROM_PIXEL_FORMAT) { + SetBitDepthFromDataType(data_type, &info->bits_per_sample, + &info->exponent_bits_per_sample); + } else if (bit_depth.type == JXL_BIT_DEPTH_CUSTOM) { + info->bits_per_sample = bit_depth.bits_per_sample; + info->exponent_bits_per_sample = bit_depth.exponent_bits_per_sample; + } +} + } // namespace bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, const JXLDecompressParams& dparams, size_t* decoded_bytes, PackedPixelFile* ppf, std::vector* jpeg_bytes) { - auto decoder = JxlDecoderMake(/*memory_manager=*/nullptr); + JxlSignature sig = JxlSignatureCheck(bytes, bytes_size); + // silently return false if this is not a JXL file + if (sig == JXL_SIG_INVALID) return false; + + auto decoder = JxlDecoderMake(dparams.memory_manager); JxlDecoder* dec = decoder.get(); ppf->frames.clear(); @@ -84,14 +146,9 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, return false; } - JxlPixelFormat format; + JxlPixelFormat format = {}; // Initialize to calm down clang-tidy. std::vector accepted_formats = dparams.accepted_formats; - if (accepted_formats.empty()) { - for (const uint32_t num_channels : {1, 2, 3, 4}) { - accepted_formats.push_back( - {num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0}); - } - } + JxlColorEncoding color_encoding; size_t num_color_channels = 0; if (!dparams.color_space.empty()) { @@ -107,7 +164,9 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, bool can_reconstruct_jpeg = false; std::vector jpeg_data_chunk; if (jpeg_bytes != nullptr) { - jpeg_data_chunk.resize(16384); + // This bound is very likely to be enough to hold the entire + // reconstructed JPEG, to avoid having to do expensive retries. + jpeg_data_chunk.resize(bytes_size * 3 / 2 + 1024); jpeg_bytes->resize(0); } @@ -128,24 +187,28 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, } else { events |= (JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME | JXL_DEC_PREVIEW_IMAGE | JXL_DEC_BOX); + if (accepted_formats.empty()) { + // decoding just the metadata, not the pixel data + events ^= (JXL_DEC_FULL_IMAGE | JXL_DEC_PREVIEW_IMAGE); + } } if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec, events)) { fprintf(stderr, "JxlDecoderSubscribeEvents failed\n"); return false; } if (jpeg_bytes == nullptr) { - if (JXL_DEC_SUCCESS != - JxlDecoderSetRenderSpotcolors(dec, dparams.render_spotcolors)) { + if (JXL_DEC_SUCCESS != JxlDecoderSetRenderSpotcolors( + dec, TO_JXL_BOOL(dparams.render_spotcolors))) { fprintf(stderr, "JxlDecoderSetRenderSpotColors failed\n"); return false; } - if (JXL_DEC_SUCCESS != - JxlDecoderSetKeepOrientation(dec, dparams.keep_orientation)) { + if (JXL_DEC_SUCCESS != JxlDecoderSetKeepOrientation( + dec, TO_JXL_BOOL(dparams.keep_orientation))) { fprintf(stderr, "JxlDecoderSetKeepOrientation failed\n"); return false; } - if (JXL_DEC_SUCCESS != - JxlDecoderSetUnpremultiplyAlpha(dec, dparams.unpremultiply_alpha)) { + if (JXL_DEC_SUCCESS != JxlDecoderSetUnpremultiplyAlpha( + dec, TO_JXL_BOOL(dparams.unpremultiply_alpha))) { fprintf(stderr, "JxlDecoderSetUnpremultiplyAlpha failed\n"); return false; } @@ -165,7 +228,7 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, return false; } uint32_t progression_index = 0; - bool codestream_done = false; + bool codestream_done = jpeg_bytes == nullptr && accepted_formats.empty(); BoxProcessor boxes(dec); for (;;) { JxlDecoderStatus status = JxlDecoderProcessInput(dec); @@ -185,8 +248,12 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, } break; } + size_t released_size = JxlDecoderReleaseInput(dec); fprintf(stderr, - "Input file is truncated and allow_partial_input was disabled."); + "Input file is truncated (total bytes: %" PRIuS + ", processed bytes: %" PRIuS + ") and --allow_partial_files is not present.\n", + bytes_size, bytes_size - released_size); return false; } else if (status == JXL_DEC_BOX) { boxes.FinalizeOutput(); @@ -202,14 +269,20 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, box_data = &ppf->metadata.iptc; } else if (memcmp(box_type, "jumb", 4) == 0) { box_data = &ppf->metadata.jumbf; + } else if (memcmp(box_type, "jhgm", 4) == 0) { + box_data = &ppf->metadata.jhgm; } else if (memcmp(box_type, "xml ", 4) == 0) { box_data = &ppf->metadata.xmp; } if (box_data) { - boxes.InitializeOutput(box_data); + if (!boxes.InitializeOutput(box_data)) { + return false; + } } } else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) { - boxes.AddMoreOutput(); + if (!boxes.AddMoreOutput()) { + return false; + } } else if (status == JXL_DEC_JPEG_RECONSTRUCTION) { can_reconstruct_jpeg = true; // Decoding to JPEG. @@ -220,6 +293,10 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, return false; } } else if (status == JXL_DEC_JPEG_NEED_MORE_OUTPUT) { + if (jpeg_bytes == nullptr) { + fprintf(stderr, "internal: jpeg_bytes == nullptr\n"); + return false; + } // Decoded a chunk to JPEG. size_t used_jpeg_output = jpeg_data_chunk.size() - JxlDecoderReleaseJPEGBuffer(dec); @@ -240,11 +317,16 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, fprintf(stderr, "JxlDecoderGetBasicInfo failed\n"); return false; } + if (accepted_formats.empty()) continue; if (num_color_channels != 0) { // Mark the change in number of color channels due to the requested // color space. ppf->info.num_color_channels = num_color_channels; } + if (dparams.output_bitdepth.type == JXL_BIT_DEPTH_CUSTOM) { + // Select format based on custom bits per sample. + ppf->info.bits_per_sample = dparams.output_bitdepth.bits_per_sample; + } // Select format according to accepted formats. if (!jxl::extras::SelectFormat(accepted_formats, ppf->info, &format)) { fprintf(stderr, "SelectFormat failed\n"); @@ -254,9 +336,11 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, if (!have_alpha) { // Mark in the basic info that alpha channel was dropped. ppf->info.alpha_bits = 0; - } else if (dparams.unpremultiply_alpha) { - // Mark in the basic info that alpha was unpremultiplied. - ppf->info.alpha_premultiplied = false; + } else { + if (dparams.unpremultiply_alpha) { + // Mark in the basic info that alpha was unpremultiplied. + ppf->info.alpha_premultiplied = JXL_FALSE; + } } bool alpha_found = false; for (uint32_t i = 0; i < ppf->info.num_extra_channels; ++i) { @@ -273,7 +357,8 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, } std::string name(eci.name_length + 1, 0); if (JXL_DEC_SUCCESS != - JxlDecoderGetExtraChannelName(dec, i, &name[0], name.size())) { + JxlDecoderGetExtraChannelName( + dec, i, const_cast(name.data()), name.size())) { fprintf(stderr, "JxlDecoderGetExtraChannelName failed\n"); return false; } @@ -287,6 +372,7 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, "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"); @@ -297,46 +383,54 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, size_t icc_size = 0; JxlColorProfileTarget target = JXL_COLOR_PROFILE_TARGET_DATA; if (JXL_DEC_SUCCESS != - JxlDecoderGetICCProfileSize(dec, nullptr, target, &icc_size)) { + JxlDecoderGetICCProfileSize(dec, target, &icc_size)) { fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n"); } if (icc_size != 0) { ppf->icc.resize(icc_size); - if (JXL_DEC_SUCCESS != - JxlDecoderGetColorAsICCProfile(dec, nullptr, target, - ppf->icc.data(), icc_size)) { + if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile( + dec, target, ppf->icc.data(), icc_size)) { fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n"); return false; } } if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsEncodedProfile( - dec, nullptr, target, &ppf->color_encoding)) { + dec, target, &ppf->color_encoding)) { ppf->color_encoding.color_space = JXL_COLOR_SPACE_UNKNOWN; } + ppf->primary_color_representation = + PackedPixelFile::kColorEncodingIsPrimary; icc_size = 0; target = JXL_COLOR_PROFILE_TARGET_ORIGINAL; if (JXL_DEC_SUCCESS != - JxlDecoderGetICCProfileSize(dec, nullptr, target, &icc_size)) { + JxlDecoderGetICCProfileSize(dec, target, &icc_size)) { fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n"); } if (icc_size != 0) { ppf->orig_icc.resize(icc_size); if (JXL_DEC_SUCCESS != - JxlDecoderGetColorAsICCProfile(dec, nullptr, target, - ppf->orig_icc.data(), icc_size)) { + JxlDecoderGetColorAsICCProfile(dec, target, ppf->orig_icc.data(), + icc_size)) { fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n"); return false; } + ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary; } } else if (status == JXL_DEC_FRAME) { - jxl::extras::PackedFrame frame(ppf->info.xsize, ppf->info.ysize, format); + auto frame_or = jxl::extras::PackedFrame::Create(ppf->info.xsize, + ppf->info.ysize, format); + JXL_ASSIGN_OR_QUIT(jxl::extras::PackedFrame frame, + jxl::extras::PackedFrame::Create( + ppf->info.xsize, ppf->info.ysize, format), + "Failed to create image frame."); if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec, &frame.frame_info)) { fprintf(stderr, "JxlDecoderGetFrameHeader failed\n"); return false; } frame.name.resize(frame.frame_info.name_length + 1, 0); if (JXL_DEC_SUCCESS != - JxlDecoderGetFrameName(dec, &frame.name[0], frame.name.size())) { + JxlDecoderGetFrameName(dec, const_cast(frame.name.data()), + frame.name.size())) { fprintf(stderr, "JxlDecoderGetFrameName failed\n"); return false; } @@ -367,9 +461,13 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, fprintf(stderr, "JxlDecoderPreviewOutBufferSize failed\n"); return false; } - ppf->preview_frame = std::unique_ptr( - new jxl::extras::PackedFrame(ppf->info.preview.xsize, - ppf->info.preview.ysize, format)); + JXL_ASSIGN_OR_QUIT( + jxl::extras::PackedImage preview_image, + jxl::extras::PackedImage::Create(ppf->info.preview.xsize, + ppf->info.preview.ysize, format), + "Failed to create preview image."); + ppf->preview_frame = + jxl::make_unique(std::move(preview_image)); if (buffer_size != ppf->preview_frame->color.pixels_size) { fprintf(stderr, "Invalid out buffer size %" PRIuS " %" PRIuS "\n", buffer_size, ppf->preview_frame->color.pixels_size); @@ -421,11 +519,26 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, return false; } } + if (JXL_DEC_SUCCESS != + JxlDecoderSetImageOutBitDepth(dec, &dparams.output_bitdepth)) { + fprintf(stderr, "JxlDecoderSetImageOutBitDepth failed\n"); + return false; + } + UpdateBitDepth(dparams.output_bitdepth, format.data_type, &ppf->info); + bool have_alpha = (format.num_channels == 2 || format.num_channels == 4); + if (have_alpha) { + // Interleaved alpha channels has the same bit depth as color channels. + ppf->info.alpha_bits = ppf->info.bits_per_sample; + ppf->info.alpha_exponent_bits = ppf->info.exponent_bits_per_sample; + } JxlPixelFormat ec_format = format; ec_format.num_channels = 1; - for (const auto& eci : ppf->extra_channels_info) { - frame.extra_channels.emplace_back(jxl::extras::PackedImage( - ppf->info.xsize, ppf->info.ysize, ec_format)); + for (auto& eci : ppf->extra_channels_info) { + JXL_ASSIGN_OR_QUIT(jxl::extras::PackedImage image, + jxl::extras::PackedImage::Create( + ppf->info.xsize, ppf->info.ysize, ec_format), + "Failed to create extra channel image."); + frame.extra_channels.emplace_back(std::move(image)); auto& ec = frame.extra_channels.back(); size_t buffer_size; if (JXL_DEC_SUCCESS != JxlDecoderExtraChannelBufferSize( @@ -446,6 +559,8 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, fprintf(stderr, "JxlDecoderSetExtraChannelBuffer failed\n"); return false; } + UpdateBitDepth(dparams.output_bitdepth, ec_format.data_type, + &eci.ec_info); } } else if (status == JXL_DEC_SUCCESS) { // Decoding finished successfully. @@ -463,6 +578,28 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, } } boxes.FinalizeOutput(); + if (!ppf->metadata.exif.empty()) { + // Verify that Exif box has a valid TIFF header at the specified offset. + // Discard bytes preceding the header. + if (ppf->metadata.exif.size() >= 4) { + uint32_t offset = LoadBE32(ppf->metadata.exif.data()); + if (offset <= ppf->metadata.exif.size() - 8) { + std::vector exif(ppf->metadata.exif.begin() + 4 + offset, + ppf->metadata.exif.end()); + bool bigendian; + if (IsExif(exif, &bigendian)) { + ppf->metadata.exif = std::move(exif); + } else { + fprintf(stderr, "Warning: invalid TIFF header in Exif\n"); + } + } else { + fprintf(stderr, "Warning: invalid Exif offset: %" PRIu32 "\n", offset); + } + } else { + fprintf(stderr, "Warning: invalid Exif length: %" PRIuS "\n", + ppf->metadata.exif.size()); + } + } if (jpeg_bytes != nullptr) { if (!can_reconstruct_jpeg) return false; size_t used_jpeg_output = diff --git a/media/libjxl/src/lib/extras/dec/jxl.h b/media/libjxl/src/lib/extras/dec/jxl.h index c462fa4b74..7c80280c03 100644 --- a/media/libjxl/src/lib/extras/dec/jxl.h +++ b/media/libjxl/src/lib/extras/dec/jxl.h @@ -8,14 +8,16 @@ // Decodes JPEG XL images in memory. -#include +#include +#include +#include +#include +#include #include #include #include -#include "jxl/parallel_runner.h" -#include "jxl/types.h" #include "lib/extras/packed_image.h" namespace jxl { @@ -38,6 +40,9 @@ struct JXLDecompressParams { JxlParallelRunner runner; void* runner_opaque = nullptr; + // If memory_manager is set, decoder uses it. + JxlMemoryManager* memory_manager = nullptr; + // Whether truncated input should be treated as an error. bool allow_partial_input = false; @@ -53,6 +58,9 @@ struct JXLDecompressParams { bool use_image_callback = true; // Whether to unpremultiply colors for associated alpha channels. bool unpremultiply_alpha = false; + + // Controls the effective bit depth of the output pixels. + JxlBitDepth output_bitdepth = {JXL_BIT_DEPTH_FROM_PIXEL_FORMAT, 0, 0}; }; bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, diff --git a/media/libjxl/src/lib/extras/dec/pgx.cc b/media/libjxl/src/lib/extras/dec/pgx.cc index 1417348c61..4499069d77 100644 --- a/media/libjxl/src/lib/extras/dec/pgx.cc +++ b/media/libjxl/src/lib/extras/dec/pgx.cc @@ -7,6 +7,7 @@ #include +#include "lib/extras/size_constraints.h" #include "lib/jxl/base/bits.h" #include "lib/jxl/base/compiler_specific.h" @@ -145,15 +146,14 @@ class Parser { } // namespace Status DecodeImagePGX(const Span bytes, - const ColorHints& color_hints, - const SizeConstraints& constraints, - PackedPixelFile* ppf) { + const ColorHints& color_hints, PackedPixelFile* ppf, + const SizeConstraints* constraints) { Parser parser(bytes); HeaderPGX header = {}; - const uint8_t* pos; + const uint8_t* pos = nullptr; if (!parser.ParseHeader(&header, &pos)) return false; JXL_RETURN_IF_ERROR( - VerifyDimensions(&constraints, header.xsize, header.ysize)); + VerifyDimensions(constraints, header.xsize, header.ysize)); if (header.bits_per_sample == 0 || header.bits_per_sample > 32) { return JXL_FAILURE("PGX: bits_per_sample invalid"); } @@ -165,7 +165,7 @@ Status DecodeImagePGX(const Span bytes, // Original data is uint, so exponent_bits_per_sample = 0. ppf->info.bits_per_sample = header.bits_per_sample; ppf->info.exponent_bits_per_sample = 0; - ppf->info.uses_original_profile = true; + ppf->info.uses_original_profile = JXL_TRUE; // No alpha in PGX ppf->info.alpha_bits = 0; @@ -188,7 +188,12 @@ Status DecodeImagePGX(const Span bytes, }; ppf->frames.clear(); // Allocates the frame buffer. - ppf->frames.emplace_back(header.xsize, header.ysize, format); + { + JXL_ASSIGN_OR_RETURN( + PackedFrame frame, + PackedFrame::Create(header.xsize, header.ysize, format)); + ppf->frames.emplace_back(std::move(frame)); + } const auto& frame = ppf->frames.back(); size_t pgx_remaining_size = bytes.data() + bytes.size() - pos; if (pgx_remaining_size < frame.color.pixels_size) { diff --git a/media/libjxl/src/lib/extras/dec/pgx.h b/media/libjxl/src/lib/extras/dec/pgx.h index 38aedf51a4..ce852e6965 100644 --- a/media/libjxl/src/lib/extras/dec/pgx.h +++ b/media/libjxl/src/lib/extras/dec/pgx.h @@ -14,17 +14,19 @@ #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" -#include "lib/jxl/codec_in_out.h" namespace jxl { + +struct SizeConstraints; + namespace extras { // Decodes `bytes` into `ppf`. Status DecodeImagePGX(Span bytes, const ColorHints& color_hints, - const SizeConstraints& constraints, PackedPixelFile* ppf); + PackedPixelFile* ppf, + const SizeConstraints* constraints = nullptr); } // namespace extras } // namespace jxl diff --git a/media/libjxl/src/lib/extras/dec/pgx_test.cc b/media/libjxl/src/lib/extras/dec/pgx_test.cc index 41e6bf8106..9370e5d5ae 100644 --- a/media/libjxl/src/lib/extras/dec/pgx_test.cc +++ b/media/libjxl/src/lib/extras/dec/pgx_test.cc @@ -5,16 +5,25 @@ #include "lib/extras/dec/pgx.h" -#include "gtest/gtest.h" +#include +#include +#include + +#include "lib/extras/packed_image.h" #include "lib/extras/packed_image_convert.h" +#include "lib/jxl/base/span.h" +#include "lib/jxl/image_bundle.h" +#include "lib/jxl/image_ops.h" +#include "lib/jxl/test_memory_manager.h" +#include "lib/jxl/test_utils.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) { @@ -23,9 +32,8 @@ TEST(CodecPGXTest, Test8bits) { PackedPixelFile ppf; ThreadPool* pool = nullptr; - EXPECT_TRUE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(), - SizeConstraints(), &ppf)); - CodecInOut io; + EXPECT_TRUE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(), &ppf)); + CodecInOut io{jxl::test::MemoryManager()}; EXPECT_TRUE(ConvertPackedPixelFileToCodecInOut(ppf, pool, &io)); ScaleImage(255.f, io.Main().color()); @@ -51,9 +59,8 @@ TEST(CodecPGXTest, Test16bits) { PackedPixelFile ppf; ThreadPool* pool = nullptr; - EXPECT_TRUE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(), - SizeConstraints(), &ppf)); - CodecInOut io; + EXPECT_TRUE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(), &ppf)); + CodecInOut io{jxl::test::MemoryManager()}; EXPECT_TRUE(ConvertPackedPixelFileToCodecInOut(ppf, pool, &io)); ScaleImage(255.f, io.Main().color()); diff --git a/media/libjxl/src/lib/extras/dec/pnm.cc b/media/libjxl/src/lib/extras/dec/pnm.cc index 03aecef29c..587cd189a9 100644 --- a/media/libjxl/src/lib/extras/dec/pnm.cc +++ b/media/libjxl/src/lib/extras/dec/pnm.cc @@ -5,27 +5,24 @@ #include "lib/extras/dec/pnm.h" -#include -#include +#include +#include +#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/c_callback_support.h" +#include "lib/jxl/base/span.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; -}; - class Parser { public: explicit Parser(const Span bytes) @@ -60,8 +57,10 @@ class Parser { case 'f': header->is_gray = true; return ParseHeaderPFM(header, pos); + + default: + return false; } - return false; } // Exposed for testing @@ -165,11 +164,12 @@ class Parser { Status MatchString(const char* keyword, bool skipws = true) { const uint8_t* ppos = pos_; - while (*keyword) { + const uint8_t* kw = reinterpret_cast(keyword); + while (*kw) { if (ppos >= end_) return JXL_FAILURE("PAM: unexpected end of input"); - if (*keyword != *ppos) return false; + if (*kw != *ppos) return false; ppos++; - keyword++; + kw++; } pos_ = ppos; if (skipws) { @@ -183,16 +183,20 @@ class Parser { Status ParseHeaderPAM(HeaderPNM* header, const uint8_t** pos) { size_t depth = 3; size_t max_val = 255; + JXL_RETURN_IF_ERROR(SkipWhitespace()); while (!MatchString("ENDHDR", /*skipws=*/false)) { - JXL_RETURN_IF_ERROR(SkipWhitespace()); if (MatchString("WIDTH")) { JXL_RETURN_IF_ERROR(ParseUnsigned(&header->xsize)); + JXL_RETURN_IF_ERROR(SkipWhitespace()); } else if (MatchString("HEIGHT")) { JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize)); + JXL_RETURN_IF_ERROR(SkipWhitespace()); } else if (MatchString("DEPTH")) { JXL_RETURN_IF_ERROR(ParseUnsigned(&depth)); + JXL_RETURN_IF_ERROR(SkipWhitespace()); } else if (MatchString("MAXVAL")) { JXL_RETURN_IF_ERROR(ParseUnsigned(&max_val)); + JXL_RETURN_IF_ERROR(SkipWhitespace()); } else if (MatchString("TUPLTYPE")) { if (MatchString("RGB_ALPHA")) { header->has_alpha = true; @@ -209,6 +213,24 @@ class Parser { } else if (MatchString("BLACKANDWHITE")) { header->is_gray = true; max_val = 1; + } else if (MatchString("Alpha")) { + header->ec_types.push_back(JXL_CHANNEL_ALPHA); + } else if (MatchString("Depth")) { + header->ec_types.push_back(JXL_CHANNEL_DEPTH); + } else if (MatchString("SpotColor")) { + header->ec_types.push_back(JXL_CHANNEL_SPOT_COLOR); + } else if (MatchString("SelectionMask")) { + header->ec_types.push_back(JXL_CHANNEL_SELECTION_MASK); + } else if (MatchString("Black")) { + header->ec_types.push_back(JXL_CHANNEL_BLACK); + } else if (MatchString("CFA")) { + header->ec_types.push_back(JXL_CHANNEL_CFA); + } else if (MatchString("Thermal")) { + header->ec_types.push_back(JXL_CHANNEL_THERMAL); + } else if (MatchString("Unknown")) { + header->ec_types.push_back(JXL_CHANNEL_UNKNOWN); + } else if (MatchString("Optional")) { + header->ec_types.push_back(JXL_CHANNEL_OPTIONAL); } else { return JXL_FAILURE("PAM: unknown TUPLTYPE"); } @@ -223,13 +245,13 @@ class Parser { } size_t num_channels = header->is_gray ? 1 : 3; if (header->has_alpha) num_channels++; - if (num_channels != depth) { + if (num_channels + header->ec_types.size() != depth) { return JXL_FAILURE("PAM: bad DEPTH"); } if (max_val == 0 || max_val >= 65536) { return JXL_FAILURE("PAM: bad MAXVAL"); } - // e.g When `max_val` is 1 , we want 1 bit: + // e.g. When `max_val` is 1 , we want 1 bit: header->bits_per_sample = FloorLog2Nonzero(max_val) + 1; if ((1u << header->bits_per_sample) - 1 != max_val) return JXL_FAILURE("PNM: unsupported MaxVal (expected 2^n - 1)"); @@ -297,31 +319,141 @@ class Parser { const uint8_t* const end_; }; -Span MakeSpan(const char* str) { - return Span(reinterpret_cast(str), - strlen(str)); -} - } // namespace +struct PNMChunkedInputFrame { + JxlChunkedFrameInputSource operator()() { + return JxlChunkedFrameInputSource{ + this, + METHOD_TO_C_CALLBACK( + &PNMChunkedInputFrame::GetColorChannelsPixelFormat), + METHOD_TO_C_CALLBACK(&PNMChunkedInputFrame::GetColorChannelDataAt), + METHOD_TO_C_CALLBACK(&PNMChunkedInputFrame::GetExtraChannelPixelFormat), + METHOD_TO_C_CALLBACK(&PNMChunkedInputFrame::GetExtraChannelDataAt), + METHOD_TO_C_CALLBACK(&PNMChunkedInputFrame::ReleaseCurrentData)}; + } + + void /* NOLINT */ 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 size_t bytes_per_channel = + DivCeil(dec->header_.bits_per_sample, jxl::kBitsPerByte); + const size_t num_channels = dec->header_.is_gray ? 1 : 3; + const size_t bytes_per_pixel = num_channels * bytes_per_channel; + *row_offset = dec->header_.xsize * bytes_per_pixel; + const size_t offset = ypos * *row_offset + xpos * bytes_per_pixel; + return dec->pnm_.data() + offset + dec->data_start_; + } + + void GetExtraChannelPixelFormat(size_t ec_index, + JxlPixelFormat* pixel_format) { + (void)this; + *pixel_format = {}; + JXL_DEBUG_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) { + (void)this; + *row_offset = 0; + JXL_DEBUG_ABORT("Not implemented"); + return nullptr; + } + + void ReleaseCurrentData(const void* buffer) {} + + JxlPixelFormat format; + const ChunkedPNMDecoder* dec; +}; + +StatusOr ChunkedPNMDecoder::Init(const char* path) { + ChunkedPNMDecoder dec; + JXL_ASSIGN_OR_RETURN(dec.pnm_, MemoryMappedFile::Init(path)); + size_t size = dec.pnm_.size(); + if (size < 2) return JXL_FAILURE("Invalid ppm"); + size_t hdr_buf = std::min(size, 10 * 1024); + Span span(dec.pnm_.data(), hdr_buf); + Parser parser(span); + HeaderPNM& header = dec.header_; + const uint8_t* pos = nullptr; + if (!parser.ParseHeader(&header, &pos)) { + return StatusCode::kGenericError; + } + dec.data_start_ = pos - span.data(); + + 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"); + } + + const size_t bytes_per_channel = + DivCeil(dec.header_.bits_per_sample, jxl::kBitsPerByte); + const size_t num_channels = dec.header_.is_gray ? 1 : 3; + const size_t bytes_per_pixel = num_channels * bytes_per_channel; + size_t row_size = dec.header_.xsize * bytes_per_pixel; + if (size < header.ysize * row_size + dec.data_start_) { + return JXL_FAILURE("PNM file too small"); + } + return dec; +} + +jxl::Status ChunkedPNMDecoder::InitializePPF(const ColorHints& color_hints, + PackedPixelFile* ppf) { + // 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, + }; + + PNMChunkedInputFrame frame; + frame.format = format; + frame.dec = this; + ppf->chunked_frames.emplace_back(header_.xsize, header_.ysize, frame); + return true; +} + Status DecodeImagePNM(const Span bytes, - const ColorHints& color_hints, - const SizeConstraints& constraints, - PackedPixelFile* ppf) { + const ColorHints& color_hints, PackedPixelFile* ppf, + const SizeConstraints* constraints) { Parser parser(bytes); HeaderPNM header = {}; const uint8_t* pos = nullptr; if (!parser.ParseHeader(&header, &pos)) return false; JXL_RETURN_IF_ERROR( - VerifyDimensions(&constraints, header.xsize, header.ysize)); + VerifyDimensions(constraints, header.xsize, header.ysize)); 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)); @@ -341,7 +473,17 @@ Status DecodeImagePNM(const Span bytes, ppf->info.alpha_bits = (header.has_alpha ? ppf->info.bits_per_sample : 0); ppf->info.alpha_exponent_bits = 0; ppf->info.num_color_channels = (header.is_gray ? 1 : 3); - ppf->info.num_extra_channels = (header.has_alpha ? 1 : 0); + uint32_t num_alpha_channels = (header.has_alpha ? 1 : 0); + uint32_t num_interleaved_channels = + ppf->info.num_color_channels + num_alpha_channels; + ppf->info.num_extra_channels = num_alpha_channels + header.ec_types.size(); + + for (auto type : header.ec_types) { + PackedExtraChannel pec = {}; + pec.ec_info.bits_per_sample = ppf->info.bits_per_sample; + pec.ec_info.type = type; + ppf->extra_channels_info.emplace_back(std::move(pec)); + } JxlDataType data_type; if (header.floating_point) { @@ -356,64 +498,73 @@ Status DecodeImagePNM(const Span bytes, } const JxlPixelFormat format{ - /*num_channels=*/ppf->info.num_color_channels + - ppf->info.num_extra_channels, + /*num_channels=*/num_interleaved_channels, /*data_type=*/data_type, /*endianness=*/header.big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN, /*align=*/0, }; + const JxlPixelFormat ec_format{1, format.data_type, format.endianness, 0}; ppf->frames.clear(); - ppf->frames.emplace_back(header.xsize, header.ysize, format); + { + JXL_ASSIGN_OR_RETURN( + PackedFrame frame, + PackedFrame::Create(header.xsize, header.ysize, format)); + ppf->frames.emplace_back(std::move(frame)); + } auto* frame = &ppf->frames.back(); - + for (size_t i = 0; i < header.ec_types.size(); ++i) { + JXL_ASSIGN_OR_RETURN( + PackedImage ec, + PackedImage::Create(header.xsize, header.ysize, ec_format)); + frame->extra_channels.emplace_back(std::move(ec)); + } size_t pnm_remaining_size = bytes.data() + bytes.size() - pos; if (pnm_remaining_size < frame->color.pixels_size) { return JXL_FAILURE("PNM file too small"); } - const bool flipped_y = header.bits_per_sample == 32; // PFMs are flipped + uint8_t* out = reinterpret_cast(frame->color.pixels()); - for (size_t y = 0; y < header.ysize; ++y) { - size_t y_in = flipped_y ? header.ysize - 1 - y : y; - const uint8_t* row_in = &pos[y_in * frame->color.stride]; - uint8_t* row_out = &out[y * frame->color.stride]; - memcpy(row_out, row_in, frame->color.stride); + std::vector ec_out(header.ec_types.size()); + for (size_t i = 0; i < ec_out.size(); ++i) { + ec_out[i] = reinterpret_cast(frame->extra_channels[i].pixels()); + } + if (ec_out.empty()) { + const bool flipped_y = header.bits_per_sample == 32; // PFMs are flipped + for (size_t y = 0; y < header.ysize; ++y) { + size_t y_in = flipped_y ? header.ysize - 1 - y : y; + const uint8_t* row_in = &pos[y_in * frame->color.stride]; + uint8_t* row_out = &out[y * frame->color.stride]; + memcpy(row_out, row_in, frame->color.stride); + } + } else { + JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(data_type)); + size_t pwidth = PackedImage::BitsPerChannel(data_type) / 8; + for (size_t y = 0; y < header.ysize; ++y) { + for (size_t x = 0; x < header.xsize; ++x) { + memcpy(out, pos, frame->color.pixel_stride()); + out += frame->color.pixel_stride(); + pos += frame->color.pixel_stride(); + for (auto& p : ec_out) { + memcpy(p, pos, pwidth); + pos += pwidth; + p += pwidth; + } + } + } + } + if (ppf->info.exponent_bits_per_sample == 0) { + ppf->input_bitdepth.type = JXL_BIT_DEPTH_FROM_CODESTREAM; } return true; } -void TestCodecPNM() { - size_t u = 77777; // Initialized to wrong value. - double d = 77.77; -// Failing to parse invalid strings results in a crash if `JXL_CRASH_ON_ERROR` -// is defined and hence the tests fail. Therefore we only run these tests if -// `JXL_CRASH_ON_ERROR` is not defined. -#ifndef JXL_CRASH_ON_ERROR - JXL_CHECK(false == Parser(MakeSpan("")).ParseUnsigned(&u)); - JXL_CHECK(false == Parser(MakeSpan("+")).ParseUnsigned(&u)); - JXL_CHECK(false == Parser(MakeSpan("-")).ParseUnsigned(&u)); - JXL_CHECK(false == Parser(MakeSpan("A")).ParseUnsigned(&u)); +// Exposed for testing. +Status PnmParseSigned(Bytes str, double* v) { + return Parser(str).ParseSigned(v); +} - JXL_CHECK(false == Parser(MakeSpan("")).ParseSigned(&d)); - JXL_CHECK(false == Parser(MakeSpan("+")).ParseSigned(&d)); - JXL_CHECK(false == Parser(MakeSpan("-")).ParseSigned(&d)); - JXL_CHECK(false == Parser(MakeSpan("A")).ParseSigned(&d)); -#endif - JXL_CHECK(true == Parser(MakeSpan("1")).ParseUnsigned(&u)); - JXL_CHECK(u == 1); - - JXL_CHECK(true == Parser(MakeSpan("32")).ParseUnsigned(&u)); - JXL_CHECK(u == 32); - - JXL_CHECK(true == Parser(MakeSpan("1")).ParseSigned(&d)); - JXL_CHECK(d == 1.0); - JXL_CHECK(true == Parser(MakeSpan("+2")).ParseSigned(&d)); - JXL_CHECK(d == 2.0); - JXL_CHECK(true == Parser(MakeSpan("-3")).ParseSigned(&d)); - JXL_CHECK(std::abs(d - -3.0) < 1E-15); - JXL_CHECK(true == Parser(MakeSpan("3.141592")).ParseSigned(&d)); - JXL_CHECK(std::abs(d - 3.141592) < 1E-15); - JXL_CHECK(true == Parser(MakeSpan("-3.141592")).ParseSigned(&d)); - JXL_CHECK(std::abs(d - -3.141592) < 1E-15); +Status PnmParseUnsigned(Bytes str, size_t* v) { + return Parser(str).ParseUnsigned(v); } } // namespace extras diff --git a/media/libjxl/src/lib/extras/dec/pnm.h b/media/libjxl/src/lib/extras/dec/pnm.h index f6374830ce..a6ad14fdc8 100644 --- a/media/libjxl/src/lib/extras/dec/pnm.h +++ b/media/libjxl/src/lib/extras/dec/pnm.h @@ -15,22 +15,48 @@ #include #include "lib/extras/dec/color_hints.h" +#include "lib/extras/mmap.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" namespace jxl { + +struct SizeConstraints; + namespace extras { // Decodes `bytes` into `ppf`. color_hints may specify "color_space", which // defaults to sRGB. Status DecodeImagePNM(Span bytes, const ColorHints& color_hints, - const SizeConstraints& constraints, PackedPixelFile* ppf); + 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 +}; + +class ChunkedPNMDecoder { + public: + static StatusOr Init(const char* file_path); + // Initializes `ppf` with a pointer to this `ChunkedPNMDecoder`. + jxl::Status InitializePPF(const ColorHints& color_hints, + PackedPixelFile* ppf); + + private: + HeaderPNM header_ = {}; + size_t data_start_ = 0; + MemoryMappedFile pnm_; + + friend struct PNMChunkedInputFrame; +}; } // namespace extras } // namespace jxl diff --git a/media/libjxl/src/lib/extras/enc/apng.cc b/media/libjxl/src/lib/extras/enc/apng.cc index db6cf9ef4a..4d3ce8c17a 100644 --- a/media/libjxl/src/lib/extras/enc/apng.cc +++ b/media/libjxl/src/lib/extras/enc/apng.cc @@ -36,50 +36,70 @@ * */ -#include -#include - +#include #include #include #include "lib/extras/exif.h" #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/printf_macros.h" +#if JPEGXL_ENABLE_APNG #include "png.h" /* original (unpatched) libpng is ok */ +#endif namespace jxl { namespace extras { +#if JPEGXL_ENABLE_APNG namespace { +constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69, + 0x66, 0x00, 0x00}; + class APNGEncoder : public Encoder { public: std::vector AcceptedFormats() const override { std::vector formats; for (const uint32_t num_channels : {1, 2, 3, 4}) { - for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) { - formats.push_back(JxlPixelFormat{num_channels, data_type, - JXL_BIG_ENDIAN, /*align=*/0}); + for (const JxlDataType data_type : + {JXL_TYPE_UINT8, JXL_TYPE_UINT16, JXL_TYPE_FLOAT}) { + for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) { + formats.push_back( + JxlPixelFormat{num_channels, data_type, endianness, /*align=*/0}); + } } } return formats; } Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, ThreadPool* pool) const override { + // Encode main image frames JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); encoded_image->icc.clear(); encoded_image->bitstreams.resize(1); - return EncodePackedPixelFileToAPNG(ppf, pool, - &encoded_image->bitstreams.front()); + JXL_RETURN_IF_ERROR(EncodePackedPixelFileToAPNG( + ppf, pool, &encoded_image->bitstreams.front())); + + // Encode extra channels + for (size_t i = 0; i < ppf.extra_channels_info.size(); ++i) { + encoded_image->extra_channel_bitstreams.emplace_back(); + auto& ec_bitstreams = encoded_image->extra_channel_bitstreams.back(); + ec_bitstreams.emplace_back(); + JXL_RETURN_IF_ERROR(EncodePackedPixelFileToAPNG( + ppf, pool, &ec_bitstreams.back(), true, i)); + } + return true; } private: Status EncodePackedPixelFileToAPNG(const PackedPixelFile& ppf, ThreadPool* pool, - std::vector* bytes) const; + std::vector* bytes, + bool encode_extra_channels = false, + size_t extra_channel_index = 0) const; }; -static void PngWrite(png_structp png_ptr, png_bytep data, png_size_t length) { +void PngWrite(png_structp png_ptr, png_bytep data, png_size_t length) { std::vector* bytes = static_cast*>(png_get_io_ptr(png_ptr)); bytes->insert(bytes->end(), data, data + length); @@ -96,21 +116,38 @@ class BlobsWriterPNG { // identity to avoid repeated orientation. std::vector exif = blobs.exif; ResetExifOrientation(exif); + // By convention, the data is prefixed with "Exif\0\0" when stored in + // the legacy (and non-standard) "Raw profile type exif" text chunk + // currently used here. + // TODO(user): Store Exif data in an eXIf chunk instead, which always + // begins with the TIFF header. + if (exif.size() >= sizeof kExifSignature && + memcmp(exif.data(), kExifSignature, sizeof kExifSignature) != 0) { + exif.insert(exif.begin(), kExifSignature, + kExifSignature + sizeof kExifSignature); + } JXL_RETURN_IF_ERROR(EncodeBase16("exif", exif, strings)); } if (!blobs.iptc.empty()) { JXL_RETURN_IF_ERROR(EncodeBase16("iptc", blobs.iptc, strings)); } if (!blobs.xmp.empty()) { + // TODO(user): Store XMP data in an "XML:com.adobe.xmp" text chunk + // instead. JXL_RETURN_IF_ERROR(EncodeBase16("xmp", blobs.xmp, strings)); } return true; } private: + // TODO(eustas): use array static JXL_INLINE char EncodeNibble(const uint8_t nibble) { - JXL_ASSERT(nibble < 16); - return (nibble < 10) ? '0' + nibble : 'a' + nibble - 10; + if (nibble < 16) { + return (nibble < 10) ? '0' + nibble : 'a' + nibble - 10; + } else { + JXL_DEBUG_ABORT("Internal logic error"); + return 0; + } } static Status EncodeBase16(const std::string& type, @@ -118,7 +155,7 @@ class BlobsWriterPNG { std::vector* strings) { // Encoding: base16 with newline after 72 chars. const size_t base16_size = - 2 * bytes.size() + DivCeil(bytes.size(), size_t(36)) + 1; + 2 * bytes.size() + DivCeil(bytes.size(), static_cast(36)) + 1; std::string base16; base16.reserve(base16_size); for (size_t i = 0; i < bytes.size(); ++i) { @@ -127,7 +164,7 @@ class BlobsWriterPNG { base16.push_back(EncodeNibble(bytes[i] & 0x0F)); } base16.push_back('\n'); - JXL_ASSERT(base16.length() == base16_size); + JXL_ENSURE(base16.length() == base16_size); char key[30]; snprintf(key, sizeof(key), "Raw profile type %s", type.c_str()); @@ -136,13 +173,13 @@ class BlobsWriterPNG { snprintf(header, sizeof(header), "\n%s\n%8" PRIuS, type.c_str(), bytes.size()); - strings->push_back(std::string(key)); + strings->emplace_back(key); strings->push_back(std::string(header) + base16); return true; } }; -void MaybeAddCICP(JxlColorEncoding c_enc, png_structp png_ptr, +void MaybeAddCICP(const JxlColorEncoding& c_enc, png_structp png_ptr, png_infop info_ptr) { png_byte cicp_data[4] = {}; png_unknown_chunk cicp_chunk; @@ -172,23 +209,94 @@ void MaybeAddCICP(JxlColorEncoding c_enc, png_structp png_ptr, cicp_data[3] = 1; cicp_chunk.data = cicp_data; cicp_chunk.size = sizeof(cicp_data); - cicp_chunk.location = PNG_HAVE_PLTE; + cicp_chunk.location = PNG_HAVE_IHDR; memcpy(cicp_chunk.name, "cICP", 5); - png_set_keep_unknown_chunks(png_ptr, 3, + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, reinterpret_cast("cICP"), 1); png_set_unknown_chunks(png_ptr, info_ptr, &cicp_chunk, 1); } +bool MaybeAddSRGB(const JxlColorEncoding& c_enc, png_structp png_ptr, + png_infop info_ptr) { + if (c_enc.transfer_function == JXL_TRANSFER_FUNCTION_SRGB && + (c_enc.color_space == JXL_COLOR_SPACE_GRAY || + (c_enc.color_space == JXL_COLOR_SPACE_RGB && + c_enc.primaries == JXL_PRIMARIES_SRGB && + c_enc.white_point == JXL_WHITE_POINT_D65))) { + png_set_sRGB(png_ptr, info_ptr, c_enc.rendering_intent); + png_set_cHRM_fixed(png_ptr, info_ptr, 31270, 32900, 64000, 33000, 30000, + 60000, 15000, 6000); + png_set_gAMA_fixed(png_ptr, info_ptr, 45455); + return true; + } + return false; +} + +void MaybeAddCHRM(const JxlColorEncoding& c_enc, png_structp png_ptr, + png_infop info_ptr) { + if (c_enc.color_space != JXL_COLOR_SPACE_RGB) return; + if (c_enc.primaries == 0) return; + png_set_cHRM(png_ptr, info_ptr, c_enc.white_point_xy[0], + c_enc.white_point_xy[1], c_enc.primaries_red_xy[0], + c_enc.primaries_red_xy[1], c_enc.primaries_green_xy[0], + c_enc.primaries_green_xy[1], c_enc.primaries_blue_xy[0], + c_enc.primaries_blue_xy[1]); +} + +void MaybeAddGAMA(const JxlColorEncoding& c_enc, png_structp png_ptr, + png_infop info_ptr) { + switch (c_enc.transfer_function) { + case JXL_TRANSFER_FUNCTION_LINEAR: + png_set_gAMA_fixed(png_ptr, info_ptr, PNG_FP_1); + break; + case JXL_TRANSFER_FUNCTION_SRGB: + png_set_gAMA_fixed(png_ptr, info_ptr, 45455); + break; + case JXL_TRANSFER_FUNCTION_GAMMA: + png_set_gAMA(png_ptr, info_ptr, c_enc.gamma); + break; + + default:; + // No gAMA chunk. + } +} + +void MaybeAddCLLi(const JxlColorEncoding& c_enc, const float intensity_target, + png_structp png_ptr, png_infop info_ptr) { + if (c_enc.transfer_function != JXL_TRANSFER_FUNCTION_PQ) return; + if (intensity_target == 10'000) return; + + const uint32_t max_content_light_level = + static_cast(10'000.f * Clamp1(intensity_target, 0.f, 10'000.f)); + png_byte chunk_data[8] = {}; + png_save_uint_32(chunk_data, max_content_light_level); + // Leave MaxFALL set to 0. + png_unknown_chunk chunk; + memcpy(chunk.name, "cLLi", 5); + chunk.data = chunk_data; + chunk.size = sizeof chunk_data; + chunk.location = PNG_HAVE_IHDR; + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, + reinterpret_cast("cLLi"), 1); + png_set_unknown_chunks(png_ptr, info_ptr, &chunk, 1); +} + Status APNGEncoder::EncodePackedPixelFileToAPNG( - const PackedPixelFile& ppf, ThreadPool* pool, - std::vector* bytes) const { - size_t xsize = ppf.info.xsize; - size_t ysize = ppf.info.ysize; - bool has_alpha = ppf.info.alpha_bits != 0; - bool is_gray = ppf.info.num_color_channels == 1; - size_t color_channels = ppf.info.num_color_channels; + const PackedPixelFile& ppf, ThreadPool* pool, std::vector* bytes, + bool encode_extra_channels, size_t extra_channel_index) const { + JxlExtraChannelInfo ec_info{}; + if (encode_extra_channels) { + if (ppf.extra_channels_info.size() <= extra_channel_index) { + return JXL_FAILURE("Invalid index for extra channel"); + } + ec_info = ppf.extra_channels_info[extra_channel_index].ec_info; + } + + bool has_alpha = !encode_extra_channels && (ppf.info.alpha_bits != 0); + bool is_gray = encode_extra_channels || (ppf.info.num_color_channels == 1); + size_t color_channels = + encode_extra_channels ? 1 : ppf.info.num_color_channels; size_t num_channels = color_channels + (has_alpha ? 1 : 0); - size_t num_samples = num_channels * xsize * ysize; if (!ppf.info.have_animation && ppf.frames.size() != 1) { return JXL_FAILURE("Invalid number of frames"); @@ -198,11 +306,27 @@ Status APNGEncoder::EncodePackedPixelFileToAPNG( size_t anim_chunks = 0; for (const auto& frame : ppf.frames) { - JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info)); + const PackedImage& color = encode_extra_channels + ? frame.extra_channels[extra_channel_index] + : frame.color; - const PackedImage& color = frame.color; + size_t xsize = color.xsize; + size_t ysize = color.ysize; + size_t num_samples = num_channels * xsize * ysize; + + uint32_t bits_per_sample = encode_extra_channels ? ec_info.bits_per_sample + : ppf.info.bits_per_sample; + if (!encode_extra_channels) { + JXL_RETURN_IF_ERROR(VerifyPackedImage(color, ppf.info)); + } else { + JXL_RETURN_IF_ERROR(VerifyFormat(color.format)); + JXL_RETURN_IF_ERROR(VerifyBitDepth(color.format.data_type, + bits_per_sample, + ec_info.exponent_bits_per_sample)); + } const JxlPixelFormat format = color.format; const uint8_t* in = reinterpret_cast(color.pixels()); + JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(format.data_type)); size_t data_bits_per_sample = PackedImage::BitsPerChannel(format.data_type); size_t bytes_per_sample = data_bits_per_sample / 8; size_t out_bytes_per_sample = bytes_per_sample > 1 ? 2 : 1; @@ -211,54 +335,55 @@ Status APNGEncoder::EncodePackedPixelFileToAPNG( std::vector out(out_size); if (format.data_type == JXL_TYPE_UINT8) { - if (ppf.info.bits_per_sample < 8) { - float mul = 255.0 / ((1u << ppf.info.bits_per_sample) - 1); + if (bits_per_sample < 8) { + float mul = 255.0 / ((1u << bits_per_sample) - 1); for (size_t i = 0; i < num_samples; ++i) { - out[i] = static_cast(in[i] * mul + 0.5); + out[i] = static_cast(std::lroundf(in[i] * mul)); } } else { - memcpy(&out[0], in, out_size); + memcpy(out.data(), in, out_size); } } else if (format.data_type == JXL_TYPE_UINT16) { - if (ppf.info.bits_per_sample < 16 || - format.endianness != JXL_BIG_ENDIAN) { - float mul = 65535.0 / ((1u << ppf.info.bits_per_sample) - 1); + if (bits_per_sample < 16 || format.endianness != JXL_BIG_ENDIAN) { + float mul = 65535.0 / ((1u << bits_per_sample) - 1); const uint8_t* p_in = in; uint8_t* p_out = out.data(); for (size_t i = 0; i < num_samples; ++i, p_in += 2, p_out += 2) { uint32_t val = (format.endianness == JXL_BIG_ENDIAN ? LoadBE16(p_in) : LoadLE16(p_in)); - StoreBE16(static_cast(val * mul + 0.5), p_out); + StoreBE16(static_cast(std::lroundf(val * mul)), p_out); } } else { - memcpy(&out[0], in, out_size); + memcpy(out.data(), in, out_size); } } else if (format.data_type == JXL_TYPE_FLOAT) { - float mul = 65535.0; + constexpr float kMul = 65535.0; const uint8_t* p_in = in; uint8_t* p_out = out.data(); - for (size_t i = 0; i < num_samples; ++i, p_in += 4, p_out += 2) { - uint32_t val = (format.endianness == JXL_BIG_ENDIAN ? LoadBE32(p_in) - : LoadLE32(p_in)); - float fval; - memcpy(&fval, &val, 4); - StoreBE16(static_cast(fval * mul + 0.5), p_out); + for (size_t i = 0; i < num_samples; + ++i, p_in += sizeof(float), p_out += 2) { + float val = + Clamp1(format.endianness == JXL_BIG_ENDIAN ? LoadBEFloat(p_in) + : format.endianness == JXL_LITTLE_ENDIAN + ? LoadLEFloat(p_in) + : *reinterpret_cast(p_in), + 0.f, 1.f); + StoreBE16(static_cast(std::lroundf(val * kMul)), p_out); } - } else { - return JXL_FAILURE("Unsupported pixel data type"); } - png_structp png_ptr; png_infop info_ptr; - png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, + nullptr); if (!png_ptr) return JXL_FAILURE("Could not init png encoder"); info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) return JXL_FAILURE("Could not init png info struct"); + png_set_compression_level(png_ptr, 1); - png_set_write_fn(png_ptr, bytes, PngWrite, NULL); + png_set_write_fn(png_ptr, bytes, PngWrite, nullptr); png_set_flush(png_ptr, 0); int width = xsize; @@ -271,12 +396,23 @@ Status APNGEncoder::EncodePackedPixelFileToAPNG( png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - if (count == 0) { - MaybeAddCICP(ppf.color_encoding, png_ptr, info_ptr); - if (!ppf.icc.empty()) { - png_set_benign_errors(png_ptr, 1); - png_set_iCCP(png_ptr, info_ptr, "1", 0, ppf.icc.data(), ppf.icc.size()); + if (count == 0 && !encode_extra_channels) { + if (!MaybeAddSRGB(ppf.color_encoding, png_ptr, info_ptr)) { + if (ppf.primary_color_representation != + PackedPixelFile::kIccIsPrimary) { + MaybeAddCICP(ppf.color_encoding, png_ptr, info_ptr); + } + if (!ppf.icc.empty()) { + png_set_benign_errors(png_ptr, 1); + png_set_iCCP(png_ptr, info_ptr, "1", 0, ppf.icc.data(), + ppf.icc.size()); + } + MaybeAddCHRM(ppf.color_encoding, png_ptr, info_ptr); + MaybeAddGAMA(ppf.color_encoding, png_ptr, info_ptr); } + MaybeAddCLLi(ppf.color_encoding, ppf.info.intensity_target, png_ptr, + info_ptr); + std::vector textstrings; JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(ppf.metadata, &textstrings)); for (size_t kk = 0; kk + 1 < textstrings.size(); kk += 2) { @@ -326,7 +462,7 @@ Status APNGEncoder::EncodePackedPixelFileToAPNG( png_write_flush(png_ptr); const size_t pos = bytes->size(); - png_write_image(png_ptr, &rows[0]); + png_write_image(png_ptr, rows.data()); png_write_flush(png_ptr); if (count > 0) { std::vector fdata(4); @@ -334,10 +470,10 @@ Status APNGEncoder::EncodePackedPixelFileToAPNG( size_t p = pos; while (p + 8 < bytes->size()) { size_t len = png_get_uint_32(bytes->data() + p); - JXL_ASSERT(bytes->operator[](p + 4) == 'I'); - JXL_ASSERT(bytes->operator[](p + 5) == 'D'); - JXL_ASSERT(bytes->operator[](p + 6) == 'A'); - JXL_ASSERT(bytes->operator[](p + 7) == 'T'); + JXL_ENSURE(bytes->operator[](p + 4) == 'I'); + JXL_ENSURE(bytes->operator[](p + 5) == 'D'); + JXL_ENSURE(bytes->operator[](p + 6) == 'A'); + JXL_ENSURE(bytes->operator[](p + 7) == 'T'); fdata.insert(fdata.end(), bytes->data() + p + 8, bytes->data() + p + 8 + len); p += len + 12; @@ -350,7 +486,7 @@ Status APNGEncoder::EncodePackedPixelFileToAPNG( count++; if (count == ppf.frames.size() || !ppf.info.have_animation) { - png_write_end(png_ptr, NULL); + png_write_end(png_ptr, nullptr); } png_destroy_write_struct(&png_ptr, &info_ptr); @@ -360,9 +496,14 @@ Status APNGEncoder::EncodePackedPixelFileToAPNG( } } // namespace +#endif std::unique_ptr GetAPNGEncoder() { +#if JPEGXL_ENABLE_APNG return jxl::make_unique(); +#else + return nullptr; +#endif } } // namespace extras diff --git a/media/libjxl/src/lib/extras/enc/encode.cc b/media/libjxl/src/lib/extras/enc/encode.cc index dc593d2900..71be78e36c 100644 --- a/media/libjxl/src/lib/extras/enc/encode.cc +++ b/media/libjxl/src/lib/extras/enc/encode.cc @@ -7,24 +7,17 @@ #include -#if JPEGXL_ENABLE_APNG #include "lib/extras/enc/apng.h" -#endif -#if JPEGXL_ENABLE_EXR #include "lib/extras/enc/exr.h" -#endif -#if JPEGXL_ENABLE_JPEG #include "lib/extras/enc/jpg.h" -#endif #include "lib/extras/enc/npy.h" #include "lib/extras/enc/pgx.h" #include "lib/extras/enc/pnm.h" -#include "lib/jxl/base/printf_macros.h" namespace jxl { namespace extras { -Status Encoder::VerifyBasicInfo(const JxlBasicInfo& info) const { +Status Encoder::VerifyBasicInfo(const JxlBasicInfo& info) { if (info.xsize == 0 || info.ysize == 0) { return JXL_FAILURE("Empty image"); } @@ -40,8 +33,34 @@ Status Encoder::VerifyBasicInfo(const JxlBasicInfo& info) const { return true; } -Status Encoder::VerifyPackedImage(const PackedImage& image, - const JxlBasicInfo& info) const { +Status Encoder::VerifyFormat(const JxlPixelFormat& format) const { + for (auto f : AcceptedFormats()) { + if (f.num_channels != format.num_channels) continue; + if (f.data_type != format.data_type) continue; + if (f.data_type == JXL_TYPE_UINT8 || f.endianness == format.endianness) { + return true; + } + } + return JXL_FAILURE("Format is not in the list of accepted formats."); +} + +Status Encoder::VerifyBitDepth(JxlDataType data_type, uint32_t bits_per_sample, + uint32_t exponent_bits) { + if ((data_type == JXL_TYPE_UINT8 && + (bits_per_sample == 0 || bits_per_sample > 8 || exponent_bits != 0)) || + (data_type == JXL_TYPE_UINT16 && + (bits_per_sample <= 8 || bits_per_sample > 16 || exponent_bits != 0)) || + (data_type == JXL_TYPE_FLOAT16 && + (bits_per_sample > 16 || exponent_bits > 5))) { + return JXL_FAILURE( + "Incompatible data_type %d and bit depth %u with exponent bits %u", + static_cast(data_type), bits_per_sample, exponent_bits); + } + return true; +} + +Status Encoder::VerifyImageSize(const PackedImage& image, + const JxlBasicInfo& info) { if (image.pixels() == nullptr) { return JXL_FAILURE("Invalid image."); } @@ -57,80 +76,71 @@ Status Encoder::VerifyPackedImage(const PackedImage& image, image.format.num_channels != info_num_channels) { return JXL_FAILURE("Frame size does not match image size"); } - if (info.bits_per_sample > - PackedImage::BitsPerChannel(image.format.data_type)) { - return JXL_FAILURE("Bit depth does not fit pixel data type"); - } return true; } -Status SelectFormat(const std::vector& accepted_formats, - const JxlBasicInfo& basic_info, JxlPixelFormat* format) { - const size_t original_bit_depth = basic_info.bits_per_sample; - size_t current_bit_depth = 0; - size_t num_alpha_channels = (basic_info.alpha_bits != 0 ? 1 : 0); - size_t num_channels = basic_info.num_color_channels + num_alpha_channels; - for (;;) { - for (const JxlPixelFormat& candidate : accepted_formats) { - if (candidate.num_channels != num_channels) continue; - const size_t candidate_bit_depth = - PackedImage::BitsPerChannel(candidate.data_type); - if ( - // Candidate bit depth is less than what we have and still enough - (original_bit_depth <= candidate_bit_depth && - candidate_bit_depth < current_bit_depth) || - // Or larger than the too-small bit depth we currently have - (current_bit_depth < candidate_bit_depth && - current_bit_depth < original_bit_depth)) { - *format = candidate; - current_bit_depth = candidate_bit_depth; - } - } - if (current_bit_depth == 0) { - if (num_channels > basic_info.num_color_channels) { - // Try dropping the alpha channel. - --num_channels; - continue; - } - return JXL_FAILURE("no appropriate format found"); - } - break; - } - if (current_bit_depth < original_bit_depth) { - JXL_WARNING("encoding %" PRIuS "-bit original to %" PRIuS " bits", - original_bit_depth, current_bit_depth); - } +Status Encoder::VerifyPackedImage(const PackedImage& image, + const JxlBasicInfo& info) const { + JXL_RETURN_IF_ERROR(VerifyImageSize(image, info)); + JXL_RETURN_IF_ERROR(VerifyFormat(image.format)); + JXL_RETURN_IF_ERROR(VerifyBitDepth(image.format.data_type, + info.bits_per_sample, + info.exponent_bits_per_sample)); return true; } +template +class MetadataEncoder : public Encoder { + public: + std::vector AcceptedFormats() const override { + std::vector formats; + // empty, i.e. no need for actual pixel data + return formats; + } + + Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded, + ThreadPool* pool) const override { + JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); + encoded->icc.clear(); + encoded->bitstreams.resize(1); + if (metadata == 0) encoded->bitstreams.front() = ppf.metadata.exif; + if (metadata == 1) encoded->bitstreams.front() = ppf.metadata.xmp; + if (metadata == 2) encoded->bitstreams.front() = ppf.metadata.jumbf; + return true; + } +}; + std::unique_ptr Encoder::FromExtension(std::string extension) { std::transform( extension.begin(), extension.end(), extension.begin(), [](char c) { return std::tolower(c, std::locale::classic()); }); -#if JPEGXL_ENABLE_APNG if (extension == ".png" || extension == ".apng") return GetAPNGEncoder(); -#endif - -#if JPEGXL_ENABLE_JPEG if (extension == ".jpg") return GetJPEGEncoder(); if (extension == ".jpeg") return GetJPEGEncoder(); -#endif - if (extension == ".npy") return GetNumPyEncoder(); - if (extension == ".pgx") return GetPGXEncoder(); - if (extension == ".pam") return GetPAMEncoder(); if (extension == ".pgm") return GetPGMEncoder(); if (extension == ".ppm") return GetPPMEncoder(); + if (extension == ".pnm") return GetPNMEncoder(); if (extension == ".pfm") return GetPFMEncoder(); - -#if JPEGXL_ENABLE_EXR if (extension == ".exr") return GetEXREncoder(); -#endif + if (extension == ".exif") return jxl::make_unique>(); + if (extension == ".xmp") return jxl::make_unique>(); + if (extension == ".xml") return jxl::make_unique>(); + if (extension == ".jumbf") return jxl::make_unique>(); + if (extension == ".jumb") return jxl::make_unique>(); return nullptr; } +std::string ListOfEncodeCodecs() { + std::string list_of_codecs("PPM, PNM, PFM, PAM, PGX"); + if (GetAPNGEncoder()) list_of_codecs.append(", PNG, APNG"); + if (GetJPEGEncoder()) list_of_codecs.append(", JPEG"); + if (GetEXREncoder()) list_of_codecs.append(", EXR"); + return list_of_codecs; +} + } // namespace extras } // namespace jxl diff --git a/media/libjxl/src/lib/extras/enc/encode.h b/media/libjxl/src/lib/extras/enc/encode.h index 92eec50b6f..a71f3b220f 100644 --- a/media/libjxl/src/lib/extras/enc/encode.h +++ b/media/libjxl/src/lib/extras/enc/encode.h @@ -8,10 +8,17 @@ // Facade for image encoders. +#include +#include + +#include +#include #include #include +#include +#include -#include "lib/extras/dec/decode.h" +#include "lib/extras/packed_image.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/status.h" @@ -20,7 +27,7 @@ namespace extras { struct EncodedImage { // One (if the format supports animations or the image has only one frame) or - // more sequential bitstreams. + // more 1quential bitstreams. std::vector> bitstreams; // For each extra channel one or more sequential bitstreams. @@ -43,22 +50,30 @@ class Encoder { virtual ~Encoder() = default; + // Set of pixel formats that this encoder takes as input. + // If empty, the 'encoder' does not need any pixels (it's metadata-only). virtual std::vector AcceptedFormats() const = 0; // Any existing data in encoded_image is discarded. virtual Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, - ThreadPool* pool = nullptr) const = 0; + ThreadPool* pool) const = 0; void SetOption(std::string name, std::string value) { options_[std::move(name)] = std::move(value); } + static Status VerifyBasicInfo(const JxlBasicInfo& info); + static Status VerifyImageSize(const PackedImage& image, + const JxlBasicInfo& info); + static Status VerifyBitDepth(JxlDataType data_type, uint32_t bits_per_sample, + uint32_t exponent_bits); + protected: const std::unordered_map& options() const { return options_; } - Status VerifyBasicInfo(const JxlBasicInfo& info) const; + Status VerifyFormat(const JxlPixelFormat& format) const; Status VerifyPackedImage(const PackedImage& image, const JxlBasicInfo& info) const; @@ -67,9 +82,7 @@ class Encoder { std::unordered_map options_; }; -// TODO(sboukortt): consider exposing this as part of the C API. -Status SelectFormat(const std::vector& accepted_formats, - const JxlBasicInfo& basic_info, JxlPixelFormat* format); +std::string ListOfEncodeCodecs(); } // namespace extras } // namespace jxl diff --git a/media/libjxl/src/lib/extras/enc/exr.cc b/media/libjxl/src/lib/extras/enc/exr.cc index 05e05f96ce..5a4f7d768c 100644 --- a/media/libjxl/src/lib/extras/enc/exr.cc +++ b/media/libjxl/src/lib/extras/enc/exr.cc @@ -5,20 +5,23 @@ #include "lib/extras/enc/exr.h" +#if JPEGXL_ENABLE_EXR #include #include #include #include +#endif +#include #include -#include "jxl/codestream_header.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/byte_order.h" namespace jxl { namespace extras { +#if JPEGXL_ENABLE_EXR namespace { namespace OpenEXR = OPENEXR_IMF_NAMESPACE; @@ -81,7 +84,7 @@ Status EncodeImageEXR(const PackedImage& image, const JxlBasicInfo& info, const size_t xsize = info.xsize; const size_t ysize = info.ysize; const bool has_alpha = info.alpha_bits > 0; - const bool alpha_is_premultiplied = info.alpha_premultiplied; + const bool alpha_is_premultiplied = FROM_JXL_BOOL(info.alpha_premultiplied); if (info.num_color_channels != 3 || c_enc.color_space != JXL_COLOR_SPACE_RGB || @@ -110,7 +113,7 @@ Status EncodeImageEXR(const PackedImage& image, const JxlBasicInfo& info, chromaticities.white = Imath::V2f(c_enc.white_point_xy[0], c_enc.white_point_xy[1]); OpenEXR::addChromaticities(header, chromaticities); - OpenEXR::addWhiteLuminance(header, 255.0f); + OpenEXR::addWhiteLuminance(header, info.intensity_target); auto loadFloat = format.endianness == JXL_BIG_ENDIAN ? LoadBEFloat : LoadLEFloat; @@ -162,7 +165,7 @@ class EXREncoder : public Encoder { std::vector AcceptedFormats() const override { std::vector formats; for (const uint32_t num_channels : {1, 2, 3, 4}) { - for (const JxlDataType data_type : {JXL_TYPE_FLOAT, JXL_TYPE_FLOAT16}) { + for (const JxlDataType data_type : {JXL_TYPE_FLOAT}) { for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) { formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, /*data_type=*/data_type, @@ -174,7 +177,7 @@ class EXREncoder : public Encoder { return formats; } Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, - ThreadPool* pool = nullptr) const override { + ThreadPool* pool) const override { JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); encoded_image->icc.clear(); encoded_image->bitstreams.clear(); @@ -191,9 +194,14 @@ class EXREncoder : public Encoder { }; } // namespace +#endif std::unique_ptr GetEXREncoder() { +#if JPEGXL_ENABLE_EXR return jxl::make_unique(); +#else + return nullptr; +#endif } } // namespace extras diff --git a/media/libjxl/src/lib/extras/enc/jpegli.cc b/media/libjxl/src/lib/extras/enc/jpegli.cc new file mode 100644 index 0000000000..52de317399 --- /dev/null +++ b/media/libjxl/src/lib/extras/enc/jpegli.cc @@ -0,0 +1,570 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "lib/extras/enc/jpegli.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/extras/enc/encode.h" +#include "lib/extras/packed_image.h" +#include "lib/jpegli/common.h" +#include "lib/jpegli/encode.h" +#include "lib/jpegli/types.h" +#include "lib/jxl/base/byte_order.h" +#include "lib/jxl/base/common.h" +#include "lib/jxl/base/data_parallel.h" +#include "lib/jxl/base/status.h" +#include "lib/jxl/color_encoding_internal.h" +#include "lib/jxl/enc_xyb.h" +#include "lib/jxl/simd_util.h" + +namespace jxl { +namespace extras { + +namespace { + +void MyErrorExit(j_common_ptr cinfo) { + jmp_buf* env = static_cast(cinfo->client_data); + (*cinfo->err->output_message)(cinfo); + jpegli_destroy_compress(reinterpret_cast(cinfo)); + longjmp(*env, 1); +} + +Status VerifyInput(const PackedPixelFile& ppf) { + const JxlBasicInfo& info = ppf.info; + JXL_RETURN_IF_ERROR(Encoder::VerifyBasicInfo(info)); + if (ppf.frames.size() != 1) { + return JXL_FAILURE("JPEG input must have exactly one frame."); + } + if (info.num_color_channels != 1 && info.num_color_channels != 3) { + return JXL_FAILURE("Invalid number of color channels %d", + info.num_color_channels); + } + const PackedImage& image = ppf.frames[0].color; + JXL_RETURN_IF_ERROR(Encoder::VerifyImageSize(image, info)); + if (image.format.data_type == JXL_TYPE_FLOAT16) { + return JXL_FAILURE("FLOAT16 input is not supported."); + } + JXL_RETURN_IF_ERROR(Encoder::VerifyBitDepth(image.format.data_type, + info.bits_per_sample, + info.exponent_bits_per_sample)); + if ((image.format.data_type == JXL_TYPE_UINT8 && info.bits_per_sample != 8) || + (image.format.data_type == JXL_TYPE_UINT16 && + info.bits_per_sample != 16)) { + return JXL_FAILURE("Only full bit depth unsigned types are supported."); + } + return true; +} + +Status GetColorEncoding(const PackedPixelFile& ppf, + ColorEncoding* color_encoding) { + if (ppf.primary_color_representation == PackedPixelFile::kIccIsPrimary) { + IccBytes icc = ppf.icc; + JXL_RETURN_IF_ERROR( + color_encoding->SetICC(std::move(icc), JxlGetDefaultCms())); + } else { + JXL_RETURN_IF_ERROR(color_encoding->FromExternal(ppf.color_encoding)); + } + if (color_encoding->ICC().empty()) { + return JXL_FAILURE("Invalid color encoding."); + } + return true; +} + +bool HasICCProfile(const std::vector& app_data) { + size_t pos = 0; + while (pos < app_data.size()) { + if (pos + 16 > app_data.size()) return false; + uint8_t marker = app_data[pos + 1]; + size_t marker_len = (app_data[pos + 2] << 8) + app_data[pos + 3] + 2; + if (marker == 0xe2 && memcmp(&app_data[pos + 4], "ICC_PROFILE", 12) == 0) { + return true; + } + pos += marker_len; + } + return false; +} + +Status WriteAppData(j_compress_ptr cinfo, + const std::vector& app_data) { + size_t pos = 0; + while (pos < app_data.size()) { + if (pos + 4 > app_data.size()) { + return JXL_FAILURE("Incomplete APP header."); + } + uint8_t marker = app_data[pos + 1]; + size_t marker_len = (app_data[pos + 2] << 8) + app_data[pos + 3] + 2; + if (app_data[pos] != 0xff || marker < 0xe0 || marker > 0xef) { + return JXL_FAILURE("Invalid APP marker %02x %02x", app_data[pos], marker); + } + if (marker_len <= 4) { + return JXL_FAILURE("Invalid APP marker length."); + } + if (pos + marker_len > app_data.size()) { + return JXL_FAILURE("Incomplete APP data"); + } + jpegli_write_marker(cinfo, marker, &app_data[pos + 4], marker_len - 4); + pos += marker_len; + } + return true; +} + +constexpr int kICCMarker = 0xe2; +constexpr unsigned char kICCSignature[12] = { + 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00}; +constexpr uint8_t kUnknownTf = 2; +constexpr unsigned char kCICPTagSignature[4] = {0x63, 0x69, 0x63, 0x70}; +constexpr size_t kCICPTagSize = 12; + +bool FindCICPTag(const uint8_t* icc_data, size_t len, bool is_first_chunk, + size_t* cicp_offset, size_t* cicp_length, uint8_t* cicp_tag, + size_t* cicp_pos) { + if (is_first_chunk) { + // Look up the offset of the CICP tag from the first chunk of ICC data. + if (len < 132) { + return false; + } + uint32_t tag_count = LoadBE32(&icc_data[128]); + if (len < 132 + 12 * tag_count) { + return false; + } + for (uint32_t i = 0; i < tag_count; ++i) { + if (memcmp(&icc_data[132 + 12 * i], kCICPTagSignature, 4) == 0) { + *cicp_offset = LoadBE32(&icc_data[136 + 12 * i]); + *cicp_length = LoadBE32(&icc_data[140 + 12 * i]); + } + } + if (*cicp_length < kCICPTagSize) { + return false; + } + } + if (*cicp_offset < len) { + size_t n_bytes = std::min(len - *cicp_offset, kCICPTagSize - *cicp_pos); + memcpy(&cicp_tag[*cicp_pos], &icc_data[*cicp_offset], n_bytes); + *cicp_pos += n_bytes; + *cicp_offset = 0; + } else { + *cicp_offset -= len; + } + return true; +} + +uint8_t LookupCICPTransferFunctionFromAppData(const uint8_t* app_data, + size_t len) { + size_t last_index = 0; + size_t cicp_offset = 0; + size_t cicp_length = 0; + uint8_t cicp_tag[kCICPTagSize] = {}; + size_t cicp_pos = 0; + size_t pos = 0; + while (pos < len) { + const uint8_t* marker = &app_data[pos]; + if (pos + 4 > len) { + return kUnknownTf; + } + size_t marker_size = (marker[2] << 8) + marker[3] + 2; + if (pos + marker_size > len) { + return kUnknownTf; + } + if (marker_size < 18 || marker[0] != 0xff || marker[1] != kICCMarker || + memcmp(&marker[4], kICCSignature, 12) != 0) { + pos += marker_size; + continue; + } + uint8_t index = marker[16]; + uint8_t total = marker[17]; + const uint8_t* payload = marker + 18; + const size_t payload_size = marker_size - 18; + if (index != last_index + 1 || index > total) { + return kUnknownTf; + } + if (!FindCICPTag(payload, payload_size, last_index == 0, &cicp_offset, + &cicp_length, &cicp_tag[0], &cicp_pos)) { + return kUnknownTf; + } + if (cicp_pos == kCICPTagSize) { + break; + } + ++last_index; + } + if (cicp_pos >= kCICPTagSize && memcmp(cicp_tag, kCICPTagSignature, 4) == 0) { + return cicp_tag[9]; + } + return kUnknownTf; +} + +uint8_t LookupCICPTransferFunctionFromICCProfile(const uint8_t* icc_data, + size_t len) { + size_t cicp_offset = 0; + size_t cicp_length = 0; + uint8_t cicp_tag[kCICPTagSize] = {}; + size_t cicp_pos = 0; + if (!FindCICPTag(icc_data, len, true, &cicp_offset, &cicp_length, + &cicp_tag[0], &cicp_pos)) { + return kUnknownTf; + } + if (cicp_pos >= kCICPTagSize && memcmp(cicp_tag, kCICPTagSignature, 4) == 0) { + return cicp_tag[9]; + } + return kUnknownTf; +} + +JpegliDataType ConvertDataType(JxlDataType type) { + switch (type) { + case JXL_TYPE_UINT8: + return JPEGLI_TYPE_UINT8; + case JXL_TYPE_UINT16: + return JPEGLI_TYPE_UINT16; + case JXL_TYPE_FLOAT: + return JPEGLI_TYPE_FLOAT; + default: + return JPEGLI_TYPE_UINT8; + } +} + +JpegliEndianness ConvertEndianness(JxlEndianness endianness) { + switch (endianness) { + case JXL_NATIVE_ENDIAN: + return JPEGLI_NATIVE_ENDIAN; + case JXL_LITTLE_ENDIAN: + return JPEGLI_LITTLE_ENDIAN; + case JXL_BIG_ENDIAN: + return JPEGLI_BIG_ENDIAN; + default: + return JPEGLI_NATIVE_ENDIAN; + } +} + +void ToFloatRow(const uint8_t* row_in, JxlPixelFormat format, size_t xsize, + size_t c_out, float* row_out) { + bool is_little_endian = + (format.endianness == JXL_LITTLE_ENDIAN || + (format.endianness == JXL_NATIVE_ENDIAN && IsLittleEndian())); + static constexpr double kMul8 = 1.0 / 255.0; + static constexpr double kMul16 = 1.0 / 65535.0; + const size_t c_in = format.num_channels; + if (format.data_type == JXL_TYPE_UINT8) { + for (size_t x = 0; x < xsize; ++x) { + for (size_t c = 0; c < c_out; ++c) { + const size_t ix = c_in * x + c; + row_out[c_out * x + c] = row_in[ix] * kMul8; + } + } + } else if (format.data_type == JXL_TYPE_UINT16 && is_little_endian) { + for (size_t x = 0; x < xsize; ++x) { + for (size_t c = 0; c < c_out; ++c) { + const size_t ix = c_in * x + c; + row_out[c_out * x + c] = LoadLE16(&row_in[2 * ix]) * kMul16; + } + } + } else if (format.data_type == JXL_TYPE_UINT16 && !is_little_endian) { + for (size_t x = 0; x < xsize; ++x) { + for (size_t c = 0; c < c_out; ++c) { + const size_t ix = c_in * x + c; + row_out[c_out * x + c] = LoadBE16(&row_in[2 * ix]) * kMul16; + } + } + } else if (format.data_type == JXL_TYPE_FLOAT && is_little_endian) { + for (size_t x = 0; x < xsize; ++x) { + for (size_t c = 0; c < c_out; ++c) { + const size_t ix = c_in * x + c; + row_out[c_out * x + c] = LoadLEFloat(&row_in[4 * ix]); + } + } + } else if (format.data_type == JXL_TYPE_FLOAT && !is_little_endian) { + for (size_t x = 0; x < xsize; ++x) { + for (size_t c = 0; c < c_out; ++c) { + const size_t ix = c_in * x + c; + row_out[c_out * x + c] = LoadBEFloat(&row_in[4 * ix]); + } + } + } +} + +Status EncodeJpegToTargetSize(const PackedPixelFile& ppf, + const JpegSettings& jpeg_settings, + size_t target_size, ThreadPool* pool, + std::vector* output) { + output->clear(); + size_t best_error = std::numeric_limits::max(); + float distance0 = -1.0f; + float distance1 = -1.0f; + float distance = 1.0f; + for (int step = 0; step < 15; ++step) { + JpegSettings settings = jpeg_settings; + settings.libjpeg_quality = 0; + settings.distance = distance; + settings.target_size = 0; + std::vector compressed; + JXL_RETURN_IF_ERROR(EncodeJpeg(ppf, settings, pool, &compressed)); + size_t size = compressed.size(); + // prefer being under the target size to being over it + size_t error = size < target_size + ? target_size - size + : static_cast(1.2f * (size - target_size)); + if (error < best_error) { + best_error = error; + std::swap(*output, compressed); + } + float rel_error = size * 1.0f / target_size; + if (std::abs(rel_error - 1.0f) < 0.002f) { + break; + } + if (size < target_size) { + distance1 = distance; + } else { + distance0 = distance; + } + if (distance1 == -1) { + distance *= std::pow(rel_error, 1.5) * 1.05; + } else if (distance0 == -1) { + distance *= std::pow(rel_error, 1.5) * 0.95; + } else { + distance = 0.5 * (distance0 + distance1); + } + } + return true; +} + +} // namespace + +Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings, + ThreadPool* pool, std::vector* compressed) { + if (jpeg_settings.libjpeg_quality > 0) { + auto encoder = Encoder::FromExtension(".jpg"); + encoder->SetOption("q", std::to_string(jpeg_settings.libjpeg_quality)); + if (!jpeg_settings.libjpeg_chroma_subsampling.empty()) { + encoder->SetOption("chroma_subsampling", + jpeg_settings.libjpeg_chroma_subsampling); + } + EncodedImage encoded; + JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded, pool)); + size_t target_size = encoded.bitstreams[0].size(); + return EncodeJpegToTargetSize(ppf, jpeg_settings, target_size, pool, + compressed); + } + if (jpeg_settings.target_size > 0) { + return EncodeJpegToTargetSize(ppf, jpeg_settings, jpeg_settings.target_size, + pool, compressed); + } + JXL_RETURN_IF_ERROR(VerifyInput(ppf)); + + ColorEncoding color_encoding; + JXL_RETURN_IF_ERROR(GetColorEncoding(ppf, &color_encoding)); + + ColorSpaceTransform c_transform(*JxlGetDefaultCms()); + ColorEncoding xyb_encoding; + if (jpeg_settings.xyb) { + if (HasICCProfile(jpeg_settings.app_data)) { + return JXL_FAILURE("APP data ICC profile is not supported in XYB mode."); + } + const ColorEncoding& c_desired = ColorEncoding::LinearSRGB(false); + JXL_RETURN_IF_ERROR( + c_transform.Init(color_encoding, c_desired, 255.0f, ppf.info.xsize, 1)); + xyb_encoding.SetColorSpace(jxl::ColorSpace::kXYB); + xyb_encoding.SetRenderingIntent(jxl::RenderingIntent::kPerceptual); + JXL_RETURN_IF_ERROR(xyb_encoding.CreateICC()); + } + const ColorEncoding& output_encoding = + jpeg_settings.xyb ? xyb_encoding : color_encoding; + + // We need to declare all the non-trivial destructor local variables + // before the call to setjmp(). + std::vector pixels; + unsigned char* output_buffer = nullptr; + unsigned long output_size = 0; // NOLINT + std::vector row_bytes; + const size_t max_vector_size = MaxVectorSize(); + size_t rowlen = RoundUpTo(ppf.info.xsize, max_vector_size); + hwy::AlignedFreeUniquePtr xyb_tmp = + hwy::AllocateAligned(6 * rowlen); + hwy::AlignedFreeUniquePtr premul_absorb = + hwy::AllocateAligned(max_vector_size * 12); + ComputePremulAbsorb(255.0f, premul_absorb.get()); + + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + jpeg_error_mgr jerr; + jmp_buf env; + cinfo.err = jpegli_std_error(&jerr); + jerr.error_exit = &MyErrorExit; + if (setjmp(env)) { + return false; + } + cinfo.client_data = static_cast(&env); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &output_buffer, &output_size); + const JxlBasicInfo& info = ppf.info; + cinfo.image_width = info.xsize; + cinfo.image_height = info.ysize; + cinfo.input_components = info.num_color_channels; + cinfo.in_color_space = + cinfo.input_components == 1 ? JCS_GRAYSCALE : JCS_RGB; + if (jpeg_settings.xyb) { + jpegli_set_xyb_mode(&cinfo); + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + } else if (jpeg_settings.use_std_quant_tables) { + jpegli_use_standard_quant_tables(&cinfo); + } + uint8_t cicp_tf = kUnknownTf; + if (!jpeg_settings.app_data.empty()) { + cicp_tf = LookupCICPTransferFunctionFromAppData( + jpeg_settings.app_data.data(), jpeg_settings.app_data.size()); + } else if (!output_encoding.IsSRGB()) { + cicp_tf = LookupCICPTransferFunctionFromICCProfile( + output_encoding.ICC().data(), output_encoding.ICC().size()); + } + jpegli_set_cicp_transfer_function(&cinfo, cicp_tf); + jpegli_set_defaults(&cinfo); + if (!jpeg_settings.chroma_subsampling.empty()) { + if (jpeg_settings.chroma_subsampling == "444") { + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 1; + } else if (jpeg_settings.chroma_subsampling == "440") { + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 2; + } else if (jpeg_settings.chroma_subsampling == "422") { + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 1; + } else if (jpeg_settings.chroma_subsampling == "420") { + cinfo.comp_info[0].h_samp_factor = 2; + cinfo.comp_info[0].v_samp_factor = 2; + } else { + return false; + } + for (int i = 1; i < cinfo.num_components; ++i) { + cinfo.comp_info[i].h_samp_factor = 1; + cinfo.comp_info[i].v_samp_factor = 1; + } + } else if (!jpeg_settings.xyb) { + // Default is no chroma subsampling. + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 1; + } + jpegli_enable_adaptive_quantization( + &cinfo, TO_JXL_BOOL(jpeg_settings.use_adaptive_quantization)); + if (jpeg_settings.psnr_target > 0.0) { + jpegli_set_psnr(&cinfo, jpeg_settings.psnr_target, + jpeg_settings.search_tolerance, + jpeg_settings.min_distance, jpeg_settings.max_distance); + } else if (jpeg_settings.quality > 0.0) { + float distance = jpegli_quality_to_distance(jpeg_settings.quality); + jpegli_set_distance(&cinfo, distance, TRUE); + } else { + jpegli_set_distance(&cinfo, jpeg_settings.distance, TRUE); + } + jpegli_set_progressive_level(&cinfo, jpeg_settings.progressive_level); + cinfo.optimize_coding = TO_JXL_BOOL(jpeg_settings.optimize_coding); + if (!jpeg_settings.app_data.empty()) { + // Make sure jpegli_start_compress() does not write any APP markers. + cinfo.write_JFIF_header = JXL_FALSE; + cinfo.write_Adobe_marker = JXL_FALSE; + } + const PackedImage& image = ppf.frames[0].color; + if (jpeg_settings.xyb) { + jpegli_set_input_format(&cinfo, JPEGLI_TYPE_FLOAT, JPEGLI_NATIVE_ENDIAN); + } else { + jpegli_set_input_format(&cinfo, ConvertDataType(image.format.data_type), + ConvertEndianness(image.format.endianness)); + } + jpegli_start_compress(&cinfo, TRUE); + if (!jpeg_settings.app_data.empty()) { + JXL_RETURN_IF_ERROR(WriteAppData(&cinfo, jpeg_settings.app_data)); + } + if ((jpeg_settings.app_data.empty() && !output_encoding.IsSRGB()) || + jpeg_settings.xyb) { + jpegli_write_icc_profile(&cinfo, output_encoding.ICC().data(), + output_encoding.ICC().size()); + } + const uint8_t* pixels = reinterpret_cast(image.pixels()); + if (jpeg_settings.xyb) { + float* src_buf = c_transform.BufSrc(0); + float* dst_buf = c_transform.BufDst(0); + for (size_t y = 0; y < image.ysize; ++y) { + // convert to float + ToFloatRow(&pixels[y * image.stride], image.format, image.xsize, + info.num_color_channels, src_buf); + // convert to linear srgb + if (!c_transform.Run(0, src_buf, dst_buf, image.xsize)) { + return false; + } + // deinterleave channels + float* row0 = &xyb_tmp[0]; + float* row1 = &xyb_tmp[rowlen]; + float* row2 = &xyb_tmp[2 * rowlen]; + for (size_t x = 0; x < image.xsize; ++x) { + row0[x] = dst_buf[3 * x + 0]; + row1[x] = dst_buf[3 * x + 1]; + row2[x] = dst_buf[3 * x + 2]; + } + // convert to xyb + LinearRGBRowToXYB(row0, row1, row2, premul_absorb.get(), image.xsize); + // scale xyb + ScaleXYBRow(row0, row1, row2, image.xsize); + // interleave channels + float* row_out = &xyb_tmp[3 * rowlen]; + for (size_t x = 0; x < image.xsize; ++x) { + row_out[3 * x + 0] = row0[x]; + row_out[3 * x + 1] = row1[x]; + row_out[3 * x + 2] = row2[x]; + } + // feed to jpegli as native endian floats + JSAMPROW row[] = {reinterpret_cast(row_out)}; + jpegli_write_scanlines(&cinfo, row, 1); + } + } else { + row_bytes.resize(image.stride); + if (cinfo.num_components == static_cast(image.format.num_channels)) { + for (size_t y = 0; y < info.ysize; ++y) { + memcpy(row_bytes.data(), pixels + y * image.stride, image.stride); + JSAMPROW row[] = {row_bytes.data()}; + jpegli_write_scanlines(&cinfo, row, 1); + } + } else { + for (size_t y = 0; y < info.ysize; ++y) { + JXL_RETURN_IF_ERROR( + PackedImage::ValidateDataType(image.format.data_type)); + int bytes_per_channel = + PackedImage::BitsPerChannel(image.format.data_type) / 8; + int bytes_per_pixel = cinfo.num_components * bytes_per_channel; + for (size_t x = 0; x < info.xsize; ++x) { + memcpy(&row_bytes[x * bytes_per_pixel], + &pixels[y * image.stride + x * image.pixel_stride()], + bytes_per_pixel); + } + JSAMPROW row[] = {row_bytes.data()}; + jpegli_write_scanlines(&cinfo, row, 1); + } + } + } + jpegli_finish_compress(&cinfo); + compressed->resize(output_size); + std::copy_n(output_buffer, output_size, compressed->data()); + return true; + }; + bool success = try_catch_block(); + jpegli_destroy_compress(&cinfo); + if (output_buffer) free(output_buffer); + return success; +} + +} // namespace extras +} // namespace jxl diff --git a/media/libjxl/src/lib/extras/enc/jpegli.h b/media/libjxl/src/lib/extras/enc/jpegli.h new file mode 100644 index 0000000000..9538b2e3fc --- /dev/null +++ b/media/libjxl/src/lib/extras/enc/jpegli.h @@ -0,0 +1,53 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_EXTRAS_ENC_JPEGLI_H_ +#define LIB_EXTRAS_ENC_JPEGLI_H_ + +// Encodes JPG pixels and metadata in memory using the libjpegli library. + +#include + +#include +#include + +#include "lib/extras/packed_image.h" +#include "lib/jxl/base/data_parallel.h" +#include "lib/jxl/base/status.h" + +namespace jxl { +namespace extras { + +struct JpegSettings { + bool xyb = false; + size_t target_size = 0; + float quality = 0.0f; + float distance = 1.f; + bool use_adaptive_quantization = true; + bool use_std_quant_tables = false; + int progressive_level = 2; + bool optimize_coding = true; + std::string chroma_subsampling; + int libjpeg_quality = 0; + std::string libjpeg_chroma_subsampling; + // Parameters for selecting distance based on PSNR target. + float psnr_target = 0.0f; + float search_tolerance = 0.01; + float min_distance = 0.1f; + float max_distance = 25.0f; + // If not empty, must contain concatenated APP marker segments. In this case, + // these and only these APP marker segments will be written to the JPEG + // output. In xyb mode app_data must not contain an ICC profile, in this + // case an additional APP2 ICC profile for the XYB colorspace will be emitted. + std::vector app_data; +}; + +Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings, + ThreadPool* pool, std::vector* compressed); + +} // namespace extras +} // namespace jxl + +#endif // LIB_EXTRAS_ENC_JPEGLI_H_ diff --git a/media/libjxl/src/lib/extras/enc/jpg.cc b/media/libjxl/src/lib/extras/enc/jpg.cc index 93a39dd2e4..a9209ffce0 100644 --- a/media/libjxl/src/lib/extras/enc/jpg.cc +++ b/media/libjxl/src/lib/extras/enc/jpg.cc @@ -5,27 +5,33 @@ #include "lib/extras/enc/jpg.h" -#include -#include -#include +#if JPEGXL_ENABLE_JPEG +#include "lib/jxl/base/include_jpeglib.h" // NOLINT +#endif #include -#include -#include +#include +#include +#include +#include +#include #include #include #include #include "lib/extras/exif.h" +#include "lib/jxl/base/common.h" +#include "lib/jxl/base/sanitizers.h" #include "lib/jxl/base/status.h" -#include "lib/jxl/sanitizers.h" #if JPEGXL_ENABLE_SJPEG #include "sjpeg.h" +#include "sjpegi.h" #endif namespace jxl { namespace extras { +#if JPEGXL_ENABLE_JPEG namespace { constexpr unsigned char kICCSignature[12] = { @@ -42,12 +48,138 @@ enum class JpegEncoder { kSJpeg, }; -bool IsSRGBEncoding(const JxlColorEncoding& c) { - return ((c.color_space == JXL_COLOR_SPACE_RGB || - c.color_space == JXL_COLOR_SPACE_GRAY) && - c.primaries == JXL_PRIMARIES_SRGB && - c.white_point == JXL_WHITE_POINT_D65 && - c.transfer_function == JXL_TRANSFER_FUNCTION_SRGB); +// Popular jpeg scan scripts +// The fields of the individual scans are: +// comps_in_scan, component_index[], Ss, Se, Ah, Al +constexpr auto kScanScript1 = to_array({ + {1, {0}, 0, 0, 0, 0}, // + {1, {1}, 0, 0, 0, 0}, // + {1, {2}, 0, 0, 0, 0}, // + {1, {0}, 1, 8, 0, 0}, // + {1, {0}, 9, 63, 0, 0}, // + {1, {1}, 1, 63, 0, 0}, // + {1, {2}, 1, 63, 0, 0} // +}); +constexpr size_t kNumScans1 = kScanScript1.size(); + +constexpr auto kScanScript2 = to_array({ + {1, {0}, 0, 0, 0, 0}, // + {1, {1}, 0, 0, 0, 0}, // + {1, {2}, 0, 0, 0, 0}, // + {1, {0}, 1, 2, 0, 1}, // + {1, {0}, 3, 63, 0, 1}, // + {1, {0}, 1, 63, 1, 0}, // + {1, {1}, 1, 63, 0, 0}, // + {1, {2}, 1, 63, 0, 0} // +}); +constexpr size_t kNumScans2 = kScanScript2.size(); + +constexpr auto kScanScript3 = to_array({ + {1, {0}, 0, 0, 0, 0}, // + {1, {1}, 0, 0, 0, 0}, // + {1, {2}, 0, 0, 0, 0}, // + {1, {0}, 1, 63, 0, 2}, // + {1, {0}, 1, 63, 2, 1}, // + {1, {0}, 1, 63, 1, 0}, // + {1, {1}, 1, 63, 0, 0}, // + {1, {2}, 1, 63, 0, 0} // +}); +constexpr size_t kNumScans3 = kScanScript3.size(); + +constexpr auto kScanScript4 = to_array({ + {3, {0, 1, 2}, 0, 0, 0, 1}, // + {1, {0}, 1, 5, 0, 2}, // + {1, {2}, 1, 63, 0, 1}, // + {1, {1}, 1, 63, 0, 1}, // + {1, {0}, 6, 63, 0, 2}, // + {1, {0}, 1, 63, 2, 1}, // + {3, {0, 1, 2}, 0, 0, 1, 0}, // + {1, {2}, 1, 63, 1, 0}, // + {1, {1}, 1, 63, 1, 0}, // + {1, {0}, 1, 63, 1, 0} // +}); +constexpr size_t kNumScans4 = kScanScript4.size(); + +constexpr auto kScanScript5 = to_array({ + {3, {0, 1, 2}, 0, 0, 0, 1}, // + {1, {0}, 1, 5, 0, 2}, // + {1, {1}, 1, 5, 0, 2}, // + {1, {2}, 1, 5, 0, 2}, // + {1, {1}, 6, 63, 0, 2}, // + {1, {2}, 6, 63, 0, 2}, // + {1, {0}, 6, 63, 0, 2}, // + {1, {0}, 1, 63, 2, 1}, // + {1, {1}, 1, 63, 2, 1}, // + {1, {2}, 1, 63, 2, 1}, // + {3, {0, 1, 2}, 0, 0, 1, 0}, // + {1, {0}, 1, 63, 1, 0}, // + {1, {1}, 1, 63, 1, 0}, // + {1, {2}, 1, 63, 1, 0} // +}); +constexpr size_t kNumScans5 = kScanScript5.size(); + +// default progressive mode of jpegli +constexpr auto kScanScript6 = to_array({ + {3, {0, 1, 2}, 0, 0, 0, 0}, // + {1, {0}, 1, 2, 0, 0}, // + {1, {1}, 1, 2, 0, 0}, // + {1, {2}, 1, 2, 0, 0}, // + {1, {0}, 3, 63, 0, 2}, // + {1, {1}, 3, 63, 0, 2}, // + {1, {2}, 3, 63, 0, 2}, // + {1, {0}, 3, 63, 2, 1}, // + {1, {1}, 3, 63, 2, 1}, // + {1, {2}, 3, 63, 2, 1}, // + {1, {0}, 3, 63, 1, 0}, // + {1, {1}, 3, 63, 1, 0}, // + {1, {2}, 3, 63, 1, 0}, // +}); +constexpr size_t kNumScans6 = kScanScript6.size(); + +// Adapt RGB scan info to grayscale jpegs. +void FilterScanComponents(const jpeg_compress_struct* cinfo, + jpeg_scan_info* si) { + const int all_comps_in_scan = si->comps_in_scan; + si->comps_in_scan = 0; + for (int j = 0; j < all_comps_in_scan; ++j) { + const int component = si->component_index[j]; + if (component < cinfo->input_components) { + si->component_index[si->comps_in_scan++] = component; + } + } +} + +Status SetJpegProgression(int progressive_id, + std::vector* scan_infos, + jpeg_compress_struct* cinfo) { + if (progressive_id < 0) { + return true; + } + if (progressive_id == 0) { + jpeg_simple_progression(cinfo); + return true; + } + const jpeg_scan_info* kScanScripts[] = { + kScanScript1.data(), kScanScript2.data(), kScanScript3.data(), + kScanScript4.data(), kScanScript5.data(), kScanScript6.data()}; + constexpr auto kNumScans = to_array( + {kNumScans1, kNumScans2, kNumScans3, kNumScans4, kNumScans5, kNumScans6}); + if (progressive_id > static_cast(kNumScans.size())) { + return JXL_FAILURE("Unknown jpeg scan script id %d", progressive_id); + } + const jpeg_scan_info* scan_script = kScanScripts[progressive_id - 1]; + const size_t num_scans = kNumScans[progressive_id - 1]; + // filter scan script for number of components + for (size_t i = 0; i < num_scans; ++i) { + jpeg_scan_info scan_info = scan_script[i]; + FilterScanComponents(cinfo, &scan_info); + if (scan_info.comps_in_scan > 0) { + scan_infos->emplace_back(scan_info); + } + } + cinfo->scan_info = scan_infos->data(); + cinfo->num_scans = scan_infos->size(); + return true; } void WriteICCProfile(jpeg_compress_struct* const cinfo, @@ -81,8 +213,8 @@ void WriteExif(jpeg_compress_struct* const cinfo, for (const unsigned char c : kExifSignature) { jpeg_write_m_byte(cinfo, c); } - for (size_t i = 0; i < exif.size(); ++i) { - jpeg_write_m_byte(cinfo, exif[i]); + for (uint8_t c : exif) { + jpeg_write_m_byte(cinfo, c); } } @@ -106,34 +238,65 @@ Status SetChromaSubsampling(const std::string& subsampling, return false; } +struct JpegParams { + // Common between sjpeg and libjpeg + int quality = 100; + std::string chroma_subsampling = "444"; + // Libjpeg parameters + int progressive_id = -1; + bool optimize_coding = true; + bool is_xyb = false; + // Sjpeg parameters + int libjpeg_quality = 0; + std::string libjpeg_chroma_subsampling = "444"; + float psnr_target = 0; + std::string custom_base_quant_fn; + float search_q_start = 65.0f; + float search_q_min = 1.0f; + float search_q_max = 100.0f; + int search_max_iters = 20; + float search_tolerance = 0.1f; + float search_q_precision = 0.01f; + float search_first_iter_slope = 3.0f; + bool enable_adaptive_quant = true; +}; + Status EncodeWithLibJpeg(const PackedImage& image, const JxlBasicInfo& info, const std::vector& icc, - std::vector exif, size_t quality, - const std::string& chroma_subsampling, + std::vector exif, const JpegParams& params, std::vector* bytes) { if (BITS_IN_JSAMPLE != 8 || sizeof(JSAMPLE) != 1) { return JXL_FAILURE("Only 8 bit JSAMPLE is supported."); } - jpeg_compress_struct cinfo; - // cinfo is initialized by libjpeg, which we are not instrumenting with - // msan. - msan::UnpoisonMemory(&cinfo, sizeof(cinfo)); + jpeg_compress_struct cinfo = {}; jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); unsigned char* buffer = nullptr; - unsigned long size = 0; +#ifdef LIBJPEG_TURBO_VERSION + unsigned long size = 0; // NOLINT +#else + size_t size = 0; // NOLINT +#endif jpeg_mem_dest(&cinfo, &buffer, &size); cinfo.image_width = image.xsize; cinfo.image_height = image.ysize; cinfo.input_components = info.num_color_channels; cinfo.in_color_space = info.num_color_channels == 1 ? JCS_GRAYSCALE : JCS_RGB; jpeg_set_defaults(&cinfo); - cinfo.optimize_coding = TRUE; + cinfo.optimize_coding = static_cast(params.optimize_coding); if (cinfo.input_components == 3) { - JXL_RETURN_IF_ERROR(SetChromaSubsampling(chroma_subsampling, &cinfo)); + JXL_RETURN_IF_ERROR( + SetChromaSubsampling(params.chroma_subsampling, &cinfo)); } - jpeg_set_quality(&cinfo, quality, TRUE); + if (params.is_xyb) { + // Tell libjpeg not to convert XYB data to YCbCr. + jpeg_set_colorspace(&cinfo, JCS_RGB); + } + jpeg_set_quality(&cinfo, params.quality, TRUE); + std::vector scan_infos; + JXL_RETURN_IF_ERROR( + SetJpegProgression(params.progressive_id, &scan_infos, &cinfo)); jpeg_start_compress(&cinfo, TRUE); if (!icc.empty()) { WriteICCProfile(&cinfo, icc); @@ -145,13 +308,39 @@ Status EncodeWithLibJpeg(const PackedImage& image, const JxlBasicInfo& info, if (cinfo.input_components > 3 || cinfo.input_components < 0) return JXL_FAILURE("invalid numbers of components"); - std::vector raw_bytes(image.pixels_size); - memcpy(&raw_bytes[0], reinterpret_cast(image.pixels()), - image.pixels_size); - for (size_t y = 0; y < info.ysize; ++y) { - JSAMPROW row[] = {raw_bytes.data() + y * image.stride}; - - jpeg_write_scanlines(&cinfo, row, 1); + std::vector row_bytes(image.stride); + const uint8_t* pixels = reinterpret_cast(image.pixels()); + if (cinfo.num_components == static_cast(image.format.num_channels) && + image.format.data_type == JXL_TYPE_UINT8) { + for (size_t y = 0; y < info.ysize; ++y) { + memcpy(row_bytes.data(), pixels + y * image.stride, image.stride); + JSAMPROW row[] = {row_bytes.data()}; + jpeg_write_scanlines(&cinfo, row, 1); + } + } else if (image.format.data_type == JXL_TYPE_UINT8) { + for (size_t y = 0; y < info.ysize; ++y) { + const uint8_t* image_row = pixels + y * image.stride; + for (size_t x = 0; x < info.xsize; ++x) { + const uint8_t* image_pixel = image_row + x * image.pixel_stride(); + memcpy(&row_bytes[x * cinfo.num_components], image_pixel, + cinfo.num_components); + } + JSAMPROW row[] = {row_bytes.data()}; + jpeg_write_scanlines(&cinfo, row, 1); + } + } else { + for (size_t y = 0; y < info.ysize; ++y) { + const uint8_t* image_row = pixels + y * image.stride; + for (size_t x = 0; x < info.xsize; ++x) { + const uint8_t* image_pixel = image_row + x * image.pixel_stride(); + for (int c = 0; c < cinfo.num_components; ++c) { + uint32_t val16 = (image_pixel[2 * c] << 8) + image_pixel[2 * c + 1]; + row_bytes[x * cinfo.num_components + c] = (val16 + 128) / 257; + } + } + JSAMPROW row[] = {row_bytes.data()}; + jpeg_write_scanlines(&cinfo, row, 1); + } } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); @@ -164,15 +353,93 @@ Status EncodeWithLibJpeg(const PackedImage& image, const JxlBasicInfo& info, return true; } +#if JPEGXL_ENABLE_SJPEG +struct MySearchHook : public sjpeg::SearchHook { + uint8_t base_tables[2][64]; + float q_start; + float q_precision; + float first_iter_slope; + void ReadBaseTables(const std::string& fn) { + const uint8_t kJPEGAnnexKMatrices[2][64] = { + {16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113, 92, + 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99}, + {17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99}}; + memcpy(base_tables[0], kJPEGAnnexKMatrices[0], sizeof(base_tables[0])); + memcpy(base_tables[1], kJPEGAnnexKMatrices[1], sizeof(base_tables[1])); + if (!fn.empty()) { + std::ifstream f(fn); + std::string line; + int idx = 0; + while (idx < 128 && std::getline(f, line)) { + if (line.empty() || line[0] == '#') continue; + std::istringstream line_stream(line); + std::string token; + while (idx < 128 && std::getline(line_stream, token, ',')) { + uint8_t val = std::stoi(token); + base_tables[idx / 64][idx % 64] = val; + idx++; + } + } + } + } + bool Setup(const sjpeg::EncoderParam& param) override { + sjpeg::SearchHook::Setup(param); + q = q_start; + return true; + } + void NextMatrix(int idx, uint8_t dst[64]) override { + float factor = (q <= 0) ? 5000.0f + : (q < 50.0f) ? 5000.0f / q + : (q < 100.0f) ? 2 * (100.0f - q) + : 0.0f; + sjpeg::SetQuantMatrix(base_tables[idx], factor, dst); + } + bool Update(float result) override { + value = result; + if (std::fabs(value - target) < tolerance * target) { + return true; + } + if (value > target) { + qmax = q; + } else { + qmin = q; + } + if (qmin == qmax) { + return true; + } + const float last_q = q; + if (pass == 0) { + q += first_iter_slope * + (for_size ? 0.1 * std::log(target / value) : (target - value)); + q = std::max(qmin, std::min(qmax, q)); + } else { + q = (qmin + qmax) / 2.; + } + return (pass > 0 && std::fabs(q - last_q) < q_precision); + } + ~MySearchHook() override = default; +}; +#endif + Status EncodeWithSJpeg(const PackedImage& image, const JxlBasicInfo& info, const std::vector& icc, - std::vector exif, size_t quality, - const std::string& chroma_subsampling, + std::vector exif, const JpegParams& params, std::vector* bytes) { #if !JPEGXL_ENABLE_SJPEG return JXL_FAILURE("JPEG XL was built without sjpeg support"); #else - sjpeg::EncoderParam param(quality); + if (image.format.data_type != JXL_TYPE_UINT8) { + return JXL_FAILURE("Unsupported pixel data type"); + } + if (info.alpha_bits > 0) { + return JXL_FAILURE("alpha is not supported"); + } + sjpeg::EncoderParam param(params.quality); if (!icc.empty()) { param.iccp.assign(icc.begin(), icc.end()); } @@ -180,13 +447,43 @@ Status EncodeWithSJpeg(const PackedImage& image, const JxlBasicInfo& info, ResetExifOrientation(exif); param.exif.assign(exif.begin(), exif.end()); } - if (chroma_subsampling == "444") { + if (params.chroma_subsampling == "444") { param.yuv_mode = SJPEG_YUV_444; - } else if (chroma_subsampling == "420") { + } else if (params.chroma_subsampling == "420") { + param.yuv_mode = SJPEG_YUV_420; + } else if (params.chroma_subsampling == "420sharp") { param.yuv_mode = SJPEG_YUV_SHARP; } else { return JXL_FAILURE("sjpeg does not support this chroma subsampling mode"); } + param.adaptive_quantization = params.enable_adaptive_quant; + std::unique_ptr hook; + if (params.libjpeg_quality > 0) { + JpegParams libjpeg_params; + libjpeg_params.quality = params.libjpeg_quality; + libjpeg_params.chroma_subsampling = params.libjpeg_chroma_subsampling; + std::vector libjpeg_bytes; + JXL_RETURN_IF_ERROR(EncodeWithLibJpeg(image, info, icc, exif, + libjpeg_params, &libjpeg_bytes)); + param.target_mode = sjpeg::EncoderParam::TARGET_SIZE; + param.target_value = libjpeg_bytes.size(); + } + if (params.psnr_target > 0) { + param.target_mode = sjpeg::EncoderParam::TARGET_PSNR; + param.target_value = params.psnr_target; + } + if (param.target_mode != sjpeg::EncoderParam::TARGET_NONE) { + param.passes = params.search_max_iters; + param.tolerance = params.search_tolerance; + param.qmin = params.search_q_min; + param.qmax = params.search_q_max; + hook = jxl::make_unique(); + hook->ReadBaseTables(params.custom_base_quant_fn); + hook->q_start = params.search_q_start; + hook->q_precision = params.search_q_precision; + hook->first_iter_slope = params.search_first_iter_slope; + param.search_hook = hook.get(); + } size_t stride = info.xsize * 3; const uint8_t* pixels = reinterpret_cast(image.pixels()); std::string output; @@ -202,27 +499,20 @@ Status EncodeWithSJpeg(const PackedImage& image, const JxlBasicInfo& info, Status EncodeImageJPG(const PackedImage& image, const JxlBasicInfo& info, const std::vector& icc, std::vector exif, JpegEncoder encoder, - size_t quality, const std::string& chroma_subsampling, - ThreadPool* pool, std::vector* bytes) { - if (image.format.data_type != JXL_TYPE_UINT8) { - return JXL_FAILURE("Unsupported pixel data type"); - } - if (info.alpha_bits > 0) { - return JXL_FAILURE("alpha is not supported"); - } - if (quality > 100) { + const JpegParams& params, ThreadPool* pool, + std::vector* bytes) { + if (params.quality > 100) { return JXL_FAILURE("please specify a 0-100 JPEG quality"); } switch (encoder) { case JpegEncoder::kLibJpeg: - JXL_RETURN_IF_ERROR(EncodeWithLibJpeg(image, info, icc, std::move(exif), - quality, chroma_subsampling, - bytes)); + JXL_RETURN_IF_ERROR( + EncodeWithLibJpeg(image, info, icc, std::move(exif), params, bytes)); break; case JpegEncoder::kSJpeg: - JXL_RETURN_IF_ERROR(EncodeWithSJpeg(image, info, icc, std::move(exif), - quality, chroma_subsampling, bytes)); + JXL_RETURN_IF_ERROR( + EncodeWithSJpeg(image, info, icc, std::move(exif), params, bytes)); break; default: return JXL_FAILURE("tried to use an unknown JPEG encoder"); @@ -234,64 +524,94 @@ Status EncodeImageJPG(const PackedImage& image, const JxlBasicInfo& info, class JPEGEncoder : public Encoder { std::vector AcceptedFormats() const override { std::vector formats; - for (const uint32_t num_channels : {1, 3}) { + for (const uint32_t num_channels : {1, 2, 3, 4}) { for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) { formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, /*data_type=*/JXL_TYPE_UINT8, /*endianness=*/endianness, /*align=*/0}); } + formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, + /*data_type=*/JXL_TYPE_UINT16, + /*endianness=*/JXL_BIG_ENDIAN, + /*align=*/0}); } return formats; } Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, - ThreadPool* pool = nullptr) const override { + ThreadPool* pool) const override { JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); - const auto& options = this->options(); - int quality = 100; - auto it_quality = options.find("q"); - if (it_quality != options.end()) { - std::istringstream is(it_quality->second); - JXL_RETURN_IF_ERROR(static_cast(is >> quality)); - } - std::string chroma_subsampling = "444"; - auto it_chroma_subsampling = options.find("chroma_subsampling"); - if (it_chroma_subsampling != options.end()) { - chroma_subsampling = it_chroma_subsampling->second; - } JpegEncoder jpeg_encoder = JpegEncoder::kLibJpeg; - auto it_encoder = options.find("jpeg_encoder"); - if (it_encoder != options.end()) { - if (it_encoder->second == "libjpeg") { - jpeg_encoder = JpegEncoder::kLibJpeg; - } else if (it_encoder->second == "sjpeg") { - jpeg_encoder = JpegEncoder::kSJpeg; - } else { - return JXL_FAILURE("unknown jpeg encoder \"%s\"", - it_encoder->second.c_str()); + JpegParams params; + for (const auto& it : options()) { + if (it.first == "q") { + std::istringstream is(it.second); + JXL_RETURN_IF_ERROR(static_cast(is >> params.quality)); + } else if (it.first == "libjpeg_quality") { + std::istringstream is(it.second); + JXL_RETURN_IF_ERROR(static_cast(is >> params.libjpeg_quality)); + } else if (it.first == "chroma_subsampling") { + params.chroma_subsampling = it.second; + } else if (it.first == "libjpeg_chroma_subsampling") { + params.libjpeg_chroma_subsampling = it.second; + } else if (it.first == "jpeg_encoder") { + if (it.second == "libjpeg") { + jpeg_encoder = JpegEncoder::kLibJpeg; + } else if (it.second == "sjpeg") { + jpeg_encoder = JpegEncoder::kSJpeg; + } else { + return JXL_FAILURE("unknown jpeg encoder \"%s\"", it.second.c_str()); + } + } else if (it.first == "progressive") { + std::istringstream is(it.second); + JXL_RETURN_IF_ERROR(static_cast(is >> params.progressive_id)); + } else if (it.first == "optimize" && it.second == "OFF") { + params.optimize_coding = false; + } else if (it.first == "adaptive_q" && it.second == "OFF") { + params.enable_adaptive_quant = false; + } else if (it.first == "psnr") { + params.psnr_target = std::stof(it.second); + } else if (it.first == "base_quant_fn") { + params.custom_base_quant_fn = it.second; + } else if (it.first == "search_q_start") { + params.search_q_start = std::stof(it.second); + } else if (it.first == "search_q_min") { + params.search_q_min = std::stof(it.second); + } else if (it.first == "search_q_max") { + params.search_q_max = std::stof(it.second); + } else if (it.first == "search_max_iters") { + params.search_max_iters = std::stoi(it.second); + } else if (it.first == "search_tolerance") { + params.search_tolerance = std::stof(it.second); + } else if (it.first == "search_q_precision") { + params.search_q_precision = std::stof(it.second); + } else if (it.first == "search_first_iter_slope") { + params.search_first_iter_slope = std::stof(it.second); } } - std::vector icc; - if (!IsSRGBEncoding(ppf.color_encoding)) { - icc = ppf.icc; - } + params.is_xyb = (ppf.color_encoding.color_space == JXL_COLOR_SPACE_XYB); encoded_image->bitstreams.clear(); encoded_image->bitstreams.reserve(ppf.frames.size()); for (const auto& frame : ppf.frames) { JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info)); encoded_image->bitstreams.emplace_back(); JXL_RETURN_IF_ERROR(EncodeImageJPG( - frame.color, ppf.info, icc, ppf.metadata.exif, jpeg_encoder, quality, - chroma_subsampling, pool, &encoded_image->bitstreams.back())); + frame.color, ppf.info, ppf.icc, ppf.metadata.exif, jpeg_encoder, + params, pool, &encoded_image->bitstreams.back())); } return true; } }; } // namespace +#endif std::unique_ptr GetJPEGEncoder() { +#if JPEGXL_ENABLE_JPEG return jxl::make_unique(); +#else + return nullptr; +#endif } } // namespace extras diff --git a/media/libjxl/src/lib/extras/enc/jxl.cc b/media/libjxl/src/lib/extras/enc/jxl.cc new file mode 100644 index 0000000000..15f6267316 --- /dev/null +++ b/media/libjxl/src/lib/extras/enc/jxl.cc @@ -0,0 +1,387 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "lib/extras/enc/jxl.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "lib/extras/packed_image.h" +#include "lib/jxl/base/exif.h" + +namespace jxl { +namespace extras { + +JxlEncoderStatus SetOption(const JXLOption& opt, + JxlEncoderFrameSettings* settings) { + return opt.is_float + ? JxlEncoderFrameSettingsSetFloatOption(settings, opt.id, opt.fval) + : JxlEncoderFrameSettingsSetOption(settings, opt.id, opt.ival); +} + +bool SetFrameOptions(const std::vector& options, size_t frame_index, + size_t* option_idx, JxlEncoderFrameSettings* settings) { + while (*option_idx < options.size()) { + const auto& opt = options[*option_idx]; + if (opt.frame_index > frame_index) { + break; + } + if (JXL_ENC_SUCCESS != SetOption(opt, settings)) { + fprintf(stderr, "Setting option id %d failed.\n", opt.id); + return false; + } + (*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(params.memory_manager); + JxlEncoder* enc = encoder.get(); + + if (params.allow_expert_options) { + JxlEncoderAllowExpertOptions(enc); + } + + if (params.runner_opaque != nullptr && + JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(enc, params.runner, + 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; + } + if (JXL_ENC_SUCCESS != + JxlEncoderSetFrameDistance(settings, params.distance)) { + fprintf(stderr, "Setting frame distance failed.\n"); + return false; + } + if (params.debug_image) { + JxlEncoderSetDebugImageCallback(settings, params.debug_image, + params.debug_image_opaque); + } + if (params.stats) { + JxlEncoderCollectStats(settings, params.stats); + } + + bool has_jpeg_bytes = (jpeg_bytes != nullptr); + bool use_boxes = !ppf.metadata.exif.empty() || !ppf.metadata.xmp.empty() || + !ppf.metadata.jhgm.empty() || !ppf.metadata.jumbf.empty() || + !ppf.metadata.iptc.empty(); + bool use_container = params.use_container || use_boxes || + (has_jpeg_bytes && params.jpeg_store_metadata); + + if (JXL_ENC_SUCCESS != + JxlEncoderUseContainer(enc, static_cast(use_container))) { + fprintf(stderr, "JxlEncoderUseContainer failed.\n"); + return false; + } + + if (has_jpeg_bytes) { + if (params.jpeg_store_metadata && + JXL_ENC_SUCCESS != JxlEncoderStoreJPEGMetadata(enc, JXL_TRUE)) { + fprintf(stderr, "Storing JPEG metadata failed.\n"); + return false; + } + if (params.jpeg_store_metadata && params.jpeg_strip_exif) { + fprintf(stderr, + "Cannot store metadata and strip exif at the same time.\n"); + return false; + } + if (params.jpeg_store_metadata && params.jpeg_strip_xmp) { + fprintf(stderr, + "Cannot store metadata and strip xmp at the same time.\n"); + return false; + } + if (!params.jpeg_store_metadata && params.jpeg_strip_exif) { + JxlEncoderFrameSettingsSetOption(settings, + JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF, 0); + } + if (!params.jpeg_store_metadata && params.jpeg_strip_xmp) { + JxlEncoderFrameSettingsSetOption(settings, + JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP, 0); + } + if (params.jpeg_strip_jumbf) { + JxlEncoderFrameSettingsSetOption( + settings, JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF, 0); + } + if (JXL_ENC_SUCCESS != JxlEncoderAddJPEGFrame(settings, jpeg_bytes->data(), + jpeg_bytes->size())) { + JxlEncoderError error = JxlEncoderGetError(enc); + if (error == JXL_ENC_ERR_BAD_INPUT) { + fprintf(stderr, + "Error while decoding the JPEG image. It may be corrupt (e.g. " + "truncated) or of an unsupported type (e.g. CMYK).\n"); + } else if (error == JXL_ENC_ERR_JBRD) { + fprintf(stderr, + "JPEG bitstream reconstruction data could not be created. " + "Possibly there is too much tail data.\n" + "Try using --allow_jpeg_reconstruction 0, to losslessly " + "recompress the JPEG image data without bitstream " + "reconstruction data.\n"); + } else { + fprintf(stderr, "JxlEncoderAddJPEGFrame() failed.\n"); + } + return false; + } + } else { + size_t num_alpha_channels = 0; // Adjusted below. + JxlBasicInfo basic_info = ppf.info; + basic_info.xsize *= params.already_downsampled; + basic_info.ysize *= params.already_downsampled; + if (basic_info.alpha_bits > 0) num_alpha_channels = 1; + if (params.intensity_target > 0) { + basic_info.intensity_target = params.intensity_target; + } + basic_info.num_extra_channels = + std::max(num_alpha_channels, ppf.info.num_extra_channels); + basic_info.num_color_channels = ppf.info.num_color_channels; + const bool lossless = (params.distance == 0); + auto non_perceptual_option = std::find_if( + params.options.begin(), params.options.end(), [](JXLOption option) { + return option.id == + JXL_ENC_FRAME_SETTING_DISABLE_PERCEPTUAL_HEURISTICS; + }); + const bool non_perceptual = non_perceptual_option != params.options.end() && + non_perceptual_option->ival == 1; + basic_info.uses_original_profile = TO_JXL_BOOL(lossless || non_perceptual); + if (params.override_bitdepth != 0) { + basic_info.bits_per_sample = params.override_bitdepth; + basic_info.exponent_bits_per_sample = + params.override_bitdepth == 32 ? 8 : 0; + } + if (JXL_ENC_SUCCESS != + JxlEncoderSetCodestreamLevel(enc, params.codestream_level)) { + fprintf(stderr, "Setting --codestream_level failed.\n"); + return false; + } + if (JXL_ENC_SUCCESS != JxlEncoderSetBasicInfo(enc, &basic_info)) { + fprintf(stderr, "JxlEncoderSetBasicInfo() failed.\n"); + return false; + } + if (JXL_ENC_SUCCESS != + JxlEncoderSetUpsamplingMode(enc, params.already_downsampled, + params.upsampling_mode)) { + fprintf(stderr, "JxlEncoderSetUpsamplingMode() failed.\n"); + return false; + } + if (JXL_ENC_SUCCESS != + JxlEncoderSetFrameBitDepth(settings, &ppf.input_bitdepth)) { + fprintf(stderr, "JxlEncoderSetFrameBitDepth() failed.\n"); + return false; + } + if (num_alpha_channels != 0 && + JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelDistance( + settings, 0, params.alpha_distance)) { + fprintf(stderr, "Setting alpha distance failed.\n"); + return false; + } + if (lossless && + JXL_ENC_SUCCESS != JxlEncoderSetFrameLossless(settings, JXL_TRUE)) { + fprintf(stderr, "JxlEncoderSetFrameLossless() failed.\n"); + return false; + } + if (ppf.primary_color_representation == PackedPixelFile::kIccIsPrimary) { + if (JXL_ENC_SUCCESS != + JxlEncoderSetICCProfile(enc, ppf.icc.data(), ppf.icc.size())) { + fprintf(stderr, "JxlEncoderSetICCProfile() failed.\n"); + return false; + } + } else { + if (JXL_ENC_SUCCESS != + JxlEncoderSetColorEncoding(enc, &ppf.color_encoding)) { + fprintf(stderr, "JxlEncoderSetColorEncoding() failed.\n"); + return false; + } + } + + if (use_boxes) { + if (JXL_ENC_SUCCESS != JxlEncoderUseBoxes(enc)) { + fprintf(stderr, "JxlEncoderUseBoxes() failed.\n"); + return false; + } + // Prepend 4 zero bytes to exif for tiff header offset + std::vector exif_with_offset; + bool bigendian; + if (IsExif(ppf.metadata.exif, &bigendian)) { + exif_with_offset.resize(ppf.metadata.exif.size() + 4); + memcpy(exif_with_offset.data() + 4, ppf.metadata.exif.data(), + ppf.metadata.exif.size()); + } + const struct BoxInfo { + const char* type; + const std::vector& bytes; + } boxes[] = { + {"Exif", exif_with_offset}, {"xml ", ppf.metadata.xmp}, + {"jumb", ppf.metadata.jumbf}, {"xml ", ppf.metadata.iptc}, + {"jhgm", ppf.metadata.jhgm}, + }; + for (auto box : boxes) { + if (!box.bytes.empty()) { + if (JXL_ENC_SUCCESS != + JxlEncoderAddBox(enc, box.type, box.bytes.data(), + box.bytes.size(), + TO_JXL_BOOL(params.compress_boxes))) { + fprintf(stderr, "JxlEncoderAddBox() failed (%s).\n", box.type); + return false; + } + } + } + JxlEncoderCloseBoxes(enc); + } + + 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; + size_t num_interleaved_alpha = + (ppixelformat.num_channels - ppf.info.num_color_channels); + 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"); + return false; + } + // Only set extra channel buffer if it is provided non-interleaved. + for (size_t i = 0; i < pframe.extra_channels.size(); ++i) { + if (JXL_ENC_SUCCESS != + JxlEncoderSetExtraChannelBuffer(settings, &ppixelformat, + pframe.extra_channels[i].pixels(), + pframe.extra_channels[i].stride * + pframe.extra_channels[i].ysize, + num_interleaved_alpha + i)) { + 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, TO_JXL_BOOL(last_frame), + chunked_frame.GetInputSource())) { + fprintf(stderr, "JxlEncoderAddChunkedFrame() failed.\n"); + return false; + } + } + } + JxlEncoderCloseInput(enc); + if (params.HasOutputProcessor()) { + if (JXL_ENC_SUCCESS != JxlEncoderFlushInput(enc)) { + fprintf(stderr, "JxlEncoderAddChunkedFrame() failed.\n"); + return false; + } + } else if (!ReadCompressedOutput(enc, compressed)) { + return false; + } + return true; +} + +} // namespace extras +} // namespace jxl diff --git a/media/libjxl/src/lib/extras/enc/jxl.h b/media/libjxl/src/lib/extras/enc/jxl.h new file mode 100644 index 0000000000..a6573eb129 --- /dev/null +++ b/media/libjxl/src/lib/extras/enc/jxl.h @@ -0,0 +1,102 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_EXTRAS_ENC_JXL_H_ +#define LIB_EXTRAS_ENC_JXL_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "lib/extras/packed_image.h" + +namespace jxl { +namespace extras { + +struct JXLOption { + JXLOption(JxlEncoderFrameSettingId id, int64_t val, size_t frame_index) + : id(id), is_float(false), ival(val), frame_index(frame_index) {} + JXLOption(JxlEncoderFrameSettingId id, float val, size_t frame_index) + : id(id), is_float(true), fval(val), frame_index(frame_index) {} + + JxlEncoderFrameSettingId id; + bool is_float; + union { + int64_t ival; + float fval; + }; + size_t frame_index; +}; + +struct JXLCompressParams { + std::vector options; + + // Target butteraugli distance, 0.0 means lossless. + float distance = 1.0f; + float alpha_distance = 0.0f; + + // If set to true, forces container mode. + bool use_container = false; + + // Whether to enable/disable byte-exact jpeg reconstruction for jpeg inputs. + bool jpeg_store_metadata = true; + bool jpeg_strip_exif = false; + bool jpeg_strip_xmp = false; + bool jpeg_strip_jumbf = false; + + // Whether to create brob boxes. + bool compress_boxes = true; + + // Upper bound on the intensity level present in the image in nits (zero means + // that the library chooses a default). + float intensity_target = 0; + int already_downsampled = 1; + int upsampling_mode = -1; + + // Overrides for bitdepth, codestream level and alpha premultiply. + size_t override_bitdepth = 0; + int32_t codestream_level = -1; + int32_t premultiply = -1; + + // If runner_opaque is set, the encoder uses this parallel runner. + JxlParallelRunner runner = JxlThreadParallelRunner; + void* runner_opaque = nullptr; + + // If memory_manager is set, encoder uses it. + JxlMemoryManager* memory_manager = nullptr; + + JxlEncoderOutputProcessor output_processor = {}; + JxlDebugImageCallback debug_image = nullptr; + void* debug_image_opaque = nullptr; + JxlEncoderStats* stats = nullptr; + bool allow_expert_options = false; + + void AddOption(JxlEncoderFrameSettingId id, int64_t val) { + options.emplace_back(id, val, 0); + } + void AddFloatOption(JxlEncoderFrameSettingId id, float val) { + options.emplace_back(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); + +} // namespace extras +} // namespace jxl + +#endif // LIB_EXTRAS_ENC_JXL_H_ diff --git a/media/libjxl/src/lib/extras/enc/npy.cc b/media/libjxl/src/lib/extras/enc/npy.cc index 1428e6427d..38777a3535 100644 --- a/media/libjxl/src/lib/extras/enc/npy.cc +++ b/media/libjxl/src/lib/extras/enc/npy.cc @@ -5,14 +5,15 @@ #include "lib/extras/enc/npy.h" -#include +#include +#include #include #include #include -#include "jxl/types.h" #include "lib/extras/packed_image.h" +#include "lib/jxl/base/common.h" namespace jxl { namespace extras { @@ -53,14 +54,17 @@ class JSONDict : public JSONField { static_assert(std::is_convertible::value, "T must be a JSONField"); T* ret = new T(); - values_.emplace_back( - key, std::unique_ptr(static_cast(ret))); + JSONField* field = static_cast(ret); + auto handle = std::unique_ptr(field); + values_.emplace_back(key, std::move(handle)); return ret; } template void Add(const std::string& key, const T& value) { - values_.emplace_back(key, std::unique_ptr(new JSONValue(value))); + JSONField* field = static_cast(new JSONValue(value)); + auto handle = std::unique_ptr(field); + values_.emplace_back(key, std::move(handle)); } void Write(std::ostream& o, uint32_t indent) const override { @@ -72,11 +76,11 @@ class JSONDict : public JSONField { o << ","; } is_first = false; - o << std::endl << indent_str << " \"" << key_value.first << "\": "; + o << "\n" << indent_str << " \"" << key_value.first << "\": "; key_value.second->Write(o, indent + 2); } if (!values_.empty()) { - o << std::endl << indent_str; + o << "\n" << indent_str; } o << "}"; } @@ -113,11 +117,11 @@ class JSONArray : public JSONField { o << ","; } is_first = false; - o << std::endl << indent_str << " "; + o << "\n" << indent_str << " "; value->Write(o, indent + 2); } if (!values_.empty()) { - o << std::endl << indent_str; + o << "\n" << indent_str; } o << "]"; } @@ -161,13 +165,13 @@ void GenerateMetadata(const PackedPixelFile& ppf, std::vector* out) { } { - auto ectype = meta.AddEmpty("extra_channel_type"); - auto bps = meta.AddEmpty("bits_per_sample"); - auto ebps = meta.AddEmpty("exp_bits_per_sample"); + auto* ectype = meta.AddEmpty("extra_channel_type"); + auto* bps = meta.AddEmpty("bits_per_sample"); + auto* ebps = meta.AddEmpty("exp_bits_per_sample"); bps->Add(ppf.info.bits_per_sample); ebps->Add(ppf.info.exponent_bits_per_sample); - for (size_t i = 0; i < ppf.extra_channels_info.size(); i++) { - switch (ppf.extra_channels_info[i].ec_info.type) { + for (const auto& eci : ppf.extra_channels_info) { + switch (eci.ec_info.type) { case JXL_CHANNEL_ALPHA: { ectype->Add(std::string("Alpha")); break; @@ -201,8 +205,8 @@ void GenerateMetadata(const PackedPixelFile& ppf, std::vector* out) { break; } } - bps->Add(ppf.extra_channels_info[i].ec_info.bits_per_sample); - ebps->Add(ppf.extra_channels_info[i].ec_info.exponent_bits_per_sample); + bps->Add(eci.ec_info.bits_per_sample); + ebps->Add(eci.ec_info.exponent_bits_per_sample); } } @@ -250,14 +254,14 @@ bool WriteFrameToNPYArray(size_t xsize, size_t ysize, const PackedFrame& frame, size_t sample_size = color.pixel_stride(); size_t offset = y * color.stride + x * sample_size; uint8_t* pixels = reinterpret_cast(color.pixels()); - JXL_ASSERT(offset + sample_size <= color.pixels_size); + JXL_ENSURE(offset + sample_size <= color.pixels_size); Append(out, pixels + offset, sample_size); } for (const auto& ec : frame.extra_channels) { size_t sample_size = ec.pixel_stride(); size_t offset = y * ec.stride + x * sample_size; uint8_t* pixels = reinterpret_cast(ec.pixels()); - JXL_ASSERT(offset + sample_size <= ec.pixels_size); + JXL_ENSURE(offset + sample_size <= ec.pixels_size); Append(out, pixels + offset, sample_size); } } @@ -283,7 +287,7 @@ bool WriteNPYArray(const PackedPixelFile& ppf, std::vector* out) { class NumPyEncoder : public Encoder { public: Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, - ThreadPool* pool = nullptr) const override { + ThreadPool* pool) const override { JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); GenerateMetadata(ppf, &encoded_image->metadata); encoded_image->bitstreams.emplace_back(); diff --git a/media/libjxl/src/lib/extras/enc/pgx.cc b/media/libjxl/src/lib/extras/enc/pgx.cc index ef204ad1c6..fb47b3e1a9 100644 --- a/media/libjxl/src/lib/extras/enc/pgx.cc +++ b/media/libjxl/src/lib/extras/enc/pgx.cc @@ -5,13 +5,12 @@ #include "lib/extras/enc/pgx.h" -#include -#include +#include + +#include -#include "jxl/codestream_header.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/byte_order.h" -#include "lib/jxl/base/printf_macros.h" namespace jxl { namespace extras { @@ -51,6 +50,7 @@ Status EncodeImagePGX(const PackedFrame& frame, const JxlBasicInfo& info, const PackedImage& color = frame.color; const JxlPixelFormat format = color.format; const uint8_t* in = reinterpret_cast(color.pixels()); + JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(format.data_type)); size_t data_bits_per_sample = PackedImage::BitsPerChannel(format.data_type); size_t bytes_per_sample = data_bits_per_sample / kBitsPerByte; size_t num_samples = info.xsize * info.ysize; @@ -62,7 +62,7 @@ Status EncodeImagePGX(const PackedFrame& frame, const JxlBasicInfo& info, std::vector pixels(num_samples * bytes_per_sample); if (format.data_type == JXL_TYPE_UINT8) { - memcpy(&pixels[0], in, num_samples * bytes_per_sample); + memcpy(pixels.data(), in, num_samples * bytes_per_sample); } else if (format.data_type == JXL_TYPE_UINT16) { if (format.endianness != JXL_BIG_ENDIAN) { const uint8_t* p_in = in; @@ -71,7 +71,7 @@ Status EncodeImagePGX(const PackedFrame& frame, const JxlBasicInfo& info, StoreBE16(LoadLE16(p_in), p_out); } } else { - memcpy(&pixels[0], in, num_samples * bytes_per_sample); + memcpy(pixels.data(), in, num_samples * bytes_per_sample); } } else { return JXL_FAILURE("Unsupported pixel data type"); diff --git a/media/libjxl/src/lib/extras/enc/pnm.cc b/media/libjxl/src/lib/extras/enc/pnm.cc index 9b5f6cbc95..c4aa8d12eb 100644 --- a/media/libjxl/src/lib/extras/enc/pnm.cc +++ b/media/libjxl/src/lib/extras/enc/pnm.cc @@ -5,96 +5,28 @@ #include "lib/extras/enc/pnm.h" -#include -#include - +#include +#include +#include +#include #include #include #include "lib/extras/packed_image.h" -#include "lib/jxl/base/byte_order.h" -#include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/file_io.h" +#include "lib/jxl/base/common.h" #include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" -#include "lib/jxl/color_management.h" -#include "lib/jxl/dec_external_image.h" -#include "lib/jxl/enc_color_management.h" -#include "lib/jxl/enc_external_image.h" -#include "lib/jxl/enc_image_bundle.h" -#include "lib/jxl/fields.h" // AllDefault -#include "lib/jxl/image.h" -#include "lib/jxl/image_bundle.h" namespace jxl { namespace extras { namespace { -constexpr size_t kMaxHeaderSize = 200; +constexpr size_t kMaxHeaderSize = 2000; -Status EncodeHeader(const PackedImage& image, size_t bits_per_sample, - bool little_endian, char* header, int* chars_written) { - size_t num_channels = image.format.num_channels; - bool is_gray = num_channels <= 2; - bool has_alpha = num_channels == 2 || num_channels == 4; - if (has_alpha) { // PAM - if (bits_per_sample > 16) return JXL_FAILURE("PNM cannot have > 16 bits"); - const uint32_t max_val = (1U << bits_per_sample) - 1; - *chars_written = - snprintf(header, kMaxHeaderSize, - "P7\nWIDTH %" PRIuS "\nHEIGHT %" PRIuS - "\nDEPTH %u\nMAXVAL %u\nTUPLTYPE %s\nENDHDR\n", - image.xsize, image.ysize, is_gray ? 2 : 4, max_val, - is_gray ? "GRAYSCALE_ALPHA" : "RGB_ALPHA"); - JXL_RETURN_IF_ERROR(static_cast(*chars_written) < - kMaxHeaderSize); - } else if (bits_per_sample == 32) { // PFM - const char type = is_gray ? 'f' : 'F'; - const double scale = little_endian ? -1.0 : 1.0; - *chars_written = - snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%.1f\n", - type, image.xsize, image.ysize, scale); - JXL_RETURN_IF_ERROR(static_cast(*chars_written) < - kMaxHeaderSize); - } else { // PGM/PPM - if (bits_per_sample > 16) return JXL_FAILURE("PNM cannot have > 16 bits"); - const uint32_t max_val = (1U << bits_per_sample) - 1; - const char type = is_gray ? '5' : '6'; - *chars_written = - snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%u\n", - type, image.xsize, image.ysize, max_val); - JXL_RETURN_IF_ERROR(static_cast(*chars_written) < - kMaxHeaderSize); - } - return true; -} - -Status EncodeImagePNM(const PackedImage& image, size_t bits_per_sample, - std::vector* bytes) { - // Choose native for PFM; PGM/PPM require big-endian - bool is_little_endian = bits_per_sample > 16 && IsLittleEndian(); - char header[kMaxHeaderSize]; - int header_size = 0; - JXL_RETURN_IF_ERROR(EncodeHeader(image, bits_per_sample, is_little_endian, - header, &header_size)); - bytes->resize(static_cast(header_size) + image.pixels_size); - memcpy(bytes->data(), header, static_cast(header_size)); - const bool flipped_y = bits_per_sample == 32; // PFMs are flipped - const uint8_t* in = reinterpret_cast(image.pixels()); - uint8_t* out = bytes->data() + header_size; - for (size_t y = 0; y < image.ysize; ++y) { - size_t y_out = flipped_y ? image.ysize - 1 - y : y; - const uint8_t* row_in = &in[y * image.stride]; - uint8_t* row_out = &out[y_out * image.stride]; - memcpy(row_out, row_in, image.stride); - } - return true; -} - -class PNMEncoder : public Encoder { +class BasePNMEncoder : public Encoder { public: Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, - ThreadPool* pool = nullptr) const override { + ThreadPool* pool) const override { JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); if (!ppf.metadata.exif.empty() || !ppf.metadata.iptc.empty() || !ppf.metadata.jumbf.empty() || !ppf.metadata.xmp.empty()) { @@ -106,8 +38,8 @@ class PNMEncoder : public Encoder { for (const auto& frame : ppf.frames) { JXL_RETURN_IF_ERROR(VerifyPackedImage(frame.color, ppf.info)); encoded_image->bitstreams.emplace_back(); - JXL_RETURN_IF_ERROR(EncodeImagePNM(frame.color, ppf.info.bits_per_sample, - &encoded_image->bitstreams.back())); + JXL_RETURN_IF_ERROR( + EncodeFrame(ppf, frame, &encoded_image->bitstreams.back())); } for (size_t i = 0; i < ppf.extra_channels_info.size(); ++i) { const auto& ec_info = ppf.extra_channels_info[i].ec_info; @@ -115,92 +47,274 @@ class PNMEncoder : public Encoder { auto& ec_bitstreams = encoded_image->extra_channel_bitstreams.back(); for (const auto& frame : ppf.frames) { ec_bitstreams.emplace_back(); - JXL_RETURN_IF_ERROR(EncodeImagePNM(frame.extra_channels[i], - ec_info.bits_per_sample, - &ec_bitstreams.back())); + JXL_RETURN_IF_ERROR(EncodeExtraChannel(frame.extra_channels[i], + ec_info.bits_per_sample, + &ec_bitstreams.back())); } } return true; } + + protected: + virtual Status EncodeFrame(const PackedPixelFile& ppf, + const PackedFrame& frame, + std::vector* bytes) const = 0; + virtual Status EncodeExtraChannel(const PackedImage& image, + size_t bits_per_sample, + std::vector* bytes) const = 0; +}; + +class PNMEncoder : public BasePNMEncoder { + public: + static const std::vector kAcceptedFormats; + + std::vector AcceptedFormats() const override { + return kAcceptedFormats; + } + + Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame, + std::vector* bytes) const override { + return EncodeImage(frame.color, ppf.info.bits_per_sample, bytes); + } + Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample, + std::vector* bytes) const override { + return EncodeImage(image, bits_per_sample, bytes); + } + + private: + static Status EncodeImage(const PackedImage& image, size_t bits_per_sample, + std::vector* bytes) { + uint32_t maxval = (1u << bits_per_sample) - 1; + char type = image.format.num_channels == 1 ? '5' : '6'; + char header[kMaxHeaderSize]; + size_t header_size = + snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%u\n", + type, image.xsize, image.ysize, maxval); + JXL_RETURN_IF_ERROR(header_size < kMaxHeaderSize); + bytes->resize(header_size + image.pixels_size); + memcpy(bytes->data(), header, header_size); + memcpy(bytes->data() + header_size, + reinterpret_cast(image.pixels()), image.pixels_size); + return true; + } +}; + +class PGMEncoder : public PNMEncoder { + public: + static const std::vector kAcceptedFormats; + + std::vector AcceptedFormats() const override { + return kAcceptedFormats; + } +}; + +const std::vector PGMEncoder::kAcceptedFormats = { + JxlPixelFormat{1, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0}, + JxlPixelFormat{1, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}}; + +class PPMEncoder : public PNMEncoder { + public: + static const std::vector kAcceptedFormats; + + std::vector AcceptedFormats() const override { + return kAcceptedFormats; + } +}; + +const std::vector PPMEncoder::kAcceptedFormats = { + JxlPixelFormat{3, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0}, + JxlPixelFormat{3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}}; + +const std::vector PNMEncoder::kAcceptedFormats = [] { + std::vector combined = PPMEncoder::kAcceptedFormats; + combined.insert(combined.end(), PGMEncoder::kAcceptedFormats.begin(), + PGMEncoder::kAcceptedFormats.end()); + return combined; +}(); + +class PFMEncoder : public BasePNMEncoder { + public: + std::vector AcceptedFormats() const override { + std::vector formats; + for (const uint32_t num_channels : {1, 3}) { + for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) { + formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, + /*data_type=*/JXL_TYPE_FLOAT, + /*endianness=*/endianness, + /*align=*/0}); + } + } + return formats; + } + Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame, + std::vector* bytes) const override { + return EncodeImage(frame.color, bytes); + } + Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample, + std::vector* bytes) const override { + return EncodeImage(image, bytes); + } + + private: + static Status EncodeImage(const PackedImage& image, + std::vector* bytes) { + char type = image.format.num_channels == 1 ? 'f' : 'F'; + double scale = image.format.endianness == JXL_LITTLE_ENDIAN ? -1.0 : 1.0; + char header[kMaxHeaderSize]; + size_t header_size = + snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%.1f\n", + type, image.xsize, image.ysize, scale); + JXL_RETURN_IF_ERROR(header_size < kMaxHeaderSize); + bytes->resize(header_size + image.pixels_size); + memcpy(bytes->data(), header, header_size); + const uint8_t* in = reinterpret_cast(image.pixels()); + uint8_t* out = bytes->data() + header_size; + for (size_t y = 0; y < image.ysize; ++y) { + size_t y_out = image.ysize - 1 - y; + const uint8_t* row_in = &in[y * image.stride]; + uint8_t* row_out = &out[y_out * image.stride]; + memcpy(row_out, row_in, image.stride); + } + return true; + } }; -class PPMEncoder : public PNMEncoder { +class PAMEncoder : public BasePNMEncoder { public: std::vector AcceptedFormats() const override { std::vector formats; for (const uint32_t num_channels : {1, 2, 3, 4}) { for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) { - for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) { - formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, - /*data_type=*/data_type, - /*endianness=*/endianness, - /*align=*/0}); + formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, + /*data_type=*/data_type, + /*endianness=*/JXL_BIG_ENDIAN, + /*align=*/0}); + } + } + return formats; + } + Status EncodeFrame(const PackedPixelFile& ppf, const PackedFrame& frame, + std::vector* bytes) const override { + const PackedImage& color = frame.color; + const auto& ec_info = ppf.extra_channels_info; + JXL_RETURN_IF_ERROR(frame.extra_channels.size() == ec_info.size()); + for (const auto& ec : frame.extra_channels) { + if (ec.xsize != color.xsize || ec.ysize != color.ysize) { + return JXL_FAILURE("Extra channel and color size mismatch."); + } + if (ec.format.data_type != color.format.data_type || + ec.format.endianness != color.format.endianness) { + return JXL_FAILURE("Extra channel and color format mismatch."); + } + } + if (ppf.info.alpha_bits && + (ppf.info.bits_per_sample != ppf.info.alpha_bits)) { + return JXL_FAILURE("Alpha bit depth does not match image bit depth"); + } + for (const auto& it : ec_info) { + if (it.ec_info.bits_per_sample != ppf.info.bits_per_sample) { + return JXL_FAILURE( + "Extra channel bit depth does not match image bit depth"); + } + } + const char* kColorTypes[4] = {"GRAYSCALE", "GRAYSCALE_ALPHA", "RGB", + "RGB_ALPHA"}; + uint32_t maxval = (1u << ppf.info.bits_per_sample) - 1; + uint32_t depth = color.format.num_channels + ec_info.size(); + char header[kMaxHeaderSize]; + size_t pos = 0; + pos += snprintf(header + pos, kMaxHeaderSize - pos, + "P7\nWIDTH %" PRIuS "\nHEIGHT %" PRIuS + "\nDEPTH %u\n" + "MAXVAL %u\nTUPLTYPE %s\n", + color.xsize, color.ysize, depth, maxval, + kColorTypes[color.format.num_channels - 1]); + JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize); + for (const auto& info : ec_info) { + pos += snprintf(header + pos, kMaxHeaderSize - pos, "TUPLTYPE %s\n", + ExtraChannelTypeName(info.ec_info.type).c_str()); + JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize); + } + pos += snprintf(header + pos, kMaxHeaderSize - pos, "ENDHDR\n"); + JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize); + size_t total_size = color.pixels_size; + for (const auto& ec : frame.extra_channels) { + total_size += ec.pixels_size; + } + bytes->resize(pos + total_size); + memcpy(bytes->data(), header, pos); + // If we have no extra channels, just copy color pixel data over. + if (frame.extra_channels.empty()) { + memcpy(bytes->data() + pos, reinterpret_cast(color.pixels()), + color.pixels_size); + return true; + } + // Interleave color and extra channels. + const uint8_t* in = reinterpret_cast(color.pixels()); + std::vector ec_in(frame.extra_channels.size()); + for (size_t i = 0; i < frame.extra_channels.size(); ++i) { + ec_in[i] = + reinterpret_cast(frame.extra_channels[i].pixels()); + } + uint8_t* out = bytes->data() + pos; + JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(color.format.data_type)); + size_t pwidth = PackedImage::BitsPerChannel(color.format.data_type) / 8; + for (size_t y = 0; y < color.ysize; ++y) { + for (size_t x = 0; x < color.xsize; ++x) { + memcpy(out, in, color.pixel_stride()); + out += color.pixel_stride(); + in += color.pixel_stride(); + for (auto& p : ec_in) { + memcpy(out, p, pwidth); + out += pwidth; + p += pwidth; } } } - return formats; + return true; + } + Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample, + std::vector* bytes) const override { + return true; } -}; -class PFMEncoder : public PNMEncoder { - public: - std::vector AcceptedFormats() const override { - std::vector formats; - for (const uint32_t num_channels : {1, 3}) { - for (const JxlDataType data_type : {JXL_TYPE_FLOAT16, JXL_TYPE_FLOAT}) { - for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) { - formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels, - /*data_type=*/data_type, - /*endianness=*/endianness, - /*align=*/0}); - } - } + private: + static std::string ExtraChannelTypeName(JxlExtraChannelType type) { + switch (type) { + case JXL_CHANNEL_ALPHA: + return std::string("Alpha"); + case JXL_CHANNEL_DEPTH: + return std::string("Depth"); + case JXL_CHANNEL_SPOT_COLOR: + return std::string("SpotColor"); + case JXL_CHANNEL_SELECTION_MASK: + return std::string("SelectionMask"); + case JXL_CHANNEL_BLACK: + return std::string("Black"); + case JXL_CHANNEL_CFA: + return std::string("CFA"); + case JXL_CHANNEL_THERMAL: + return std::string("Thermal"); + case JXL_CHANNEL_UNKNOWN: + return std::string("Unknown"); + case JXL_CHANNEL_OPTIONAL: + return std::string("Optional"); + default: + return std::string("UNKNOWN"); } - return formats; } }; -class PGMEncoder : public PPMEncoder { - public: - std::vector AcceptedFormats() const override { - std::vector formats = PPMEncoder::AcceptedFormats(); - for (auto it = formats.begin(); it != formats.end();) { - if (it->num_channels > 2) { - it = formats.erase(it); - } else { - ++it; - } - } - return formats; - } -}; - -class PAMEncoder : public PPMEncoder { - public: - std::vector AcceptedFormats() const override { - std::vector formats = PPMEncoder::AcceptedFormats(); - for (auto it = formats.begin(); it != formats.end();) { - if (it->num_channels != 2 && it->num_channels != 4) { - it = formats.erase(it); - } else { - ++it; - } - } - return formats; - } -}; - -Span MakeSpan(const char* str) { - return Span(reinterpret_cast(str), - strlen(str)); -} - } // namespace std::unique_ptr GetPPMEncoder() { return jxl::make_unique(); } +std::unique_ptr GetPNMEncoder() { + return jxl::make_unique(); +} + std::unique_ptr GetPFMEncoder() { return jxl::make_unique(); } diff --git a/media/libjxl/src/lib/extras/enc/pnm.h b/media/libjxl/src/lib/extras/enc/pnm.h index 403208cecd..1e0020cdbd 100644 --- a/media/libjxl/src/lib/extras/enc/pnm.h +++ b/media/libjxl/src/lib/extras/enc/pnm.h @@ -19,6 +19,7 @@ namespace extras { std::unique_ptr GetPAMEncoder(); std::unique_ptr GetPGMEncoder(); +std::unique_ptr GetPNMEncoder(); std::unique_ptr GetPPMEncoder(); std::unique_ptr GetPFMEncoder(); diff --git a/media/libjxl/src/lib/extras/exif.cc b/media/libjxl/src/lib/extras/exif.cc index 7d926558c3..aea632732b 100644 --- a/media/libjxl/src/lib/extras/exif.cc +++ b/media/libjxl/src/lib/extras/exif.cc @@ -23,7 +23,7 @@ void ResetExifOrientation(std::vector& exif) { return; // not a valid tiff header } t += 4; - uint32_t offset = (bigendian ? LoadBE32(t) : LoadLE32(t)); + uint64_t offset = (bigendian ? LoadBE32(t) : LoadLE32(t)); if (exif.size() < 12 + offset + 2 || offset < 8) return; t += offset - 4; uint16_t nb_tags = (bigendian ? LoadBE16(t) : LoadLE16(t)); diff --git a/media/libjxl/src/lib/extras/gain_map.cc b/media/libjxl/src/lib/extras/gain_map.cc new file mode 100644 index 0000000000..8cce67161b --- /dev/null +++ b/media/libjxl/src/lib/extras/gain_map.cc @@ -0,0 +1,231 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include + +#include +#include +#include +#include + +#include "lib/jxl/base/byte_order.h" +#include "lib/jxl/base/common.h" +#include "lib/jxl/color_encoding_internal.h" +#include "lib/jxl/dec_bit_reader.h" +#include "lib/jxl/enc_aux_out.h" +#include "lib/jxl/enc_bit_writer.h" +#include "lib/jxl/fields.h" +#include "lib/jxl/memory_manager_internal.h" + +namespace { + +template +class FixedSizeMemoryManager { + public: + FixedSizeMemoryManager() = default; + + JxlMemoryManager* memory_manager() { return &manager_; } + + private: + static void* FixedSizeMemoryManagerAlloc(void* opaque, size_t capacity) { + auto manager = static_cast*>(opaque); + if (capacity > N + jxl::memory_manager_internal::kAlias) { + return nullptr; + } + return manager->memory_; + } + static void FixedSizeMemoryManagerFree(void* opaque, void* pointer) {} + + uint8_t memory_[N + jxl::memory_manager_internal::kAlias]; + JxlMemoryManager manager_ = { + /*opaque=*/this, + /*alloc=*/&FixedSizeMemoryManagerAlloc, + /*free=*/&FixedSizeMemoryManagerFree, + }; +}; + +} // namespace + +JXL_BOOL JxlGainMapGetBundleSize(const JxlGainMapBundle* map_bundle, + size_t* bundle_size) { + if (map_bundle == nullptr) return JXL_FALSE; + + jxl::ColorEncoding internal_color_encoding; + size_t color_encoding_size = 0; + size_t extension_bits = 0; + if (map_bundle->has_color_encoding) { + JXL_RETURN_IF_ERROR( + internal_color_encoding.FromExternal(map_bundle->color_encoding)); + if (!jxl::Bundle::CanEncode(internal_color_encoding, &extension_bits, + &color_encoding_size)) { + return JXL_FALSE; + } + } + + *bundle_size = + 1 + // size of jhgm_version + 2 + // size_of gain_map_metadata_size + map_bundle->gain_map_metadata_size + // size of gain_map_metadata + 1 + // size of color_encoding_size + jxl::DivCeil(color_encoding_size, 8) + // size of the color_encoding + 4 + // size of compressed_icc_size + map_bundle->alt_icc_size + // size of compressed_icc + map_bundle->gain_map_size; // size of gain map + return JXL_TRUE; +} + +JXL_BOOL JxlGainMapWriteBundle(const JxlGainMapBundle* map_bundle, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* bytes_written) { + if (map_bundle == nullptr) return JXL_FALSE; + + uint8_t jhgm_version = map_bundle->jhgm_version; + + FixedSizeMemoryManager memory_manager; + jxl::ColorEncoding internal_color_encoding; + jxl::BitWriter color_encoding_writer(memory_manager.memory_manager()); + if (map_bundle->has_color_encoding) { + JXL_RETURN_IF_ERROR( + internal_color_encoding.FromExternal(map_bundle->color_encoding)); + if (!jxl::Bundle::Write(internal_color_encoding, &color_encoding_writer, + jxl::LayerType::Header, nullptr)) { + return JXL_FALSE; + } + } + + color_encoding_writer.ZeroPadToByte(); + + uint64_t cursor = 0; + uint64_t next_cursor = 0; + +#define SAFE_CURSOR_UPDATE(n) \ + do { \ + cursor = next_cursor; \ + if (!jxl::SafeAdd(cursor, n, next_cursor) || \ + next_cursor > output_buffer_size) { \ + return JXL_FALSE; \ + } \ + } while (false) + + SAFE_CURSOR_UPDATE(1); + memcpy(output_buffer + cursor, &jhgm_version, 1); + + SAFE_CURSOR_UPDATE(2); + StoreBE16(map_bundle->gain_map_metadata_size, output_buffer + cursor); + + SAFE_CURSOR_UPDATE(map_bundle->gain_map_metadata_size); + memcpy(output_buffer + cursor, map_bundle->gain_map_metadata, + map_bundle->gain_map_metadata_size); + + jxl::Bytes bytes = color_encoding_writer.GetSpan(); + uint8_t color_enc_size = static_cast(bytes.size()); + if (color_enc_size != bytes.size()) return JXL_FALSE; + SAFE_CURSOR_UPDATE(1); + memcpy(output_buffer + cursor, &color_enc_size, 1); + + SAFE_CURSOR_UPDATE(color_enc_size); + memcpy(output_buffer + cursor, bytes.data(), color_enc_size); + + SAFE_CURSOR_UPDATE(4); + StoreBE32(map_bundle->alt_icc_size, output_buffer + cursor); + + SAFE_CURSOR_UPDATE(map_bundle->alt_icc_size); + memcpy(output_buffer + cursor, map_bundle->alt_icc, map_bundle->alt_icc_size); + + SAFE_CURSOR_UPDATE(map_bundle->gain_map_size); + memcpy(output_buffer + cursor, map_bundle->gain_map, + map_bundle->gain_map_size); + +#undef SAFE_CURSOR_UPDATE + + cursor = next_cursor; + + if (bytes_written != nullptr) + *bytes_written = cursor; // Ensure size_t compatibility + return cursor == output_buffer_size ? JXL_TRUE : JXL_FALSE; +} + +JXL_BOOL JxlGainMapReadBundle(JxlGainMapBundle* map_bundle, + const uint8_t* input_buffer, + const size_t input_buffer_size, + size_t* bytes_read) { + if (map_bundle == nullptr || input_buffer == nullptr || + input_buffer_size == 0) { + return JXL_FALSE; + } + + uint64_t cursor = 0; + uint64_t next_cursor = 0; + +#define SAFE_CURSOR_UPDATE(n) \ + do { \ + cursor = next_cursor; \ + if (!jxl::SafeAdd(cursor, n, next_cursor) || \ + next_cursor > input_buffer_size) { \ + return JXL_FALSE; \ + } \ + } while (false) + + // Read the version byte + SAFE_CURSOR_UPDATE(1); + map_bundle->jhgm_version = input_buffer[cursor]; + + // Read gain_map_metadata_size + SAFE_CURSOR_UPDATE(2); + uint16_t gain_map_metadata_size = LoadBE16(input_buffer + cursor); + + SAFE_CURSOR_UPDATE(gain_map_metadata_size); + map_bundle->gain_map_metadata_size = gain_map_metadata_size; + map_bundle->gain_map_metadata = input_buffer + cursor; + + // Read compressed_color_encoding_size + SAFE_CURSOR_UPDATE(1); + uint8_t compressed_color_encoding_size; + memcpy(&compressed_color_encoding_size, input_buffer + cursor, 1); + + map_bundle->has_color_encoding = (compressed_color_encoding_size > 0); + if (map_bundle->has_color_encoding) { + SAFE_CURSOR_UPDATE(compressed_color_encoding_size); + // Decode color encoding + jxl::Span color_encoding_span( + input_buffer + cursor, compressed_color_encoding_size); + jxl::BitReader color_encoding_reader(color_encoding_span); + jxl::ColorEncoding internal_color_encoding; + if (!jxl::Bundle::Read(&color_encoding_reader, &internal_color_encoding)) { + return JXL_FALSE; + } + JXL_RETURN_IF_ERROR(color_encoding_reader.Close()); + map_bundle->color_encoding = internal_color_encoding.ToExternal(); + } + + // Read compressed_icc_size + SAFE_CURSOR_UPDATE(4); + uint32_t compressed_icc_size = LoadBE32(input_buffer + cursor); + + SAFE_CURSOR_UPDATE(compressed_icc_size); + map_bundle->alt_icc_size = compressed_icc_size; + map_bundle->alt_icc = input_buffer + cursor; + + // Calculate remaining bytes for gain map + cursor = next_cursor; + // This calculation is guaranteed not to underflow because `cursor` is always + // updated to a position within or at the end of `input_buffer` (not beyond). + // Thus, subtracting `cursor` from `input_buffer_size` (the total size of the + // buffer) will always result in a non-negative integer representing the + // remaining buffer size. + map_bundle->gain_map_size = input_buffer_size - cursor; + SAFE_CURSOR_UPDATE(map_bundle->gain_map_size); + map_bundle->gain_map = input_buffer + cursor; + +#undef SAFE_CURSOR_UPDATE + + cursor = next_cursor; + + if (bytes_read != nullptr) { + *bytes_read = cursor; + } + return JXL_TRUE; +} diff --git a/media/libjxl/src/lib/extras/gain_map_test.cc b/media/libjxl/src/lib/extras/gain_map_test.cc new file mode 100644 index 0000000000..7b4864974c --- /dev/null +++ b/media/libjxl/src/lib/extras/gain_map_test.cc @@ -0,0 +1,173 @@ +// 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 "jxl/gain_map.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "lib/jxl/test_utils.h" +#include "lib/jxl/testing.h" + +namespace { + +std::vector GoldenTestGainMap(bool has_icc, bool has_color_encoding) { + // Define the parts of the gain map + uint8_t jhgm_version = 0x00; + std::vector gain_map_metadata_size = {0x00, 0x58}; // 88 in decimal + // TODO(firsching): Replace with more realistic data + std::string first_placeholder = + "placeholder gain map metadata, fill with actual example after (ISO " + "21496-1) is finalized"; + + uint8_t color_encoding_size = has_color_encoding ? 3 : 0; + std::vector color_encoding = {0x50, 0xb4, 0x00}; + + std::vector icc_size = {0x00, 0x00, 0x00, 0x00}; + if (has_icc) { + icc_size = {0x00, 0x00, 0x00, 0x88}; // 136 in decimal + } + std::vector icc_data = jxl::test::GetCompressedIccTestProfile(); + std::string second_placeholder = + "placeholder for an actual naked JPEG XL codestream"; + + // Assemble the gain map + std::vector gain_map; + gain_map.push_back(jhgm_version); + gain_map.insert(gain_map.end(), gain_map_metadata_size.begin(), + gain_map_metadata_size.end()); + gain_map.insert(gain_map.end(), first_placeholder.begin(), + first_placeholder.end()); + gain_map.push_back(color_encoding_size); + if (has_color_encoding) { + gain_map.insert(gain_map.end(), color_encoding.begin(), + color_encoding.end()); + } + gain_map.insert(gain_map.end(), icc_size.begin(), icc_size.end()); + if (has_icc) { + gain_map.insert(gain_map.end(), icc_data.begin(), icc_data.end()); + } + gain_map.insert(gain_map.end(), second_placeholder.begin(), + second_placeholder.end()); + + return gain_map; +} + +} // namespace + +namespace jxl { +namespace { + +struct GainMapTestParams { + bool has_color_encoding; + std::vector icc_data; +}; + +class GainMapTest : public ::testing::TestWithParam {}; + +TEST_P(GainMapTest, GainMapRoundtrip) { + size_t bundle_size; + const GainMapTestParams& params = GetParam(); + std::vector golden_gain_map = + GoldenTestGainMap(!params.icc_data.empty(), params.has_color_encoding); + + JxlGainMapBundle orig_bundle; + // Initialize the bundle with some test data + orig_bundle.jhgm_version = 0; + const char* metadata_str = + "placeholder gain map metadata, fill with actual example after (ISO " + "21496-1) is finalized"; + std::vector gain_map_metadata(metadata_str, + metadata_str + strlen(metadata_str)); + orig_bundle.gain_map_metadata_size = gain_map_metadata.size(); + orig_bundle.gain_map_metadata = gain_map_metadata.data(); + + // Use the ICC profile from the parameter + orig_bundle.has_color_encoding = TO_JXL_BOOL(params.has_color_encoding); + if (orig_bundle.has_color_encoding) { + JxlColorEncoding color_encoding = {}; + JxlColorEncodingSetToLinearSRGB(&color_encoding, /*is_gray=*/JXL_FALSE); + orig_bundle.color_encoding = color_encoding; + } + + std::vector alt_icc(params.icc_data.begin(), params.icc_data.end()); + orig_bundle.alt_icc = alt_icc.data(); + orig_bundle.alt_icc_size = alt_icc.size(); + + const char* gain_map_str = + "placeholder for an actual naked JPEG XL codestream"; + std::vector gain_map(gain_map_str, + gain_map_str + strlen(gain_map_str)); + orig_bundle.gain_map_size = gain_map.size(); + orig_bundle.gain_map = gain_map.data(); + + ASSERT_TRUE(JxlGainMapGetBundleSize(&orig_bundle, &bundle_size)); + EXPECT_EQ(bundle_size, golden_gain_map.size()); + + std::vector buffer(bundle_size); + size_t bytes_written; + ASSERT_TRUE(JxlGainMapWriteBundle(&orig_bundle, buffer.data(), buffer.size(), + &bytes_written)); + EXPECT_EQ(bytes_written, bundle_size); + EXPECT_EQ(buffer[0], orig_bundle.jhgm_version); + EXPECT_EQ(buffer.size(), golden_gain_map.size()); + EXPECT_TRUE( + std::equal(buffer.begin(), buffer.end(), golden_gain_map.begin())); + + JxlGainMapBundle output_bundle; + size_t bytes_read; + ASSERT_TRUE(JxlGainMapReadBundle(&output_bundle, buffer.data(), buffer.size(), + &bytes_read)); + + EXPECT_EQ(output_bundle.gain_map_size, orig_bundle.gain_map_size); + EXPECT_EQ(output_bundle.gain_map_metadata_size, + orig_bundle.gain_map_metadata_size); + EXPECT_EQ(output_bundle.alt_icc_size, orig_bundle.alt_icc_size); + EXPECT_EQ(output_bundle.has_color_encoding, params.has_color_encoding); + EXPECT_EQ(output_bundle.jhgm_version, orig_bundle.jhgm_version); + std::vector output_gain_map_metadata( + output_bundle.gain_map_metadata, + output_bundle.gain_map_metadata + output_bundle.gain_map_metadata_size); + std::vector output_alt_icc( + output_bundle.alt_icc, + output_bundle.alt_icc + output_bundle.alt_icc_size); + std::vector output_gain_map( + output_bundle.gain_map, + output_bundle.gain_map + output_bundle.gain_map_size); + EXPECT_TRUE(std::equal(output_gain_map_metadata.begin(), + output_gain_map_metadata.end(), + gain_map_metadata.begin())); + EXPECT_TRUE(std::equal(output_alt_icc.begin(), output_alt_icc.end(), + alt_icc.begin())); + EXPECT_TRUE(std::equal(output_gain_map.begin(), output_gain_map.end(), + gain_map.begin())); +} + +JXL_GTEST_INSTANTIATE_TEST_SUITE_P( + GainMapTestCases, GainMapTest, + ::testing::Values( + GainMapTestParams{true, std::vector()}, + GainMapTestParams{true, test::GetCompressedIccTestProfile()}, + GainMapTestParams{false, test::GetCompressedIccTestProfile()}, + GainMapTestParams{false, std::vector()}), + [](const testing::TestParamInfo& info) { + std::string name = + "HasColorEncoding" + std::to_string(info.param.has_color_encoding); + + name += "ICCSize" + std::to_string(info.param.icc_data.size()); + + return name; + }); + +} // namespace +} // namespace jxl diff --git a/media/libjxl/src/lib/extras/hlg.cc b/media/libjxl/src/lib/extras/hlg.cc index e39a0807f5..d92272052b 100644 --- a/media/libjxl/src/lib/extras/hlg.cc +++ b/media/libjxl/src/lib/extras/hlg.cc @@ -5,9 +5,9 @@ #include "lib/extras/hlg.h" -#include +#include -#include "lib/jxl/enc_color_management.h" +#include namespace jxl { @@ -19,33 +19,34 @@ float GetHlgGamma(const float peak_luminance, const float surround_luminance) { Status HlgOOTF(ImageBundle* ib, const float gamma, ThreadPool* pool) { ColorEncoding linear_rec2020; linear_rec2020.SetColorSpace(ColorSpace::kRGB); - linear_rec2020.primaries = Primaries::k2100; - linear_rec2020.white_point = WhitePoint::kD65; - linear_rec2020.tf.SetTransferFunction(TransferFunction::kLinear); + JXL_RETURN_IF_ERROR(linear_rec2020.SetPrimariesType(Primaries::k2100)); + JXL_RETURN_IF_ERROR(linear_rec2020.SetWhitePointType(WhitePoint::kD65)); + linear_rec2020.Tf().SetTransferFunction(TransferFunction::kLinear); JXL_RETURN_IF_ERROR(linear_rec2020.CreateICC()); - JXL_RETURN_IF_ERROR(ib->TransformTo(linear_rec2020, GetJxlCms(), pool)); + JXL_RETURN_IF_ERROR( + ib->TransformTo(linear_rec2020, *JxlGetDefaultCms(), pool)); - JXL_RETURN_IF_ERROR(RunOnPool( - pool, 0, ib->ysize(), ThreadPool::NoInit, - [&](const int y, const int thread) { - float* const JXL_RESTRICT rows[3] = {ib->color()->PlaneRow(0, y), - ib->color()->PlaneRow(1, y), - ib->color()->PlaneRow(2, y)}; - for (size_t x = 0; x < ib->xsize(); ++x) { - float& red = rows[0][x]; - float& green = rows[1][x]; - float& blue = rows[2][x]; - const float luminance = - 0.2627f * red + 0.6780f * green + 0.0593f * blue; - const float ratio = std::pow(luminance, gamma - 1); - if (std::isfinite(ratio)) { - red *= ratio; - green *= ratio; - blue *= ratio; - } - } - }, - "HlgOOTF")); + const auto process_row = [&](const int y, const int thread) -> Status { + float* const JXL_RESTRICT rows[3] = {ib->color()->PlaneRow(0, y), + ib->color()->PlaneRow(1, y), + ib->color()->PlaneRow(2, y)}; + for (size_t x = 0; x < ib->xsize(); ++x) { + float& red = rows[0][x]; + float& green = rows[1][x]; + float& blue = rows[2][x]; + const float luminance = 0.2627f * red + 0.6780f * green + 0.0593f * blue; + const float ratio = std::pow(luminance, gamma - 1); + if (std::isfinite(ratio)) { + red *= ratio; + green *= ratio; + blue *= ratio; + } + } + return true; + }; + + JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, ib->ysize(), ThreadPool::NoInit, + process_row, "HlgOOTF")); return true; } diff --git a/media/libjxl/src/lib/extras/jpegli_test.cc b/media/libjxl/src/lib/extras/jpegli_test.cc new file mode 100644 index 0000000000..d22afd0e85 --- /dev/null +++ b/media/libjxl/src/lib/extras/jpegli_test.cc @@ -0,0 +1,422 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#if JPEGXL_ENABLE_JPEGLI + +#include "lib/extras/dec/jpegli.h" + +#include +#include + +#include +#include +#include +#include +#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" +#include "lib/extras/enc/jpegli.h" +#include "lib/extras/enc/jpg.h" +#include "lib/extras/packed_image.h" +#include "lib/jxl/base/span.h" +#include "lib/jxl/base/status.h" +#include "lib/jxl/color_encoding_internal.h" +#include "lib/jxl/test_image.h" +#include "lib/jxl/test_utils.h" +#include "lib/jxl/testing.h" + +namespace jxl { +namespace extras { +namespace { + +using ::jxl::test::Butteraugli3Norm; +using ::jxl::test::ButteraugliDistance; +using ::jxl::test::TestImage; + +Status ReadTestImage(const std::string& pathname, PackedPixelFile* ppf) { + 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(Bytes(encoded), color_hints, ppf); +} + +std::vector GetAppData(const std::vector& compressed) { + std::vector result; + size_t pos = 2; // After SOI + while (pos + 4 < compressed.size()) { + if (compressed[pos] != 0xff || compressed[pos + 1] < 0xe0 || + compressed[pos + 1] > 0xf0) { + break; + } + size_t len = (compressed[pos + 2] << 8) + compressed[pos + 3] + 2; + if (pos + len > compressed.size()) { + break; + } + result.insert(result.end(), &compressed[pos], &compressed[pos] + len); + pos += len; + } + return result; +} + +Status DecodeWithLibjpeg(const std::vector& compressed, + PackedPixelFile* ppf, + const JPGDecompressParams* dparams = nullptr) { + return DecodeImageJPG(Bytes(compressed), ColorHints(), ppf, + /*constraints=*/nullptr, dparams); +} + +Status EncodeWithLibjpeg(const PackedPixelFile& ppf, int quality, + std::vector* compressed) { + std::unique_ptr encoder = GetJPEGEncoder(); + encoder->SetOption("q", std::to_string(quality)); + EncodedImage encoded; + JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded, nullptr)); + JXL_RETURN_IF_ERROR(!encoded.bitstreams.empty()); + *compressed = std::move(encoded.bitstreams[0]); + return true; +} + +std::string Description(const JxlColorEncoding& color_encoding) { + ColorEncoding c_enc; + EXPECT_TRUE(c_enc.FromExternal(color_encoding)); + return Description(c_enc); +} + +float BitsPerPixel(const PackedPixelFile& ppf, + const std::vector& compressed) { + const size_t num_pixels = ppf.info.xsize * ppf.info.ysize; + return compressed.size() * 8.0 / num_pixels; +} + +TEST(JpegliTest, JpegliSRGBDecodeTest) { + TEST_LIBJPEG_SUPPORT(); + std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm"; + PackedPixelFile ppf0; + ASSERT_TRUE(ReadTestImage(testimage, &ppf0)); + EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf0.color_encoding)); + EXPECT_EQ(8, ppf0.info.bits_per_sample); + + std::vector compressed; + ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed)); + + PackedPixelFile ppf1; + ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf1)); + PackedPixelFile ppf2; + JpegDecompressParams dparams; + ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf2)); + EXPECT_LT(ButteraugliDistance(ppf0, ppf2), ButteraugliDistance(ppf0, ppf1)); +} + +TEST(JpegliTest, JpegliGrayscaleDecodeTest) { + TEST_LIBJPEG_SUPPORT(); + std::string testimage = "jxl/flower/flower_small.g.depth8.pgm"; + PackedPixelFile ppf0; + ASSERT_TRUE(ReadTestImage(testimage, &ppf0)); + EXPECT_EQ("Gra_D65_Rel_SRG", Description(ppf0.color_encoding)); + EXPECT_EQ(8, ppf0.info.bits_per_sample); + + std::vector compressed; + ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed)); + + PackedPixelFile ppf1; + ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf1)); + PackedPixelFile ppf2; + JpegDecompressParams dparams; + ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf2)); + EXPECT_LT(ButteraugliDistance(ppf0, ppf2), ButteraugliDistance(ppf0, ppf1)); +} + +TEST(JpegliTest, JpegliXYBEncodeTest) { + TEST_LIBJPEG_SUPPORT(); + std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm"; + PackedPixelFile ppf_in; + ASSERT_TRUE(ReadTestImage(testimage, &ppf_in)); + EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding)); + EXPECT_EQ(8, ppf_in.info.bits_per_sample); + + std::vector compressed; + JpegSettings settings; + settings.xyb = true; + ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + + PackedPixelFile ppf_out; + ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out)); + EXPECT_SLIGHTLY_BELOW(BitsPerPixel(ppf_in, compressed), 1.45f); + EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(ppf_in, ppf_out), 1.32f); +} + +TEST(JpegliTest, JpegliDecodeTestLargeSmoothArea) { + TEST_LIBJPEG_SUPPORT(); + TestImage t; + const size_t xsize = 2070; + const size_t ysize = 1063; + ASSERT_TRUE(t.SetDimensions(xsize, ysize)); + ASSERT_TRUE(t.SetChannels(3)); + t.SetAllBitDepths(8).SetEndianness(JXL_NATIVE_ENDIAN); + JXL_TEST_ASSIGN_OR_DIE(TestImage::Frame frame, t.AddFrame()); + frame.RandomFill(); + // Create a large smooth area in the top half of the image. This is to test + // that the bias statistics calculation can handle many blocks with all-zero + // AC coefficients. + for (size_t y = 0; y < ysize / 2; ++y) { + for (size_t x = 0; x < xsize; ++x) { + for (size_t c = 0; c < 3; ++c) { + ASSERT_TRUE(frame.SetValue(y, x, c, 0.5f)); + } + } + } + const PackedPixelFile& ppf0 = t.ppf(); + + std::vector compressed; + ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed)); + + PackedPixelFile ppf1; + JpegDecompressParams dparams; + ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf1)); + EXPECT_LT(ButteraugliDistance(ppf0, ppf1), 3.0f); +} + +TEST(JpegliTest, JpegliYUVEncodeTest) { + TEST_LIBJPEG_SUPPORT(); + std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm"; + PackedPixelFile ppf_in; + ASSERT_TRUE(ReadTestImage(testimage, &ppf_in)); + EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding)); + EXPECT_EQ(8, ppf_in.info.bits_per_sample); + + std::vector compressed; + JpegSettings settings; + settings.xyb = false; + ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + + PackedPixelFile ppf_out; + ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out)); + EXPECT_SLIGHTLY_BELOW(BitsPerPixel(ppf_in, compressed), 1.7f); + EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(ppf_in, ppf_out), 1.32f); +} + +TEST(JpegliTest, JpegliYUVChromaSubsamplingEncodeTest) { + TEST_LIBJPEG_SUPPORT(); + std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm"; + PackedPixelFile ppf_in; + ASSERT_TRUE(ReadTestImage(testimage, &ppf_in)); + EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding)); + EXPECT_EQ(8, ppf_in.info.bits_per_sample); + + std::vector compressed; + JpegSettings settings; + for (const char* sampling : {"440", "422", "420"}) { + settings.xyb = false; + settings.chroma_subsampling = std::string(sampling); + ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + + PackedPixelFile ppf_out; + ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out)); + EXPECT_LE(BitsPerPixel(ppf_in, compressed), 1.55f); + EXPECT_LE(ButteraugliDistance(ppf_in, ppf_out), 1.82f); + } +} + +TEST(JpegliTest, JpegliYUVEncodeTestNoAq) { + TEST_LIBJPEG_SUPPORT(); + std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm"; + PackedPixelFile ppf_in; + ASSERT_TRUE(ReadTestImage(testimage, &ppf_in)); + EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding)); + EXPECT_EQ(8, ppf_in.info.bits_per_sample); + + std::vector compressed; + JpegSettings settings; + settings.xyb = false; + settings.use_adaptive_quantization = false; + ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + + PackedPixelFile ppf_out; + ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out)); + EXPECT_SLIGHTLY_BELOW(BitsPerPixel(ppf_in, compressed), 1.85f); + EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(ppf_in, ppf_out), 1.25f); +} + +TEST(JpegliTest, JpegliHDRRoundtripTest) { + std::string testimage = "jxl/hdr_room.png"; + PackedPixelFile ppf_in; + ASSERT_TRUE(ReadTestImage(testimage, &ppf_in)); + EXPECT_EQ("Rec2100HLG", Description(ppf_in.color_encoding)); + EXPECT_EQ(16, ppf_in.info.bits_per_sample); + + std::vector compressed; + JpegSettings settings; + settings.xyb = false; + ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + + PackedPixelFile ppf_out; + JpegDecompressParams dparams; + dparams.output_data_type = JXL_TYPE_UINT16; + ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf_out)); + EXPECT_SLIGHTLY_BELOW(BitsPerPixel(ppf_in, compressed), 2.95f); + EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(ppf_in, ppf_out), 1.05f); +} + +TEST(JpegliTest, JpegliSetAppData) { + std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm"; + PackedPixelFile ppf_in; + ASSERT_TRUE(ReadTestImage(testimage, &ppf_in)); + EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding)); + EXPECT_EQ(8, ppf_in.info.bits_per_sample); + + std::vector compressed; + JpegSettings settings; + settings.app_data = {0xff, 0xe3, 0, 4, 0, 1}; + EXPECT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + EXPECT_EQ(settings.app_data, GetAppData(compressed)); + + settings.app_data = {0xff, 0xe3, 0, 6, 0, 1, 2, 3, 0xff, 0xef, 0, 4, 0, 1}; + EXPECT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + EXPECT_EQ(settings.app_data, GetAppData(compressed)); + + settings.xyb = true; + EXPECT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + EXPECT_EQ(0, memcmp(settings.app_data.data(), GetAppData(compressed).data(), + settings.app_data.size())); + + settings.xyb = false; + settings.app_data = {0}; + EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + + settings.app_data = {0xff, 0xe0}; + EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + + settings.app_data = {0xff, 0xe0, 0, 2}; + EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + + settings.app_data = {0xff, 0xeb, 0, 4, 0}; + EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + + settings.app_data = {0xff, 0xeb, 0, 4, 0, 1, 2, 3}; + EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + + settings.app_data = {0xff, 0xab, 0, 4, 0, 1}; + EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + + settings.xyb = false; + settings.app_data = { + 0xff, 0xeb, 0, 4, 0, 1, // + 0xff, 0xe2, 0, 20, 0x49, 0x43, 0x43, 0x5F, 0x50, // + 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00, 0, 1, // + 0, 0, 0, 0, // + }; + EXPECT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); + EXPECT_EQ(settings.app_data, GetAppData(compressed)); + + settings.xyb = true; + EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed)); +} + +struct TestConfig { + int num_colors; + int passes; + int dither; +}; + +class JpegliColorQuantTestParam : public ::testing::TestWithParam { +}; + +TEST_P(JpegliColorQuantTestParam, JpegliColorQuantizeTest) { + TEST_LIBJPEG_SUPPORT(); + TestConfig config = GetParam(); + std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm"; + PackedPixelFile ppf0; + ASSERT_TRUE(ReadTestImage(testimage, &ppf0)); + EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf0.color_encoding)); + EXPECT_EQ(8, ppf0.info.bits_per_sample); + + std::vector compressed; + ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed)); + + PackedPixelFile ppf1; + JPGDecompressParams dparams1; + dparams1.two_pass_quant = (config.passes == 2); + dparams1.num_colors = config.num_colors; + dparams1.dither_mode = config.dither; + ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf1, &dparams1)); + + PackedPixelFile ppf2; + JpegDecompressParams dparams2; + dparams2.two_pass_quant = (config.passes == 2); + dparams2.num_colors = config.num_colors; + dparams2.dither_mode = config.dither; + ASSERT_TRUE(DecodeJpeg(compressed, dparams2, nullptr, &ppf2)); + + double dist1 = Butteraugli3Norm(ppf0, ppf1); + double dist2 = Butteraugli3Norm(ppf0, ppf2); + printf("distance: %f vs %f\n", dist2, dist1); + if (config.passes == 1) { + if (config.num_colors == 16 && config.dither == 2) { + // TODO(szabadka) Fix this case. + EXPECT_LT(dist2, dist1 * 1.5); + } else { + EXPECT_LT(dist2, dist1 * 1.05); + } + } else if (config.num_colors > 64) { + // TODO(szabadka) Fix 2pass quantization for <= 64 colors. + EXPECT_LT(dist2, dist1 * 1.1); + } else if (config.num_colors > 32) { + EXPECT_LT(dist2, dist1 * 1.2); + } else { + EXPECT_LT(dist2, dist1 * 1.7); + } +} + +std::vector GenerateTests() { + std::vector all_tests; + for (int num_colors = 8; num_colors <= 256; num_colors *= 2) { + for (int passes = 1; passes <= 2; ++passes) { + for (int dither = 0; dither < 3; dither += passes) { + TestConfig config; + config.num_colors = num_colors; + config.passes = passes; + config.dither = dither; + all_tests.push_back(config); + } + } + } + return all_tests; +} + +std::ostream& operator<<(std::ostream& os, const TestConfig& c) { + static constexpr const char* kDitherModeStr[] = {"No", "Ordered", "FS"}; + os << c.passes << "pass"; + os << c.num_colors << "colors"; + os << kDitherModeStr[c.dither] << "dither"; + return os; +} + +std::string TestDescription(const testing::TestParamInfo& info) { + std::stringstream name; + name << info.param; + return name.str(); +} + +JXL_GTEST_INSTANTIATE_TEST_SUITE_P(JpegliColorQuantTest, + JpegliColorQuantTestParam, + testing::ValuesIn(GenerateTests()), + TestDescription); + +} // namespace +} // namespace extras +} // namespace jxl +#endif // JPEGXL_ENABLE_JPEGLI diff --git a/media/libjxl/src/lib/jxl/enc_butteraugli_pnorm.cc b/media/libjxl/src/lib/extras/metrics.cc similarity index 79% rename from media/libjxl/src/lib/jxl/enc_butteraugli_pnorm.cc rename to media/libjxl/src/lib/extras/metrics.cc index fe5629dcda..55c1130944 100644 --- a/media/libjxl/src/lib/jxl/enc_butteraugli_pnorm.cc +++ b/media/libjxl/src/lib/extras/metrics.cc @@ -3,7 +3,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -#include "lib/jxl/enc_butteraugli_pnorm.h" +#include "lib/extras/metrics.h" #include #include @@ -11,12 +11,12 @@ #include #undef HWY_TARGET_INCLUDE -#define HWY_TARGET_INCLUDE "lib/jxl/enc_butteraugli_pnorm.cc" +#define HWY_TARGET_INCLUDE "lib/extras/metrics.cc" #include #include #include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/profiler.h" +#include "lib/jxl/base/rect.h" #include "lib/jxl/base/status.h" #include "lib/jxl/color_encoding_internal.h" HWY_BEFORE_NAMESPACE(); @@ -31,8 +31,6 @@ using hwy::HWY_NAMESPACE::Rebind; double ComputeDistanceP(const ImageF& distmap, const ButteraugliParams& params, double p) { - PROFILER_FUNC; - const double onePerPixels = 1.0 / (distmap.ysize() * distmap.xsize()); if (std::abs(p - 3.0) < 1E-6) { double sum1[3] = {0.0}; @@ -45,7 +43,7 @@ double ComputeDistanceP(const ImageF& distmap, const ButteraugliParams& params, using T = float; #endif const HWY_FULL(T) d; - constexpr size_t N = MaxLanes(HWY_FULL(T)()); + constexpr size_t N = MaxLanes(d); // Manually aligned storage to avoid asan crash on clang-7 due to // unaligned spill. HWY_ALIGN T sum_totals0[N] = {0}; @@ -126,33 +124,34 @@ double ComputeDistanceP(const ImageF& distmap, const ButteraugliParams& params, } } -// TODO(lode): take alpha into account when needed -double ComputeDistance2(const ImageBundle& ib1, const ImageBundle& ib2, - const JxlCmsInterface& cms) { - PROFILER_FUNC; +void ComputeSumOfSquares(const ImageBundle& ib1, const ImageBundle& ib2, + const JxlCmsInterface& cms, double sum_of_squares[3]) { + sum_of_squares[0] = sum_of_squares[1] = sum_of_squares[2] = + std::numeric_limits::max(); // Convert to sRGB - closer to perception than linear. const Image3F* srgb1 = &ib1.color(); Image3F copy1; if (!ib1.IsSRGB()) { - JXL_CHECK( - ib1.CopyTo(Rect(ib1), ColorEncoding::SRGB(ib1.IsGray()), cms, ©1)); + if (!ib1.CopyTo(Rect(ib1), ColorEncoding::SRGB(ib1.IsGray()), cms, ©1)) + return; srgb1 = ©1; } const Image3F* srgb2 = &ib2.color(); Image3F copy2; if (!ib2.IsSRGB()) { - JXL_CHECK( - ib2.CopyTo(Rect(ib2), ColorEncoding::SRGB(ib2.IsGray()), cms, ©2)); + if (!ib2.CopyTo(Rect(ib2), ColorEncoding::SRGB(ib2.IsGray()), cms, ©2)) + return; srgb2 = ©2; } - JXL_CHECK(SameSize(*srgb1, *srgb2)); + if (!SameSize(*srgb1, *srgb2)) return; + + sum_of_squares[0] = sum_of_squares[1] = sum_of_squares[2] = 0.0; // TODO(veluca): SIMD. float yuvmatrix[3][3] = {{0.299, 0.587, 0.114}, {-0.14713, -0.28886, 0.436}, {0.615, -0.51499, -0.10001}}; - double sum_of_squares[3] = {}; for (size_t y = 0; y < srgb1->ysize(); ++y) { const float* JXL_RESTRICT row1[3]; const float* JXL_RESTRICT row2[3]; @@ -177,15 +176,6 @@ double ComputeDistance2(const ImageBundle& ib1, const ImageBundle& ib2, } } } - // Weighted PSNR as in JPEG-XL: chroma counts 1/8. - const float weights[3] = {6.0f / 8, 1.0f / 8, 1.0f / 8}; - // Avoid squaring the weight - 1/64 is too extreme. - double norm = 0; - for (size_t i = 0; i < 3; i++) { - norm += std::sqrt(sum_of_squares[i]) * weights[i]; - } - // This function returns distance *squared*. - return norm * norm; } // NOLINTNEXTLINE(google-readability-namespace-comments) @@ -201,10 +191,38 @@ double ComputeDistanceP(const ImageF& distmap, const ButteraugliParams& params, return HWY_DYNAMIC_DISPATCH(ComputeDistanceP)(distmap, params, p); } -HWY_EXPORT(ComputeDistance2); +HWY_EXPORT(ComputeSumOfSquares); + double ComputeDistance2(const ImageBundle& ib1, const ImageBundle& ib2, const JxlCmsInterface& cms) { - return HWY_DYNAMIC_DISPATCH(ComputeDistance2)(ib1, ib2, cms); + double sum_of_squares[3] = {}; + HWY_DYNAMIC_DISPATCH(ComputeSumOfSquares)(ib1, ib2, cms, sum_of_squares); + // Weighted PSNR as in JPEG-XL: chroma counts 1/8. + const float weights[3] = {6.0f / 8, 1.0f / 8, 1.0f / 8}; + // Avoid squaring the weight - 1/64 is too extreme. + double norm = 0; + for (size_t i = 0; i < 3; i++) { + norm += std::sqrt(sum_of_squares[i]) * weights[i]; + } + // This function returns distance *squared*. + return norm * norm; +} + +double ComputePSNR(const ImageBundle& ib1, const ImageBundle& ib2, + const JxlCmsInterface& cms) { + if (!SameSize(ib1, ib2)) return 0.0; + double sum_of_squares[3] = {}; + HWY_DYNAMIC_DISPATCH(ComputeSumOfSquares)(ib1, ib2, cms, sum_of_squares); + constexpr double kChannelWeights[3] = {6.0 / 8, 1.0 / 8, 1.0 / 8}; + double avg_psnr = 0; + const size_t input_pixels = ib1.xsize() * ib1.ysize(); + for (int i = 0; i < 3; ++i) { + const double rmse = std::sqrt(sum_of_squares[i] / input_pixels); + const double psnr = + sum_of_squares[i] == 0 ? 99.99 : (20 * std::log10(1 / rmse)); + avg_psnr += kChannelWeights[i] * psnr; + } + return avg_psnr; } } // namespace jxl diff --git a/media/libjxl/src/lib/jxl/enc_butteraugli_pnorm.h b/media/libjxl/src/lib/extras/metrics.h similarity index 74% rename from media/libjxl/src/lib/jxl/enc_butteraugli_pnorm.h rename to media/libjxl/src/lib/extras/metrics.h index cf6872e5d0..87a69a99ce 100644 --- a/media/libjxl/src/lib/jxl/enc_butteraugli_pnorm.h +++ b/media/libjxl/src/lib/extras/metrics.h @@ -3,8 +3,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -#ifndef LIB_JXL_ENC_BUTTERAUGLI_PNORM_H_ -#define LIB_JXL_ENC_BUTTERAUGLI_PNORM_H_ +#ifndef LIB_EXTRAS_METRICS_H_ +#define LIB_EXTRAS_METRICS_H_ #include @@ -20,6 +20,9 @@ double ComputeDistanceP(const ImageF& distmap, const ButteraugliParams& params, double ComputeDistance2(const ImageBundle& ib1, const ImageBundle& ib2, const JxlCmsInterface& cms); +double ComputePSNR(const ImageBundle& ib1, const ImageBundle& ib2, + const JxlCmsInterface& cms); + } // namespace jxl -#endif // LIB_JXL_ENC_BUTTERAUGLI_PNORM_H_ +#endif // LIB_EXTRAS_METRICS_H_ diff --git a/media/libjxl/src/lib/extras/mmap.cc b/media/libjxl/src/lib/extras/mmap.cc new file mode 100644 index 0000000000..9f5bba97ed --- /dev/null +++ b/media/libjxl/src/lib/extras/mmap.cc @@ -0,0 +1,149 @@ +// 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 "mmap.h" + +#include +#include + +#include "lib/jxl/base/common.h" + +#if defined(__unix__) || defined(__unix) || \ + defined(__APPLE__) && defined(__MACH__) +#include +#include +#include + +namespace jxl { + +struct MemoryMappedFileImpl { + static StatusOr> Init( + const char* path) { + auto f = make_unique(); + f->fd = open(path, O_RDONLY); + if (f->fd == -1) { + return JXL_FAILURE("Cannot open file %s", path); + } + f->mmap_len = lseek(f->fd, 0, SEEK_END); + lseek(f->fd, 0, SEEK_SET); + + f->ptr = mmap(nullptr, f->mmap_len, PROT_READ, MAP_SHARED, f->fd, 0); + if (f->ptr == MAP_FAILED) { + return JXL_FAILURE("mmap failure"); + } + return f; + } + + const uint8_t* data() const { return reinterpret_cast(ptr); } + size_t size() const { return mmap_len; } + + ~MemoryMappedFileImpl() { + if (fd != -1) { + close(fd); + } + if (ptr != nullptr) { + munmap(ptr, mmap_len); + } + } + + int fd = -1; + size_t mmap_len = 0; + void* ptr = nullptr; +}; + +} // namespace jxl + +#elif defined(_WIN32) +#include +#include + +namespace { + +struct HandleDeleter { + void operator()(const HANDLE handle) const { + if (handle != INVALID_HANDLE_VALUE) { + CloseHandle(handle); + } + } +}; +using HandleUniquePtr = + std::unique_ptr::type, HandleDeleter>; + +} // namespace + +namespace jxl { + +struct MemoryMappedFileImpl { + static StatusOr> Init( + const char* path) { + auto f = make_unique(); + std::wstring stemp = std::wstring(path, path + strlen(path)); + f->handle.reset(CreateFileW(stemp.c_str(), GENERIC_READ, FILE_SHARE_READ, + nullptr, OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, nullptr)); + if (f->handle.get() == INVALID_HANDLE_VALUE) { + return JXL_FAILURE("Cannot open file %s", path); + } + if (!GetFileSizeEx(f->handle.get(), &f->fsize)) { + return JXL_FAILURE("Cannot get file size (%s)", path); + } + f->handle_mapping.reset(CreateFileMappingW(f->handle.get(), nullptr, + PAGE_READONLY, 0, 0, nullptr)); + if (f->handle_mapping == nullptr) { + return JXL_FAILURE("Cannot create memory mapping (%s)", path); + } + f->ptr = MapViewOfFile(f->handle_mapping.get(), FILE_MAP_READ, 0, 0, 0); + return f; + } + + ~MemoryMappedFileImpl() { UnmapViewOfFile(ptr); } + + const uint8_t* data() const { return reinterpret_cast(ptr); } + size_t size() const { return fsize.QuadPart; } + + HandleUniquePtr handle; + HandleUniquePtr handle_mapping; + LARGE_INTEGER fsize; + void* ptr = nullptr; +}; + +} // namespace jxl + +#else + +namespace jxl { + +struct MemoryMappedFileImpl { + static StatusOr> Init( + const char* path) { + return JXL_FAILURE("Memory mapping not supported on this system"); + } + + const uint8_t* data() const { return nullptr; } + size_t size() const { return 0; } +}; + +} // namespace jxl + +#endif + +namespace jxl { + +StatusOr MemoryMappedFile::Init(const char* path) { + JXL_ASSIGN_OR_RETURN(auto mmf, MemoryMappedFileImpl::Init(path)); + MemoryMappedFile ret; + ret.impl_ = std::move(mmf); + return ret; +} + +MemoryMappedFile::MemoryMappedFile() = default; +MemoryMappedFile::~MemoryMappedFile() = default; +MemoryMappedFile::MemoryMappedFile(MemoryMappedFile&&) noexcept = default; +MemoryMappedFile& MemoryMappedFile::operator=(MemoryMappedFile&&) noexcept = + default; + +const uint8_t* MemoryMappedFile::data() const { return impl_->data(); } +size_t MemoryMappedFile::size() const { return impl_->size(); } +} // namespace jxl diff --git a/media/libjxl/src/lib/extras/mmap.h b/media/libjxl/src/lib/extras/mmap.h new file mode 100644 index 0000000000..60cc215993 --- /dev/null +++ b/media/libjxl/src/lib/extras/mmap.h @@ -0,0 +1,31 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_EXTRAS_MMAP_H_ +#define LIB_EXTRAS_MMAP_H_ + +#include + +#include "lib/jxl/base/status.h" + +namespace jxl { +struct MemoryMappedFileImpl; + +class MemoryMappedFile { + public: + static StatusOr Init(const char* path); + const uint8_t* data() const; + size_t size() const; + MemoryMappedFile(); // NOLINT + ~MemoryMappedFile(); // NOLINT + MemoryMappedFile(MemoryMappedFile&&) noexcept; // NOLINT + MemoryMappedFile& operator=(MemoryMappedFile&&) noexcept; // NOLINT + + private: + std::unique_ptr impl_; +}; +} // namespace jxl + +#endif diff --git a/media/libjxl/src/lib/extras/packed_image.h b/media/libjxl/src/lib/extras/packed_image.h index 1296472101..8a88af6188 100644 --- a/media/libjxl/src/lib/extras/packed_image.h +++ b/media/libjxl/src/lib/extras/packed_image.h @@ -9,20 +9,24 @@ // Helper class for storing external (int or float, interleaved) images. This is // the common format used by other libraries and in the libjxl API. -#include -#include -#include -#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include #include #include #include -#include "jxl/codestream_header.h" -#include "jxl/encode.h" -#include "jxl/types.h" -#include "lib/jxl/common.h" +#include "lib/jxl/base/byte_order.h" +#include "lib/jxl/base/common.h" +#include "lib/jxl/base/status.h" namespace jxl { namespace extras { @@ -30,12 +34,36 @@ namespace extras { // Class representing an interleaved image with a bunch of channels. class PackedImage { public: - PackedImage(size_t xsize, size_t ysize, const JxlPixelFormat& format) - : PackedImage(xsize, ysize, format, CalcStride(format, xsize)) {} + static StatusOr Create(size_t xsize, size_t ysize, + const JxlPixelFormat& format) { + PackedImage image(xsize, ysize, format, CalcStride(format, xsize)); + if (!image.pixels()) { + // TODO(szabadka): use specialized OOM error code + return JXL_FAILURE("Failed to allocate memory for image"); + } + return image; + } + + PackedImage Copy() const { + PackedImage copy(xsize, ysize, format, CalcStride(format, xsize)); + memcpy(reinterpret_cast(copy.pixels()), + reinterpret_cast(pixels()), pixels_size); + return copy; + } // The interleaved pixels as defined in the storage format. void* pixels() const { return pixels_.get(); } + uint8_t* pixels(size_t y, size_t x, size_t c) const { + return (reinterpret_cast(pixels_.get()) + y * stride + + x * pixel_stride_ + c * bytes_per_channel_); + } + + const uint8_t* const_pixels(size_t y, size_t x, size_t c) const { + return (reinterpret_cast(pixels_.get()) + y * stride + + x * pixel_stride_ + c * bytes_per_channel_); + } + // The image size in pixels. size_t xsize; size_t ysize; @@ -47,9 +75,15 @@ class PackedImage { JxlPixelFormat format; size_t pixels_size; - size_t pixel_stride() const { - return (BitsPerChannel(format.data_type) * format.num_channels / - jxl::kBitsPerByte); + size_t pixel_stride() const { return pixel_stride_; } + + static Status ValidateDataType(JxlDataType data_type) { + if ((data_type != JXL_TYPE_UINT8) && (data_type != JXL_TYPE_UINT16) && + (data_type != JXL_TYPE_FLOAT) && (data_type != JXL_TYPE_FLOAT16)) { + return JXL_FAILURE("Unhandled data type: %d", + static_cast(data_type)); + } + return true; } static size_t BitsPerChannel(JxlDataType data_type) { @@ -63,7 +97,55 @@ class PackedImage { case JXL_TYPE_FLOAT16: return 16; default: - JXL_ABORT("Unhandled JxlDataType"); + JXL_DEBUG_ABORT("Unreachable"); + return 0; + } + } + + float GetPixelValue(size_t y, size_t x, size_t c) const { + const uint8_t* data = const_pixels(y, x, c); + switch (format.data_type) { + case JXL_TYPE_UINT8: + return data[0] * (1.0f / 255); + case JXL_TYPE_UINT16: { + uint16_t val; + memcpy(&val, data, 2); + return (swap_endianness_ ? JXL_BSWAP16(val) : val) * (1.0f / 65535); + } + case JXL_TYPE_FLOAT: { + float val; + memcpy(&val, data, 4); + return swap_endianness_ ? BSwapFloat(val) : val; + } + default: + JXL_DEBUG_ABORT("Unreachable"); + return 0.0f; + } + } + + void SetPixelValue(size_t y, size_t x, size_t c, float val) const { + uint8_t* data = pixels(y, x, c); + switch (format.data_type) { + case JXL_TYPE_UINT8: + data[0] = Clamp1(std::round(val * 255), 0.0f, 255.0f); + break; + case JXL_TYPE_UINT16: { + uint16_t val16 = Clamp1(std::round(val * 65535), 0.0f, 65535.0f); + if (swap_endianness_) { + val16 = JXL_BSWAP16(val16); + } + memcpy(data, &val16, 2); + break; + } + case JXL_TYPE_FLOAT: { + if (swap_endianness_) { + val = BSwapFloat(val); + } + memcpy(data, &val, 4); + break; + } + default: + JXL_DEBUG_ABORT("Unreachable"); } } @@ -75,7 +157,11 @@ class PackedImage { stride(stride), format(format), pixels_size(ysize * stride), - pixels_(malloc(std::max(1, pixels_size)), free) {} + pixels_(malloc(std::max(1, pixels_size)), free) { + bytes_per_channel_ = BitsPerChannel(format.data_type) / jxl::kBitsPerByte; + pixel_stride_ = format.num_channels * bytes_per_channel_; + swap_endianness_ = SwapEndianness(format.endianness); + } static size_t CalcStride(const JxlPixelFormat& format, size_t xsize) { size_t stride = xsize * (BitsPerChannel(format.data_type) * @@ -86,6 +172,9 @@ class PackedImage { return stride; } + size_t bytes_per_channel_; + size_t pixel_stride_; + bool swap_endianness_; std::unique_ptr pixels_; }; @@ -95,8 +184,28 @@ class PackedImage { // as all other frames in the same image. class PackedFrame { public: - template - explicit PackedFrame(Args&&... args) : color(std::forward(args)...) {} + explicit PackedFrame(PackedImage&& image) : color(std::move(image)) {} + + static StatusOr Create(size_t xsize, size_t ysize, + const JxlPixelFormat& format) { + JXL_ASSIGN_OR_RETURN(PackedImage image, + PackedImage::Create(xsize, ysize, format)); + PackedFrame frame(std::move(image)); + return frame; + } + + StatusOr Copy() const { + JXL_ASSIGN_OR_RETURN( + PackedFrame copy, + PackedFrame::Create(color.xsize, color.ysize, color.format)); + copy.frame_info = frame_info; + copy.name = name; + copy.color = color.Copy(); + for (const auto& ec : extra_channels) { + copy.extra_channels.emplace_back(ec.Copy()); + } + return copy; + } // The Frame metadata. JxlFrameHeader frame_info = {}; @@ -108,40 +217,89 @@ class PackedFrame { std::vector extra_channels; }; +class ChunkedPackedFrame { + public: + ChunkedPackedFrame( + size_t xsize, size_t ysize, + std::function get_input_source) + : xsize(xsize), + ysize(ysize), + get_input_source_(std::move(get_input_source)) { + const auto input_source = get_input_source_(); + input_source.get_color_channels_pixel_format(input_source.opaque, &format); + } + + JxlChunkedFrameInputSource GetInputSource() { return get_input_source_(); } + + // The Frame metadata. + JxlFrameHeader frame_info = {}; + std::string name; + + size_t xsize; + size_t ysize; + JxlPixelFormat format; + + private: + std::function get_input_source_; +}; + // Optional metadata associated with a file class PackedMetadata { public: std::vector exif; std::vector iptc; + std::vector jhgm; std::vector jumbf; std::vector xmp; }; +// The extra channel metadata information. +struct PackedExtraChannel { + JxlExtraChannelInfo ec_info; + size_t index; + std::string name; +}; + // Helper class representing a JXL image file as decoded to pixels from the API. class PackedPixelFile { public: JxlBasicInfo info = {}; - // The extra channel metadata information. - struct PackedExtraChannel { - JxlExtraChannelInfo ec_info; - size_t index; - std::string name; - }; std::vector extra_channels_info; // Color information of the decoded pixels. - // If the icc is empty, the JxlColorEncoding should be used instead. - std::vector icc; + // `primary_color_representation` indicates whether `color_encoding` or `icc` + // is the “authoritative” encoding of the colorspace, as opposed to a fallback + // encoding. For example, if `color_encoding` is the primary one, as would + // occur when decoding a jxl file with such a representation, then `enc/jxl` + // will use it and ignore the ICC profile, whereas `enc/png` will include the + // ICC profile for compatibility. + // If `icc` is the primary representation, `enc/jxl` will preserve it when + // compressing losslessly, but *may* encode it as a color_encoding when + // compressing lossily. + enum { + kColorEncodingIsPrimary, + kIccIsPrimary + } primary_color_representation = kColorEncodingIsPrimary; JxlColorEncoding color_encoding = {}; + std::vector icc; // The icc profile of the original image. std::vector orig_icc; + JxlBitDepth input_bitdepth = {JXL_BIT_DEPTH_FROM_PIXEL_FORMAT, 0, 0}; + 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(); + } + size_t xsize() const { return info.xsize; } + size_t ysize() const { return info.ysize; } }; } // namespace extras diff --git a/media/libjxl/src/lib/extras/packed_image_convert.cc b/media/libjxl/src/lib/extras/packed_image_convert.cc index dcdd12a673..7bdb3cdf64 100644 --- a/media/libjxl/src/lib/extras/packed_image_convert.cc +++ b/media/libjxl/src/lib/extras/packed_image_convert.cc @@ -5,41 +5,52 @@ #include "lib/extras/packed_image_convert.h" -#include +#include +#include +#include +#include -#include "jxl/color_encoding.h" -#include "jxl/types.h" +#include +#include + +#include "lib/extras/packed_image.h" +#include "lib/jxl/base/rect.h" #include "lib/jxl/base/status.h" #include "lib/jxl/color_encoding_internal.h" -#include "lib/jxl/color_management.h" #include "lib/jxl/dec_external_image.h" -#include "lib/jxl/enc_color_management.h" #include "lib/jxl/enc_external_image.h" #include "lib/jxl/enc_image_bundle.h" +#include "lib/jxl/luminance.h" namespace jxl { namespace extras { Status ConvertPackedFrameToImageBundle(const JxlBasicInfo& info, + const JxlBitDepth& input_bitdepth, const PackedFrame& frame, const CodecInOut& io, ThreadPool* pool, ImageBundle* bundle) { - JXL_ASSERT(frame.color.pixels() != nullptr); - const bool float_in = frame.color.format.data_type == JXL_TYPE_FLOAT16 || - frame.color.format.data_type == JXL_TYPE_FLOAT; - size_t frame_bits_per_sample = - float_in ? PackedImage::BitsPerChannel(frame.color.format.data_type) - : info.bits_per_sample; - JXL_ASSERT(frame_bits_per_sample != 0); + JxlMemoryManager* memory_manager = io.memory_manager; + JXL_ENSURE(frame.color.pixels() != nullptr); + size_t frame_bits_per_sample; + if (input_bitdepth.type == JXL_BIT_DEPTH_FROM_PIXEL_FORMAT) { + JXL_RETURN_IF_ERROR( + PackedImage::ValidateDataType(frame.color.format.data_type)); + frame_bits_per_sample = + PackedImage::BitsPerChannel(frame.color.format.data_type); + } else { + frame_bits_per_sample = info.bits_per_sample; + } + JXL_ENSURE(frame_bits_per_sample != 0); // It is ok for the frame.color.format.num_channels to not match the // number of channels on the image. - JXL_ASSERT(1 <= frame.color.format.num_channels && + JXL_ENSURE(1 <= frame.color.format.num_channels && frame.color.format.num_channels <= 4); const Span span( static_cast(frame.color.pixels()), frame.color.pixels_size); - JXL_ASSERT(Rect(frame.frame_info.layer_info.crop_x0, + JXL_ENSURE(Rect(frame.frame_info.layer_info.crop_x0, frame.frame_info.layer_info.crop_y0, frame.frame_info.layer_info.xsize, frame.frame_info.layer_info.ysize) @@ -53,43 +64,43 @@ Status ConvertPackedFrameToImageBundle(const JxlBasicInfo& info, bundle->origin.y0 = frame.frame_info.layer_info.crop_y0; } bundle->name = frame.name; // frame.frame_info.name_length is ignored here. - JXL_ASSERT(io.metadata.m.color_encoding.IsGray() == + JXL_ENSURE(io.metadata.m.color_encoding.IsGray() == (frame.color.format.num_channels <= 2)); JXL_RETURN_IF_ERROR(ConvertFromExternal( span, frame.color.xsize, frame.color.ysize, io.metadata.m.color_encoding, - frame.color.format.num_channels, - /*alpha_is_premultiplied=*/info.alpha_premultiplied, - frame_bits_per_sample, frame.color.format.endianness, pool, bundle, - /*float_in=*/float_in, /*align=*/0)); + frame_bits_per_sample, frame.color.format, pool, bundle)); bundle->extra_channels().resize(io.metadata.m.extra_channel_info.size()); for (size_t i = 0; i < frame.extra_channels.size(); i++) { const auto& ppf_ec = frame.extra_channels[i]; - bundle->extra_channels()[i] = ImageF(ppf_ec.xsize, ppf_ec.ysize); - JXL_CHECK(BufferToImageF(ppf_ec.format, ppf_ec.xsize, ppf_ec.ysize, - ppf_ec.pixels(), ppf_ec.pixels_size, pool, - &bundle->extra_channels()[i])); + JXL_ASSIGN_OR_RETURN( + bundle->extra_channels()[i], + ImageF::Create(memory_manager, ppf_ec.xsize, ppf_ec.ysize)); + JXL_RETURN_IF_ERROR(BufferToImageF( + ppf_ec.format, ppf_ec.xsize, ppf_ec.ysize, ppf_ec.pixels(), + ppf_ec.pixels_size, pool, &bundle->extra_channels()[i])); } return true; } Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf, ThreadPool* pool, CodecInOut* io) { + JxlMemoryManager* memory_manager = io->memory_manager; const bool has_alpha = ppf.info.alpha_bits != 0; - JXL_ASSERT(!ppf.frames.empty()); + JXL_ENSURE(!ppf.frames.empty()); if (has_alpha) { - JXL_ASSERT(ppf.info.alpha_bits == ppf.info.bits_per_sample); - JXL_ASSERT(ppf.info.alpha_exponent_bits == + JXL_ENSURE(ppf.info.alpha_bits == ppf.info.bits_per_sample); + JXL_ENSURE(ppf.info.alpha_exponent_bits == ppf.info.exponent_bits_per_sample); } - const bool is_gray = ppf.info.num_color_channels == 1; - JXL_ASSERT(ppf.info.num_color_channels == 1 || + const bool is_gray = (ppf.info.num_color_channels == 1); + JXL_ENSURE(ppf.info.num_color_channels == 1 || ppf.info.num_color_channels == 3); // Convert the image metadata - io->SetSize(ppf.info.xsize, ppf.info.ysize); + JXL_RETURN_IF_ERROR(io->SetSize(ppf.info.xsize, ppf.info.ysize)); io->metadata.m.bit_depth.bits_per_sample = ppf.info.bits_per_sample; io->metadata.m.bit_depth.exponent_bits_per_sample = ppf.info.exponent_bits_per_sample; @@ -99,35 +110,41 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf, ppf.info.exponent_bits_per_sample == 0 && ppf.info.bits_per_sample <= 12; io->metadata.m.SetAlphaBits(ppf.info.alpha_bits, - ppf.info.alpha_premultiplied); + FROM_JXL_BOOL(ppf.info.alpha_premultiplied)); + ExtraChannelInfo* alpha = io->metadata.m.Find(ExtraChannel::kAlpha); + if (alpha) alpha->bit_depth = io->metadata.m.bit_depth; - io->metadata.m.xyb_encoded = !ppf.info.uses_original_profile; - JXL_ASSERT(ppf.info.orientation > 0 && ppf.info.orientation <= 8); + io->metadata.m.xyb_encoded = !FROM_JXL_BOOL(ppf.info.uses_original_profile); + JXL_ENSURE(ppf.info.orientation > 0 && ppf.info.orientation <= 8); io->metadata.m.orientation = ppf.info.orientation; // Convert animation metadata - JXL_ASSERT(ppf.frames.size() == 1 || ppf.info.have_animation); - io->metadata.m.have_animation = ppf.info.have_animation; + JXL_ENSURE(ppf.frames.size() == 1 || ppf.info.have_animation); + io->metadata.m.have_animation = FROM_JXL_BOOL(ppf.info.have_animation); io->metadata.m.animation.tps_numerator = ppf.info.animation.tps_numerator; io->metadata.m.animation.tps_denominator = ppf.info.animation.tps_denominator; io->metadata.m.animation.num_loops = ppf.info.animation.num_loops; // Convert the color encoding. - if (!ppf.icc.empty()) { - PaddedBytes icc; - icc.append(ppf.icc); - if (!io->metadata.m.color_encoding.SetICC(std::move(icc))) { + if (ppf.primary_color_representation == PackedPixelFile::kIccIsPrimary) { + IccBytes icc = ppf.icc; + if (!io->metadata.m.color_encoding.SetICC(std::move(icc), + JxlGetDefaultCms())) { fprintf(stderr, "Warning: error setting ICC profile, assuming SRGB\n"); io->metadata.m.color_encoding = ColorEncoding::SRGB(is_gray); } else { + if (io->metadata.m.color_encoding.IsCMYK()) { + // We expect gray or tri-color. + return JXL_FAILURE("Embedded ICC is CMYK"); + } if (io->metadata.m.color_encoding.IsGray() != is_gray) { // E.g. JPG image has 3 channels, but gray ICC. return JXL_FAILURE("Embedded ICC does not match image color type"); } } } else { - JXL_RETURN_IF_ERROR(ConvertExternalToInternalColorEncoding( - ppf.color_encoding, &io->metadata.m.color_encoding)); + JXL_RETURN_IF_ERROR( + io->metadata.m.color_encoding.FromExternal(ppf.color_encoding)); if (io->metadata.m.color_encoding.ICC().empty()) { return JXL_FAILURE("Failed to serialize ICC"); } @@ -136,12 +153,12 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf, // Convert the extra blobs io->blobs.exif = ppf.metadata.exif; io->blobs.iptc = ppf.metadata.iptc; + io->blobs.jhgm = ppf.metadata.jhgm; io->blobs.jumbf = ppf.metadata.jumbf; io->blobs.xmp = ppf.metadata.xmp; // Append all other extra channels. - for (const PackedPixelFile::PackedExtraChannel& info : - ppf.extra_channels_info) { + for (const auto& info : ppf.extra_channels_info) { ExtraChannelInfo out; out.type = static_cast(info.ec_info.type); out.bit_depth.bits_per_sample = info.ec_info.bits_per_sample; @@ -167,18 +184,17 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf, JXL_RETURN_IF_ERROR( io->metadata.m.preview_size.Set(preview_xsize, preview_ysize)); JXL_RETURN_IF_ERROR(ConvertPackedFrameToImageBundle( - ppf.info, *ppf.preview_frame, *io, pool, &io->preview_frame)); + ppf.info, ppf.input_bitdepth, *ppf.preview_frame, *io, pool, + &io->preview_frame)); } // Convert the pixels - io->dec_pixels = 0; io->frames.clear(); for (const auto& frame : ppf.frames) { - ImageBundle bundle(&io->metadata.m); - JXL_RETURN_IF_ERROR( - ConvertPackedFrameToImageBundle(ppf.info, frame, *io, pool, &bundle)); + ImageBundle bundle(memory_manager, &io->metadata.m); + JXL_RETURN_IF_ERROR(ConvertPackedFrameToImageBundle( + ppf.info, ppf.input_bitdepth, frame, *io, pool, &bundle)); io->frames.push_back(std::move(bundle)); - io->dec_pixels += frame.color.xsize * frame.color.ysize; } if (ppf.info.exponent_bits_per_sample == 0) { @@ -188,29 +204,64 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf, if (ppf.info.intensity_target != 0) { io->metadata.m.SetIntensityTarget(ppf.info.intensity_target); } else { - SetIntensityTarget(io); + SetIntensityTarget(&io->metadata.m); } - io->CheckMetadata(); + JXL_RETURN_IF_ERROR(io->CheckMetadata()); return true; } +StatusOr ConvertImage3FToPackedPixelFile( + const Image3F& image, const ColorEncoding& c_enc, JxlPixelFormat format, + ThreadPool* pool) { + PackedPixelFile ppf{}; + ppf.info.xsize = image.xsize(); + ppf.info.ysize = image.ysize(); + ppf.info.num_color_channels = 3; + JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(format.data_type)); + ppf.info.bits_per_sample = PackedImage::BitsPerChannel(format.data_type); + ppf.info.exponent_bits_per_sample = format.data_type == JXL_TYPE_FLOAT ? 8 + : format.data_type == JXL_TYPE_FLOAT16 + ? 5 + : 0; + ppf.color_encoding = c_enc.ToExternal(); + ppf.frames.clear(); + JXL_ASSIGN_OR_RETURN( + PackedFrame frame, + PackedFrame::Create(image.xsize(), image.ysize(), format)); + const ImageF* channels[3]; + for (int c = 0; c < 3; ++c) { + channels[c] = &image.Plane(c); + } + bool float_samples = ppf.info.exponent_bits_per_sample > 0; + JXL_RETURN_IF_ERROR(ConvertChannelsToExternal( + channels, 3, ppf.info.bits_per_sample, float_samples, format.endianness, + frame.color.stride, pool, frame.color.pixels(0, 0, 0), + frame.color.pixels_size, PixelCallback(), Orientation::kIdentity)); + ppf.frames.emplace_back(std::move(frame)); + return ppf; +} + // Allows converting from internal CodecInOut to external PackedPixelFile Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io, const JxlPixelFormat& pixel_format, const ColorEncoding& c_desired, ThreadPool* pool, PackedPixelFile* ppf) { + JxlMemoryManager* memory_manager = io.memory_manager; const bool has_alpha = io.metadata.m.HasAlpha(); - bool alpha_premultiplied = false; - JXL_ASSERT(!io.frames.empty()); + JXL_ENSURE(!io.frames.empty()); if (has_alpha) { - JXL_ASSERT(io.metadata.m.GetAlphaBits() == + JXL_ENSURE(io.metadata.m.GetAlphaBits() == io.metadata.m.bit_depth.bits_per_sample); const auto* alpha_channel = io.metadata.m.Find(ExtraChannel::kAlpha); - JXL_ASSERT(alpha_channel->bit_depth.exponent_bits_per_sample == + JXL_ENSURE(alpha_channel->bit_depth.exponent_bits_per_sample == io.metadata.m.bit_depth.exponent_bits_per_sample); - alpha_premultiplied = alpha_channel->alpha_associated; + ppf->info.alpha_bits = alpha_channel->bit_depth.bits_per_sample; + ppf->info.alpha_exponent_bits = + alpha_channel->bit_depth.exponent_bits_per_sample; + ppf->info.alpha_premultiplied = + TO_JXL_BOOL(alpha_channel->alpha_associated); } // Convert the image metadata @@ -221,29 +272,36 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io, ppf->info.exponent_bits_per_sample = io.metadata.m.bit_depth.exponent_bits_per_sample; - ppf->info.alpha_bits = io.metadata.m.GetAlphaBits(); - ppf->info.alpha_premultiplied = alpha_premultiplied; + ppf->info.intensity_target = io.metadata.m.tone_mapping.intensity_target; + ppf->info.linear_below = io.metadata.m.tone_mapping.linear_below; + ppf->info.min_nits = io.metadata.m.tone_mapping.min_nits; + ppf->info.relative_to_max_display = + TO_JXL_BOOL(io.metadata.m.tone_mapping.relative_to_max_display); - ppf->info.uses_original_profile = !io.metadata.m.xyb_encoded; - JXL_ASSERT(0 < io.metadata.m.orientation && io.metadata.m.orientation <= 8); + ppf->info.uses_original_profile = TO_JXL_BOOL(!io.metadata.m.xyb_encoded); + JXL_ENSURE(0 < io.metadata.m.orientation && io.metadata.m.orientation <= 8); ppf->info.orientation = static_cast(io.metadata.m.orientation); ppf->info.num_color_channels = io.metadata.m.color_encoding.Channels(); // Convert animation metadata - JXL_ASSERT(io.frames.size() == 1 || io.metadata.m.have_animation); - ppf->info.have_animation = io.metadata.m.have_animation; + JXL_ENSURE(io.frames.size() == 1 || io.metadata.m.have_animation); + ppf->info.have_animation = TO_JXL_BOOL(io.metadata.m.have_animation); ppf->info.animation.tps_numerator = io.metadata.m.animation.tps_numerator; 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()); - ConvertInternalToExternalColorEncoding(c_desired, &ppf->color_encoding); + ppf->primary_color_representation = + c_desired.WantICC() ? PackedPixelFile::kIccIsPrimary + : PackedPixelFile::kColorEncodingIsPrimary; + ppf->color_encoding = c_desired.ToExternal(); // Convert the extra blobs ppf->metadata.exif = io.blobs.exif; ppf->metadata.iptc = io.blobs.iptc; + ppf->metadata.jhgm = io.blobs.jhgm; ppf->metadata.jumbf = io.blobs.jumbf; ppf->metadata.xmp = io.blobs.xmp; const bool float_out = pixel_format.data_type == JXL_TYPE_FLOAT || @@ -251,37 +309,36 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io, // Convert the pixels ppf->frames.clear(); for (const auto& frame : io.frames) { - JXL_ASSERT(frame.metadata()->bit_depth.bits_per_sample != 0); + JXL_ENSURE(frame.metadata()->bit_depth.bits_per_sample != 0); // It is ok for the frame.color().kNumPlanes to not match the // number of channels on the image. + const uint32_t alpha_channels = has_alpha ? 1 : 0; const uint32_t num_channels = - frame.metadata()->color_encoding.Channels() + has_alpha; + frame.metadata()->color_encoding.Channels() + alpha_channels; JxlPixelFormat format{/*num_channels=*/num_channels, /*data_type=*/pixel_format.data_type, /*endianness=*/pixel_format.endianness, /*align=*/pixel_format.align}; - PackedFrame packed_frame(frame.oriented_xsize(), frame.oriented_ysize(), - format); + JXL_ASSIGN_OR_RETURN(PackedFrame packed_frame, + PackedFrame::Create(frame.oriented_xsize(), + frame.oriented_ysize(), format)); + JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(pixel_format.data_type)); const size_t bits_per_sample = float_out ? packed_frame.color.BitsPerChannel(pixel_format.data_type) : ppf->info.bits_per_sample; packed_frame.name = frame.name; packed_frame.frame_info.name_length = frame.name.size(); // Color transform - ImageBundle ib = frame.Copy(); + JXL_ASSIGN_OR_RETURN(ImageBundle ib, frame.Copy()); const ImageBundle* to_color_transform = &ib; ImageMetadata metadata = io.metadata.m; - ImageBundle store(&metadata); + ImageBundle store(memory_manager, &metadata); const ImageBundle* transformed; // TODO(firsching): handle the transform here. JXL_RETURN_IF_ERROR(TransformIfNeeded(*to_color_transform, c_desired, - GetJxlCms(), pool, &store, + *JxlGetDefaultCms(), pool, &store, &transformed)); - size_t stride = ib.oriented_xsize() * - (c_desired.Channels() * ppf->info.bits_per_sample) / - kBitsPerByte; - PaddedBytes pixels(stride * ib.oriented_ysize()); JXL_RETURN_IF_ERROR(ConvertToExternal( *transformed, bits_per_sample, float_out, format.num_channels, @@ -292,7 +349,7 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io, // TODO(firsching): Convert the extra channels, beside one potential alpha // channel. FIXME! - JXL_CHECK(frame.extra_channels().size() <= has_alpha); + JXL_ENSURE(frame.extra_channels().size() <= (has_alpha ? 1 : 0)); ppf->frames.push_back(std::move(packed_frame)); } diff --git a/media/libjxl/src/lib/extras/packed_image_convert.h b/media/libjxl/src/lib/extras/packed_image_convert.h index cada660446..20ed330b84 100644 --- a/media/libjxl/src/lib/extras/packed_image_convert.h +++ b/media/libjxl/src/lib/extras/packed_image_convert.h @@ -9,7 +9,8 @@ // Helper functions to convert from the external image types to the internal // CodecInOut to help transitioning to the external types. -#include "jxl/types.h" +#include + #include "lib/extras/packed_image.h" #include "lib/jxl/base/status.h" #include "lib/jxl/codec_in_out.h" @@ -29,6 +30,10 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io, const ColorEncoding& c_desired, ThreadPool* pool, PackedPixelFile* ppf); + +StatusOr ConvertImage3FToPackedPixelFile( + const Image3F& image, const ColorEncoding& c_enc, JxlPixelFormat format, + ThreadPool* pool); } // namespace extras } // namespace jxl diff --git a/media/libjxl/src/lib/extras/render_hdr.cc b/media/libjxl/src/lib/extras/render_hdr.cc deleted file mode 100644 index b247699cd3..0000000000 --- a/media/libjxl/src/lib/extras/render_hdr.cc +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) the JPEG XL Project Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#include "lib/extras/render_hdr.h" - -#include "lib/extras/hlg.h" -#include "lib/extras/tone_mapping.h" -#include "lib/jxl/enc_color_management.h" - -namespace jxl { - -Status RenderHDR(CodecInOut* io, float display_nits, ThreadPool* pool) { - const ColorEncoding& original_color_encoding = io->metadata.m.color_encoding; - if (!(original_color_encoding.tf.IsPQ() || - original_color_encoding.tf.IsHLG())) { - // Nothing to do. - return true; - } - - if (original_color_encoding.tf.IsPQ()) { - JXL_RETURN_IF_ERROR(ToneMapTo({0, display_nits}, io, pool)); - JXL_RETURN_IF_ERROR(GamutMap(io, /*preserve_saturation=*/0.1, pool)); - } else { - const float intensity_target = io->metadata.m.IntensityTarget(); - const float gamma_hlg_to_display = GetHlgGamma(display_nits); - // If the image is already in display space, we need to account for the - // already-applied OOTF. - const float gamma_display_to_display = - gamma_hlg_to_display / GetHlgGamma(intensity_target); - // Ensures that conversions to linear in HlgOOTF below will not themselves - // include the OOTF. - io->metadata.m.SetIntensityTarget(300); - - bool need_gamut_mapping = false; - for (ImageBundle& ib : io->frames) { - const float gamma = ib.c_current().tf.IsHLG() ? gamma_hlg_to_display - : gamma_display_to_display; - if (gamma < 1) need_gamut_mapping = true; - JXL_RETURN_IF_ERROR(HlgOOTF(&ib, gamma, pool)); - } - io->metadata.m.SetIntensityTarget(display_nits); - - if (need_gamut_mapping) { - JXL_RETURN_IF_ERROR(GamutMap(io, /*preserve_saturation=*/0.1, pool)); - } - } - - ColorEncoding rec2020_pq; - rec2020_pq.SetColorSpace(ColorSpace::kRGB); - rec2020_pq.white_point = WhitePoint::kD65; - rec2020_pq.primaries = Primaries::k2100; - rec2020_pq.tf.SetTransferFunction(TransferFunction::kPQ); - JXL_RETURN_IF_ERROR(rec2020_pq.CreateICC()); - io->metadata.m.color_encoding = rec2020_pq; - return io->TransformTo(rec2020_pq, GetJxlCms(), pool); -} - -} // namespace jxl diff --git a/media/libjxl/src/lib/extras/render_hdr.h b/media/libjxl/src/lib/extras/render_hdr.h deleted file mode 100644 index 95127e074b..0000000000 --- a/media/libjxl/src/lib/extras/render_hdr.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) the JPEG XL Project Authors. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#ifndef LIB_EXTRAS_RENDER_HDR_H_ -#define LIB_EXTRAS_RENDER_HDR_H_ - -#include "lib/jxl/codec_in_out.h" - -namespace jxl { - -// If `io` has an original color space using PQ or HLG, this renders it -// appropriately for a display with a peak luminance of `display_nits` and -// converts the result to a Rec. 2020 / PQ image. Otherwise, leaves the image as -// is. -// PQ images are tone-mapped using the method described in Rep. ITU-R BT.2408-5 -// annex 5, while HLG images are rendered using the HLG OOTF with a gamma -// appropriate for the given target luminance. -// With a sufficiently bright SDR display, converting the output of this -// function to an SDR colorspace may look decent. -Status RenderHDR(CodecInOut* io, float display_nits, - ThreadPool* pool = nullptr); - -} // namespace jxl - -#endif // LIB_EXTRAS_RENDER_HDR_H_ diff --git a/media/libjxl/src/lib/jxl/size_constraints.h b/media/libjxl/src/lib/extras/size_constraints.h similarity index 50% rename from media/libjxl/src/lib/jxl/size_constraints.h rename to media/libjxl/src/lib/extras/size_constraints.h index 20787b1645..cf06f8cb22 100644 --- a/media/libjxl/src/lib/jxl/size_constraints.h +++ b/media/libjxl/src/lib/extras/size_constraints.h @@ -7,6 +7,9 @@ #define LIB_JXL_SIZE_CONSTRAINTS_H_ #include +#include + +#include "lib/jxl/base/status.h" namespace jxl { @@ -18,6 +21,23 @@ struct SizeConstraints { uint64_t dec_max_pixels = 0xFFFFFFFFu; // Might be up to ~0ull }; +template ::value>::type> +Status VerifyDimensions(const SizeConstraints* constraints, T xs, T ys) { + if (!constraints) return true; + + if (xs == 0 || ys == 0) return JXL_FAILURE("Empty image."); + if (xs > constraints->dec_max_xsize) return JXL_FAILURE("Image too wide."); + if (ys > constraints->dec_max_ysize) return JXL_FAILURE("Image too tall."); + + const uint64_t num_pixels = static_cast(xs) * ys; + if (num_pixels > constraints->dec_max_pixels) { + return JXL_FAILURE("Image too big."); + } + + return true; +} + } // namespace jxl #endif // LIB_JXL_SIZE_CONSTRAINTS_H_ diff --git a/media/libjxl/src/lib/extras/time.cc b/media/libjxl/src/lib/extras/time.cc index 73d1b8f260..344994f78d 100644 --- a/media/libjxl/src/lib/extras/time.cc +++ b/media/libjxl/src/lib/extras/time.cc @@ -6,7 +6,6 @@ #include "lib/extras/time.h" #include -#include #include #include @@ -47,7 +46,7 @@ double Now() { if (timebase.denom == 0) { (void)mach_timebase_info(&timebase); } - return double(t) * timebase.numer / timebase.denom * 1E-9; + return double(t) * timebase.numer / timebase.denom * 1E-9; // notypo #elif JXL_OS_HAIKU return double(system_time_nsecs()) * 1E-9; #else diff --git a/media/libjxl/src/lib/extras/tone_mapping.cc b/media/libjxl/src/lib/extras/tone_mapping.cc index 1ed1b29119..9a700c48a3 100644 --- a/media/libjxl/src/lib/extras/tone_mapping.cc +++ b/media/libjxl/src/lib/extras/tone_mapping.cc @@ -7,17 +7,19 @@ #undef HWY_TARGET_INCLUDE #define HWY_TARGET_INCLUDE "lib/extras/tone_mapping.cc" +#include + #include #include -#include "lib/jxl/dec_tone_mapping-inl.h" -#include "lib/jxl/enc_color_management.h" +#include "lib/jxl/cms/tone_mapping-inl.h" +#include "lib/jxl/image_bundle.h" HWY_BEFORE_NAMESPACE(); namespace jxl { namespace HWY_NAMESPACE { -static constexpr float rec2020_luminances[3] = {0.2627f, 0.6780f, 0.0593f}; +static constexpr Vector3 rec2020_luminances{0.2627f, 0.6780f, 0.0593f}; Status ToneMapFrame(const std::pair display_nits, ImageBundle* const ib, ThreadPool* const pool) { @@ -30,34 +32,37 @@ Status ToneMapFrame(const std::pair display_nits, ColorEncoding linear_rec2020; linear_rec2020.SetColorSpace(ColorSpace::kRGB); - linear_rec2020.primaries = Primaries::k2100; - linear_rec2020.white_point = WhitePoint::kD65; - linear_rec2020.tf.SetTransferFunction(TransferFunction::kLinear); + JXL_RETURN_IF_ERROR(linear_rec2020.SetPrimariesType(Primaries::k2100)); + JXL_RETURN_IF_ERROR(linear_rec2020.SetWhitePointType(WhitePoint::kD65)); + linear_rec2020.Tf().SetTransferFunction(TransferFunction::kLinear); JXL_RETURN_IF_ERROR(linear_rec2020.CreateICC()); - JXL_RETURN_IF_ERROR(ib->TransformTo(linear_rec2020, GetJxlCms(), pool)); + JXL_RETURN_IF_ERROR( + ib->TransformTo(linear_rec2020, *JxlGetDefaultCms(), pool)); Rec2408ToneMapper tone_mapper( {ib->metadata()->tone_mapping.min_nits, ib->metadata()->IntensityTarget()}, display_nits, rec2020_luminances); - return RunOnPool( - pool, 0, ib->ysize(), ThreadPool::NoInit, - [&](const uint32_t y, size_t /* thread */) { - float* const JXL_RESTRICT row_r = ib->color()->PlaneRow(0, y); - float* const JXL_RESTRICT row_g = ib->color()->PlaneRow(1, y); - float* const JXL_RESTRICT row_b = ib->color()->PlaneRow(2, y); - for (size_t x = 0; x < ib->xsize(); x += Lanes(df)) { - V red = Load(df, row_r + x); - V green = Load(df, row_g + x); - V blue = Load(df, row_b + x); - tone_mapper.ToneMap(&red, &green, &blue); - Store(red, df, row_r + x); - Store(green, df, row_g + x); - Store(blue, df, row_b + x); - } - }, - "ToneMap"); + const auto process_row = [&](const uint32_t y, + size_t /* thread */) -> Status { + float* const JXL_RESTRICT row_r = ib->color()->PlaneRow(0, y); + float* const JXL_RESTRICT row_g = ib->color()->PlaneRow(1, y); + float* const JXL_RESTRICT row_b = ib->color()->PlaneRow(2, y); + for (size_t x = 0; x < ib->xsize(); x += Lanes(df)) { + V red = Load(df, row_r + x); + V green = Load(df, row_g + x); + V blue = Load(df, row_b + x); + tone_mapper.ToneMap(&red, &green, &blue); + Store(red, df, row_r + x); + Store(green, df, row_g + x); + Store(blue, df, row_b + x); + } + return true; + }; + JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, ib->ysize(), ThreadPool::NoInit, + process_row, "ToneMap")); + return true; } Status GamutMapFrame(ImageBundle* const ib, float preserve_saturation, @@ -67,30 +72,31 @@ Status GamutMapFrame(ImageBundle* const ib, float preserve_saturation, ColorEncoding linear_rec2020; linear_rec2020.SetColorSpace(ColorSpace::kRGB); - linear_rec2020.primaries = Primaries::k2100; - linear_rec2020.white_point = WhitePoint::kD65; - linear_rec2020.tf.SetTransferFunction(TransferFunction::kLinear); + JXL_RETURN_IF_ERROR(linear_rec2020.SetPrimariesType(Primaries::k2100)); + JXL_RETURN_IF_ERROR(linear_rec2020.SetWhitePointType(WhitePoint::kD65)); + linear_rec2020.Tf().SetTransferFunction(TransferFunction::kLinear); JXL_RETURN_IF_ERROR(linear_rec2020.CreateICC()); - JXL_RETURN_IF_ERROR(ib->TransformTo(linear_rec2020, GetJxlCms(), pool)); + JXL_RETURN_IF_ERROR( + ib->TransformTo(linear_rec2020, *JxlGetDefaultCms(), pool)); - JXL_RETURN_IF_ERROR(RunOnPool( - pool, 0, ib->ysize(), ThreadPool::NoInit, - [&](const uint32_t y, size_t /* thread*/) { - float* const JXL_RESTRICT row_r = ib->color()->PlaneRow(0, y); - float* const JXL_RESTRICT row_g = ib->color()->PlaneRow(1, y); - float* const JXL_RESTRICT row_b = ib->color()->PlaneRow(2, y); - for (size_t x = 0; x < ib->xsize(); x += Lanes(df)) { - V red = Load(df, row_r + x); - V green = Load(df, row_g + x); - V blue = Load(df, row_b + x); - GamutMap(&red, &green, &blue, rec2020_luminances, - preserve_saturation); - Store(red, df, row_r + x); - Store(green, df, row_g + x); - Store(blue, df, row_b + x); - } - }, - "GamutMap")); + const auto process_row = [&](const uint32_t y, size_t /* thread*/) -> Status { + float* const JXL_RESTRICT row_r = ib->color()->PlaneRow(0, y); + float* const JXL_RESTRICT row_g = ib->color()->PlaneRow(1, y); + float* const JXL_RESTRICT row_b = ib->color()->PlaneRow(2, y); + for (size_t x = 0; x < ib->xsize(); x += Lanes(df)) { + V red = Load(df, row_r + x); + V green = Load(df, row_g + x); + V blue = Load(df, row_b + x); + GamutMap(&red, &green, &blue, rec2020_luminances, preserve_saturation); + Store(red, df, row_r + x); + Store(green, df, row_g + x); + Store(blue, df, row_b + x); + } + return true; + }; + + JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, ib->ysize(), ThreadPool::NoInit, + process_row, "GamutMap")); return true; } diff --git a/media/libjxl/src/lib/extras/tone_mapping_gbench.cc b/media/libjxl/src/lib/extras/tone_mapping_gbench.cc index 2f97b88667..30b4e6e7e0 100644 --- a/media/libjxl/src/lib/extras/tone_mapping_gbench.cc +++ b/media/libjxl/src/lib/extras/tone_mapping_gbench.cc @@ -3,42 +3,58 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +#include + #include "benchmark/benchmark.h" -#include "lib/extras/codec.h" #include "lib/extras/tone_mapping.h" -#include "lib/jxl/enc_color_management.h" -#include "lib/jxl/testdata.h" +#include "lib/jxl/base/status.h" +#include "lib/jxl/image.h" +#include "tools/no_memory_manager.h" namespace jxl { -static void BM_ToneMapping(benchmark::State& state) { - CodecInOut image; - const PaddedBytes image_bytes = ReadTestData("jxl/flower/flower.png"); - JXL_CHECK(SetFromBytes(Span(image_bytes), &image)); +#define QUIT(M) \ + state.SkipWithError(M); \ + return; - // Convert to linear Rec. 2020 so that `ToneMapTo` doesn't have to and we - // mainly measure the tone mapping itself. - ColorEncoding linear_rec2020; - linear_rec2020.SetColorSpace(ColorSpace::kRGB); - linear_rec2020.primaries = Primaries::k2100; - linear_rec2020.white_point = WhitePoint::kD65; - linear_rec2020.tf.SetTransferFunction(TransferFunction::kLinear); - JXL_CHECK(linear_rec2020.CreateICC()); - JXL_CHECK(image.TransformTo(linear_rec2020, GetJxlCms())); - - for (auto _ : state) { - state.PauseTiming(); - CodecInOut tone_mapping_input; - tone_mapping_input.SetFromImage(CopyImage(*image.Main().color()), - image.Main().c_current()); - tone_mapping_input.metadata.m.SetIntensityTarget( - image.metadata.m.IntensityTarget()); - state.ResumeTiming(); - - JXL_CHECK(ToneMapTo({0.1, 100}, &tone_mapping_input)); +#define BM_CHECK(C) \ + if (!(C)) { \ + QUIT(#C) \ } - state.SetItemsProcessed(state.iterations() * image.xsize() * image.ysize()); +static void BM_ToneMapping(benchmark::State& state) { + JxlMemoryManager* memory_manager = jpegxl::tools::NoMemoryManager(); + JXL_ASSIGN_OR_QUIT(Image3F color, Image3F::Create(memory_manager, 2268, 1512), + "Failed to allocate color plane"); + FillImage(0.5f, &color); + + // Use linear Rec. 2020 so that `ToneMapTo` doesn't have to convert to it and + // we mainly measure the tone mapping itself. + ColorEncoding linear_rec2020; + linear_rec2020.SetColorSpace(ColorSpace::kRGB); + BM_CHECK(linear_rec2020.SetPrimariesType(Primaries::k2100)); + BM_CHECK(linear_rec2020.SetWhitePointType(WhitePoint::kD65)); + linear_rec2020.Tf().SetTransferFunction(TransferFunction::kLinear); + BM_CHECK(linear_rec2020.CreateICC()); + + for (auto _ : state) { + (void)_; + state.PauseTiming(); + CodecInOut tone_mapping_input{memory_manager}; + JXL_ASSIGN_OR_QUIT( + Image3F color2, + Image3F::Create(memory_manager, color.xsize(), color.ysize()), + "Failed to allocate color plane"); + BM_CHECK(CopyImageTo(color, &color2)); + BM_CHECK( + tone_mapping_input.SetFromImage(std::move(color2), linear_rec2020)); + tone_mapping_input.metadata.m.SetIntensityTarget(255); + state.ResumeTiming(); + + BM_CHECK(ToneMapTo({0.1, 100}, &tone_mapping_input)); + } + + state.SetItemsProcessed(state.iterations() * color.xsize() * color.ysize()); } BENCHMARK(BM_ToneMapping); diff --git a/media/libjxl/src/lib/include/jxl/butteraugli.h b/media/libjxl/src/lib/include/jxl/butteraugli.h deleted file mode 100644 index ba69a29623..0000000000 --- a/media/libjxl/src/lib/include/jxl/butteraugli.h +++ /dev/null @@ -1,160 +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. - */ - -/** @addtogroup libjxl_butteraugli - * @{ - * @file butteraugli.h - * @brief Butteraugli API for JPEG XL. - */ - -#ifndef JXL_BUTTERAUGLI_H_ -#define JXL_BUTTERAUGLI_H_ - -#if defined(__cplusplus) || defined(c_plusplus) -extern "C" { -#endif - -#include "jxl/jxl_export.h" -#include "jxl/memory_manager.h" -#include "jxl/parallel_runner.h" -#include "jxl/types.h" - -/** - * Opaque structure that holds a butteraugli API. - * - * Allocated and initialized with JxlButteraugliApiCreate(). - * Cleaned up and deallocated with JxlButteraugliApiDestroy(). - */ -typedef struct JxlButteraugliApiStruct JxlButteraugliApi; - -/** - * Opaque structure that holds intermediary butteraugli results. - * - * Allocated and initialized with JxlButteraugliCompute(). - * Cleaned up and deallocated with JxlButteraugliResultDestroy(). - */ -typedef struct JxlButteraugliResultStruct JxlButteraugliResult; - -/** - * Deinitializes and frees JxlButteraugliResult instance. - * - * @param result instance to be cleaned up and deallocated. - */ -JXL_EXPORT void JxlButteraugliResultDestroy(JxlButteraugliResult* result); - -/** - * Creates an instance of JxlButteraugliApi and initializes it. - * - * @p memory_manager will be used for all the library dynamic allocations made - * from this instance. The parameter may be NULL, in which case the default - * allocator will be used. See jxl/memory_manager.h for details. - * - * @param memory_manager custom allocator function. It may be NULL. The memory - * manager will be copied internally. - * @return @c NULL if the instance can not be allocated or initialized - * @return pointer to initialized JxlEncoder otherwise - */ -JXL_EXPORT JxlButteraugliApi* JxlButteraugliApiCreate( - const JxlMemoryManager* memory_manager); - -/** - * Set the parallel runner for multithreading. - * - * @param api api instance. - * @param parallel_runner function pointer to runner for multithreading. A - * multithreaded runner should be set to reach fast performance. - * @param parallel_runner_opaque opaque pointer for parallel_runner. - */ -JXL_EXPORT void JxlButteraugliApiSetParallelRunner( - JxlButteraugliApi* api, JxlParallelRunner parallel_runner, - void* parallel_runner_opaque); - -/** - * Set the hf_asymmetry option for butteraugli. - * - * @param api api instance. - * @param v new hf_asymmetry value. - */ -JXL_EXPORT void JxlButteraugliApiSetHFAsymmetry(JxlButteraugliApi* api, - float v); - -/** - * Set the intensity_target option for butteraugli. - * - * @param api api instance. - * @param v new intensity_target value. - */ -JXL_EXPORT void JxlButteraugliApiSetIntensityTarget(JxlButteraugliApi* api, - float v); - -/** - * Deinitializes and frees JxlButteraugliApi instance. - * - * @param api instance to be cleaned up and deallocated. - */ -JXL_EXPORT void JxlButteraugliApiDestroy(JxlButteraugliApi* api); - -/** - * Computes intermediary butteraugli result between an original image and a - * distortion. - * - * @param api api instance for this computation. - * @param xsize width of the compared images. - * @param ysize height of the compared images. - * @param pixel_format_orig pixel format for original image. - * @param buffer_orig pixel data for original image. - * @param size_orig size of buffer_orig in bytes. - * @param pixel_format_dist pixel format for distortion. - * @param buffer_dist pixel data for distortion. - * @param size_dist size of buffer_dist in bytes. - * @return @c NULL if the results can not be computed or initialized. - * @return pointer to initialized and computed intermediary result. - */ -JXL_EXPORT JxlButteraugliResult* JxlButteraugliCompute( - const JxlButteraugliApi* api, uint32_t xsize, uint32_t ysize, - const JxlPixelFormat* pixel_format_orig, const void* buffer_orig, - size_t size_orig, const JxlPixelFormat* pixel_format_dist, - const void* buffer_dist, size_t size_dist); - -/** - * Computes butteraugli max distance based on an intermediary butteraugli - * result. - * - * @param result intermediary result instance. - * @return max distance. - */ -JXL_EXPORT float JxlButteraugliResultGetMaxDistance( - const JxlButteraugliResult* result); - -/** - * Computes a butteraugli distance based on an intermediary butteraugli result. - * - * @param result intermediary result instance. - * @param pnorm pnorm to calculate. - * @return distance using the given pnorm. - */ -JXL_EXPORT float JxlButteraugliResultGetDistance( - const JxlButteraugliResult* result, float pnorm); - -/** - * Get a pointer to the distmap in the result. - * - * @param result intermediary result instance. - * @param buffer will be set to the distmap. The distance value for (x,y) will - * be available at buffer + y * row_stride + x. - * @param row_stride will be set to the row stride of the distmap. - */ -JXL_EXPORT void JxlButteraugliResultGetDistmap( - const JxlButteraugliResult* result, const float** buffer, - uint32_t* row_stride); - -#if defined(__cplusplus) || defined(c_plusplus) -} -#endif - -#endif /* JXL_BUTTERAUGLI_H_ */ - -/** @}*/ diff --git a/media/libjxl/src/lib/include/jxl/butteraugli_cxx.h b/media/libjxl/src/lib/include/jxl/butteraugli_cxx.h deleted file mode 100644 index 55efd74d68..0000000000 --- a/media/libjxl/src/lib/include/jxl/butteraugli_cxx.h +++ /dev/null @@ -1,60 +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. - -/// @addtogroup libjxl_butteraugli -/// @{ -/// -/// @file butteraugli_cxx.h -/// @brief C++ header-only helper for @ref butteraugli.h. -/// -/// There's no binary library associated with the header since this is a header -/// only library. - -#ifndef JXL_BUTTERAUGLI_CXX_H_ -#define JXL_BUTTERAUGLI_CXX_H_ - -#include - -#include "jxl/butteraugli.h" - -#if !(defined(__cplusplus) || defined(c_plusplus)) -#error "This a C++ only header. Use jxl/butteraugli.h from C sources." -#endif - -/// Struct to call JxlButteraugliApiDestroy from the JxlButteraugliApiPtr -/// unique_ptr. -struct JxlButteraugliApiDestroyStruct { - /// Calls @ref JxlButteraugliApiDestroy() on the passed api. - void operator()(JxlButteraugliApi* api) { JxlButteraugliApiDestroy(api); } -}; - -/// std::unique_ptr<> type that calls JxlButteraugliApiDestroy() when releasing -/// the pointer. -/// -/// Use this helper type from C++ sources to ensure the api is destroyed and -/// their internal resources released. -typedef std::unique_ptr - JxlButteraugliApiPtr; - -/// Struct to call JxlButteraugliResultDestroy from the JxlButteraugliResultPtr -/// unique_ptr. -struct JxlButteraugliResultDestroyStruct { - /// Calls @ref JxlButteraugliResultDestroy() on the passed result object. - void operator()(JxlButteraugliResult* result) { - JxlButteraugliResultDestroy(result); - } -}; - -/// std::unique_ptr<> type that calls JxlButteraugliResultDestroy() when -/// releasing the pointer. -/// -/// Use this helper type from C++ sources to ensure the result object is -/// destroyed and their internal resources released. -typedef std::unique_ptr - JxlButteraugliResultPtr; - -#endif // JXL_BUTTERAUGLI_CXX_H_ - -/// @} diff --git a/media/libjxl/src/lib/include/jxl/cms.h b/media/libjxl/src/lib/include/jxl/cms.h new file mode 100644 index 0000000000..6616a2681c --- /dev/null +++ b/media/libjxl/src/lib/include/jxl/cms.h @@ -0,0 +1,24 @@ +// 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 JXL_CMS_H_ +#define JXL_CMS_H_ + +// ICC profiles and color space conversions. + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +JXL_CMS_EXPORT const JxlCmsInterface* JxlGetDefaultCms(); + +#ifdef __cplusplus +} +#endif + +#endif // JXL_CMS_H_ diff --git a/media/libjxl/src/lib/include/jxl/cms_interface.h b/media/libjxl/src/lib/include/jxl/cms_interface.h index fb852eeb1f..137fb42d8c 100644 --- a/media/libjxl/src/lib/include/jxl/cms_interface.h +++ b/media/libjxl/src/lib/include/jxl/cms_interface.h @@ -4,7 +4,7 @@ * license that can be found in the LICENSE file. */ -/** @addtogroup libjxl_common +/** @addtogroup libjxl_color * @{ * @file cms_interface.h * @brief Interface to allow the injection of different color management systems @@ -18,13 +18,30 @@ #ifndef JXL_CMS_INTERFACE_H_ #define JXL_CMS_INTERFACE_H_ -#include "jxl/color_encoding.h" -#include "jxl/types.h" +#include +#include +#include +#include -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif +/** Parses an ICC profile and populates @p c and @p cmyk with the data. + * + * @param user_data @ref JxlCmsInterface::set_fields_data passed as-is. + * @param icc_data the ICC data to parse. + * @param icc_size how many bytes of icc_data are valid. + * @param c a @ref JxlColorEncoding to populate if applicable. + * @param cmyk a boolean to set to whether the colorspace is a CMYK colorspace. + * @return Whether the relevant fields in @p c were successfully populated. + */ +typedef JXL_BOOL (*jpegxl_cms_set_fields_from_icc_func)(void* user_data, + const uint8_t* icc_data, + size_t icc_size, + JxlColorEncoding* c, + JXL_BOOL* cmyk); + /** Represents an input or output colorspace to a color transform, as a * serialized ICC profile. */ typedef struct { @@ -49,22 +66,23 @@ typedef struct { /** Allocates and returns the data needed for @p num_threads parallel transforms * from the @p input colorspace to @p output, with up to @p pixels_per_thread - * pixels to transform per call to JxlCmsInterface::run. @p init_data comes - * directly from the JxlCmsInterface instance. Since @c run only receives the - * data returned by @c init, a reference to @p init_data should be kept there - * if access to it is desired in @c run. Likewise for JxlCmsInterface::destroy. + * pixels to transform per call to @ref JxlCmsInterface::run. @p init_data comes + * directly from the @ref JxlCmsInterface instance. Since @c run only receives + * the data returned by @c init, a reference to @p init_data should be kept + * there if access to it is desired in @c run. Likewise for @ref + * JxlCmsInterface::destroy. * * The ICC data in @p input and @p output is guaranteed to outlive the @c init / * @c run / @c destroy cycle. * - * @param init_data JxlCmsInterface::init_data passed as-is. + * @param init_data @ref JxlCmsInterface::init_data passed as-is. * @param num_threads the maximum number of threads from which - * JxlCmsInterface::run will be called. + * @ref JxlCmsInterface::run will be called. * @param pixels_per_thread the maximum number of pixels that each call to - * JxlCmsInterface::run will have to transform. + * @ref JxlCmsInterface::run will have to transform. * @param input_profile the input colorspace for the transform. - * @param output_profile the colorspace to which JxlCmsInterface::run should - * convert the input data. + * @param output_profile the colorspace to which @ref JxlCmsInterface::run + * should convert the input data. * @param intensity_target for colorspaces where luminance is relative * (essentially: not PQ), indicates the luminance at which (1, 1, 1) will * be displayed. This is useful for conversions between PQ and a relative @@ -118,7 +136,7 @@ typedef float* (*jpegxl_cms_get_buffer_func)(void* user_data, size_t thread); * @param output_buffer the buffer receiving the transformed pixel data. * @param num_pixels the number of pixels to transform from @p input to * @p output. - * @return JXL_TRUE on success, JXL_FALSE on failure. + * @return ::JXL_TRUE on success, ::JXL_FALSE on failure. */ typedef JXL_BOOL (*jpegxl_cms_run_func)(void* user_data, size_t thread, const float* input_buffer, @@ -207,6 +225,11 @@ typedef void (*jpegxl_cms_destroy_func)(void*); * @enddot */ typedef struct { + /** CMS-specific data that will be passed to @ref set_fields_from_icc. */ + void* set_fields_data; + /** Populates a @ref JxlColorEncoding from an ICC profile. */ + jpegxl_cms_set_fields_from_icc_func set_fields_from_icc; + /** CMS-specific data that will be passed to @ref init. */ void* init_data; /** Prepares a colorspace transform as described in the documentation of @ref @@ -223,7 +246,7 @@ typedef struct { jpegxl_cms_destroy_func destroy; } JxlCmsInterface; -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } #endif diff --git a/media/libjxl/src/lib/include/jxl/codestream_header.h b/media/libjxl/src/lib/include/jxl/codestream_header.h index d126577870..513c92f1dd 100644 --- a/media/libjxl/src/lib/include/jxl/codestream_header.h +++ b/media/libjxl/src/lib/include/jxl/codestream_header.h @@ -4,7 +4,7 @@ * license that can be found in the LICENSE file. */ -/** @addtogroup libjxl_common +/** @addtogroup libjxl_metadata * @{ * @file codestream_header.h * @brief Definitions of structs and enums for the metadata from the JPEG XL @@ -15,12 +15,11 @@ #ifndef JXL_CODESTREAM_HEADER_H_ #define JXL_CODESTREAM_HEADER_H_ +#include #include #include -#include "jxl/types.h" - -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -71,17 +70,8 @@ typedef struct { uint32_t ysize; } JxlPreviewHeader; -/** The intrinsic size header */ -typedef struct { - /** Intrinsic width in pixels */ - uint32_t xsize; - - /** Intrinsic height in pixels */ - uint32_t ysize; -} JxlIntrinsicSizeHeader; - /** The codestream animation header, optionally present in the beginning of - * the codestream, and if it is it applies to all animation frames, unlike + * the codestream, and if it is it applies to all animation frames, unlike @ref * JxlFrameHeader which applies to an individual frame. */ typedef struct { @@ -176,12 +166,12 @@ typedef struct { * it to to the original color profile. The decoder also does not convert to * the target display color profile. To convert the pixel data produced by * the decoder to the original color profile, one of the JxlDecoderGetColor* - * functions needs to be called with @ref JXL_COLOR_PROFILE_TARGET_DATA to get - * the color profile of the decoder output, and then an external CMS can be - * used for conversion. - * Note that for lossy compression, this should be set to false for most use - * cases, and if needed, the image should be converted to the original color - * profile after decoding, as described above. + * functions needs to be called with + * ::JXL_COLOR_PROFILE_TARGET_DATA to get the color profile of the decoder + * output, and then an external CMS can be used for conversion. Note that for + * lossy compression, this should be set to false for most use cases, and if + * needed, the image should be converted to the original color profile after + * decoding, as described above. */ JXL_BOOL uses_original_profile; @@ -204,17 +194,19 @@ typedef struct { * grayscale data, or 3 for colored data. This count does not include * the alpha channel or other extra channels. To check presence of an alpha * channel, such as in the case of RGBA color, check alpha_bits != 0. - * If and only if this is 1, the JxlColorSpace in the JxlColorEncoding is - * JXL_COLOR_SPACE_GRAY. + * If and only if this is 1, the @ref JxlColorSpace in the @ref + * JxlColorEncoding is + * ::JXL_COLOR_SPACE_GRAY. */ uint32_t num_color_channels; /** Number of additional image channels. This includes the main alpha channel, * but can also include additional channels such as depth, additional alpha * channels, spot colors, and so on. Information about the extra channels - * can be queried with JxlDecoderGetExtraChannelInfo. The main alpha channel, - * if it exists, also has its information available in the alpha_bits, - * alpha_exponent_bits and alpha_premultiplied fields in this JxlBasicInfo. + * can be queried with @ref JxlDecoderGetExtraChannelInfo. The main alpha + * channel, if it exists, also has its information available in the + * alpha_bits, alpha_exponent_bits and alpha_premultiplied fields in this @ref + * JxlBasicInfo. */ uint32_t num_extra_channels; @@ -254,7 +246,7 @@ typedef struct { */ uint32_t intrinsic_xsize; - /** Intrinsic heigth of the image. + /** Intrinsic height of the image. * The intrinsic size can be different from the actual size in pixels * (as given by xsize and ysize) and it denotes the recommended dimensions * for displaying the image, i.e. applications are advised to resample the @@ -389,6 +381,8 @@ typedef struct { /** After blending, save the frame as reference frame with this ID (0-3). * Special case: if the frame duration is nonzero, ID 0 means "will not be * referenced in the future". This value is not used for the last frame. + * When encoding, ID 3 is reserved to frames that are generated internally by + * the encoder, and should not be used by applications. */ uint32_t save_as_reference; } JxlLayerInfo; @@ -396,7 +390,8 @@ typedef struct { /** The header of one displayed frame or non-coalesced layer. */ typedef struct { /** How long to wait after rendering in ticks. The duration in seconds of a - * tick is given by tps_numerator and tps_denominator in JxlAnimationHeader. + * tick is given by tps_numerator and tps_denominator in @ref + * JxlAnimationHeader. */ uint32_t duration; @@ -404,9 +399,9 @@ typedef struct { * interpreted from most-significant to least-significant as hour, minute, * second, and frame. If timecode is nonzero, it is strictly larger than that * of a previous frame with nonzero duration. These values are only available - * if have_timecodes in JxlAnimationHeader is JXL_TRUE. - * This value is only used if have_timecodes in JxlAnimationHeader is - * JXL_TRUE. + * if have_timecodes in @ref JxlAnimationHeader is ::JXL_TRUE. + * This value is only used if have_timecodes in @ref JxlAnimationHeader is + * ::JXL_TRUE. */ uint32_t timecode; @@ -429,7 +424,7 @@ typedef struct { JxlLayerInfo layer_info; } JxlFrameHeader; -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } #endif diff --git a/media/libjxl/src/lib/include/jxl/color_encoding.h b/media/libjxl/src/lib/include/jxl/color_encoding.h index b16f6a01ee..14dcdeb0a2 100644 --- a/media/libjxl/src/lib/include/jxl/color_encoding.h +++ b/media/libjxl/src/lib/include/jxl/color_encoding.h @@ -4,7 +4,7 @@ * license that can be found in the LICENSE file. */ -/** @addtogroup libjxl_common +/** @addtogroup libjxl_color * @{ * @file color_encoding.h * @brief Color Encoding definitions used by JPEG XL. @@ -14,9 +14,7 @@ #ifndef JXL_COLOR_ENCODING_H_ #define JXL_COLOR_ENCODING_H_ -#include - -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -24,9 +22,9 @@ extern "C" { typedef enum { /** Tristimulus RGB */ JXL_COLOR_SPACE_RGB, - /** Luminance based, the primaries in JxlColorEncoding must be ignored. This - * value implies that num_color_channels in JxlBasicInfo is 1, any other value - * implies num_color_channels is 3. */ + /** Luminance based, the primaries in @ref JxlColorEncoding must be ignored. + * This value implies that num_color_channels in @ref JxlBasicInfo is 1, any + * other value implies num_color_channels is 3. */ JXL_COLOR_SPACE_GRAY, /** XYB (opsin) color space */ JXL_COLOR_SPACE_XYB, @@ -34,19 +32,19 @@ typedef enum { JXL_COLOR_SPACE_UNKNOWN, } JxlColorSpace; -/** Built-in whitepoints for color encoding. When decoding, the numerical xy - * whitepoint value can be read from the JxlColorEncoding white_point field - * regardless of the enum value. When encoding, enum values except - * JXL_WHITE_POINT_CUSTOM override the numerical fields. Some enum values match - * a subset of CICP (Rec. ITU-T H.273 | ISO/IEC 23091-2:2019(E)), however the - * white point and RGB primaries are separate enums here. +/** Built-in white points for color encoding. When decoding, the numerical xy + * white point value can be read from the @ref JxlColorEncoding white_point + * field regardless of the enum value. When encoding, enum values except + * ::JXL_WHITE_POINT_CUSTOM override the numerical fields. Some enum values + * match a subset of CICP (Rec. ITU-T H.273 | ISO/IEC 23091-2:2019(E)), however + * the white point and RGB primaries are separate enums here. */ typedef enum { /** CIE Standard Illuminant D65: 0.3127, 0.3290 */ JXL_WHITE_POINT_D65 = 1, - /** White point must be read from the JxlColorEncoding white_point field, or - * as ICC profile. This enum value is not an exact match of the corresponding - * CICP value. */ + /** White point must be read from the @ref JxlColorEncoding white_point field, + * or as ICC profile. This enum value is not an exact match of the + * corresponding CICP value. */ JXL_WHITE_POINT_CUSTOM = 2, /** CIE Standard Illuminant E (equal-energy): 1/3, 1/3 */ JXL_WHITE_POINT_E = 10, @@ -55,10 +53,10 @@ typedef enum { } JxlWhitePoint; /** Built-in primaries for color encoding. When decoding, the primaries can be - * read from the JxlColorEncoding primaries_red_xy, primaries_green_xy and + * read from the @ref JxlColorEncoding primaries_red_xy, primaries_green_xy and * primaries_blue_xy fields regardless of the enum value. When encoding, the - * enum values except JXL_PRIMARIES_CUSTOM override the numerical fields. Some - * enum values match a subset of CICP (Rec. ITU-T H.273 | ISO/IEC + * enum values except ::JXL_PRIMARIES_CUSTOM override the numerical fields. + * Some enum values match a subset of CICP (Rec. ITU-T H.273 | ISO/IEC * 23091-2:2019(E)), however the white point and RGB primaries are separate * enums here. */ @@ -66,7 +64,7 @@ typedef enum { /** The CIE xy values of the red, green and blue primaries are: 0.639998686, 0.330010138; 0.300003784, 0.600003357; 0.150002046, 0.059997204 */ JXL_PRIMARIES_SRGB = 1, - /** Primaries must be read from the JxlColorEncoding primaries_red_xy, + /** Primaries must be read from the @ref JxlColorEncoding primaries_red_xy, * primaries_green_xy and primaries_blue_xy fields, or as ICC profile. This * enum value is not an exact match of the corresponding CICP value. */ JXL_PRIMARIES_CUSTOM = 2, @@ -80,7 +78,7 @@ typedef enum { * of CICP (Rec. ITU-T H.273 | ISO/IEC 23091-2:2019(E)) unless specified * otherwise. */ typedef enum { - /** As specified in SMPTE RP 431-2 */ + /** As specified in ITU-R BT.709-6 */ JXL_TRANSFER_FUNCTION_709 = 1, /** None of the other table entries describe the transfer function. */ JXL_TRANSFER_FUNCTION_UNKNOWN = 2, @@ -94,12 +92,12 @@ typedef enum { JXL_TRANSFER_FUNCTION_DCI = 17, /** As specified in Rec. ITU-R BT.2100-1 (HLG) */ JXL_TRANSFER_FUNCTION_HLG = 18, - /** Transfer function follows power law given by the gamma value in + /** Transfer function follows power law given by the gamma value in @ref JxlColorEncoding. Not a CICP value. */ JXL_TRANSFER_FUNCTION_GAMMA = 65535, } JxlTransferFunction; -/** Renderig intent for color encoding, as specified in ISO 15076-1:2010 */ +/** Rendering intent for color encoding, as specified in ISO 15076-1:2010 */ typedef enum { /** vendor-specific */ JXL_RENDERING_INTENT_PERCEPTUAL = 0, @@ -118,18 +116,18 @@ typedef struct { */ JxlColorSpace color_space; - /** Built-in white point. If this value is JXL_WHITE_POINT_CUSTOM, must - * use the numerical whitepoint values from white_point_xy. + /** Built-in white point. If this value is ::JXL_WHITE_POINT_CUSTOM, must + * use the numerical white point values from white_point_xy. */ JxlWhitePoint white_point; /** Numerical whitepoint values in CIE xy space. */ double white_point_xy[2]; - /** Built-in RGB primaries. If this value is JXL_PRIMARIES_CUSTOM, must + /** Built-in RGB primaries. If this value is ::JXL_PRIMARIES_CUSTOM, must * use the numerical primaries values below. This field and the custom values * below are unused and must be ignored if the color space is - * JXL_COLOR_SPACE_GRAY or JXL_COLOR_SPACE_XYB. + * ::JXL_COLOR_SPACE_GRAY or ::JXL_COLOR_SPACE_XYB. */ JxlPrimaries primaries; @@ -145,7 +143,8 @@ typedef struct { /** Transfer function if have_gamma is 0 */ JxlTransferFunction transfer_function; - /** Gamma value used when transfer_function is JXL_TRANSFER_FUNCTION_GAMMA + /** Gamma value used when transfer_function is @ref + * JXL_TRANSFER_FUNCTION_GAMMA */ double gamma; @@ -153,7 +152,7 @@ typedef struct { JxlRenderingIntent rendering_intent; } JxlColorEncoding; -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } #endif diff --git a/media/libjxl/src/lib/include/jxl/compressed_icc.h b/media/libjxl/src/lib/include/jxl/compressed_icc.h new file mode 100644 index 0000000000..10232794b6 --- /dev/null +++ b/media/libjxl/src/lib/include/jxl/compressed_icc.h @@ -0,0 +1,75 @@ +/* Copyright (c) the JPEG XL Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +/** @addtogroup libjxl_metadata + * @{ + * @file compressed_icc.h + * @brief Utility functions to compress and decompress ICC streams. + */ + +#ifndef JXL_COMPRESSED_ICC_H_ +#define JXL_COMPRESSED_ICC_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Allocates a buffer using the memory manager, fills it with a compressed + * representation of an ICC profile, returns the result through @c output_buffer + * and indicates its size through @c output_size. + * + * The result must be freed using the memory manager once it is not of any more + * use. + * + * @param[in] memory_manager Pointer to a JxlMemoryManager. + * @param[in] icc Pointer to a buffer containing the uncompressed ICC profile. + * @param[in] icc_size Size of the buffer containing the ICC profile. + * @param[out] compressed_icc Will be set to a pointer to the buffer containing + * the result. + * @param[out] compressed_icc_size Will be set to the size of the buffer + * containing the result. + * @return Whether compressing the profile was successful. + */ +JXL_EXPORT JXL_BOOL JxlICCProfileEncode(const JxlMemoryManager* memory_manager, + const uint8_t* icc, size_t icc_size, + uint8_t** compressed_icc, + size_t* compressed_icc_size); + +/** + * Allocates a buffer using the memory manager, fills it with the decompressed + * version of the ICC profile in @c compressed_icc, returns the result through + * @c output_buffer and indicates its size through @c output_size. + * + * The result must be freed using the memory manager once it is not of any more + * use. + * + * @param[in] memory_manager Pointer to a JxlMemoryManager. + * @param[in] compressed_icc Pointer to a buffer containing the compressed ICC + * profile. + * @param[in] compressed_icc_size Size of the buffer containing the compressed + * ICC profile. + * @param[out] icc Will be set to a pointer to the buffer containing the result. + * @param[out] icc_size Will be set to the size of the buffer containing the + * result. + * @return Whether decompressing the profile was successful. + */ +JXL_EXPORT JXL_BOOL JxlICCProfileDecode(const JxlMemoryManager* memory_manager, + const uint8_t* compressed_icc, + size_t compressed_icc_size, + uint8_t** icc, size_t* icc_size); + +#ifdef __cplusplus +} +#endif + +#endif /* JXL_COMPRESSED_ICC_H_ */ + +/** @} */ diff --git a/media/libjxl/src/lib/include/jxl/decode.h b/media/libjxl/src/lib/include/jxl/decode.h index 66820bfcd9..69c00243d6 100644 --- a/media/libjxl/src/lib/include/jxl/decode.h +++ b/media/libjxl/src/lib/include/jxl/decode.h @@ -13,18 +13,18 @@ #ifndef JXL_DECODE_H_ #define JXL_DECODE_H_ +#include +#include +#include +#include +#include +#include +#include +#include // TODO(eustas): remove before v1.0 #include #include -#include "jxl/codestream_header.h" -#include "jxl/color_encoding.h" -#include "jxl/jxl_export.h" -#include "jxl/memory_manager.h" -#include "jxl/parallel_runner.h" -#include "jxl/types.h" -#include "jxl/version.h" - -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -66,12 +66,12 @@ typedef enum { * @p size doesn't need to be a full image, only the beginning of the file. * * @return a flag indicating if a JPEG XL signature was found and what type. - * - @ref JXL_SIG_NOT_ENOUGH_BYTES if not enough bytes were passed to + * - ::JXL_SIG_NOT_ENOUGH_BYTES if not enough bytes were passed to * determine if a valid signature is there. - * - @ref JXL_SIG_INVALID if no valid signature found for JPEG XL decoding. - * - @ref JXL_SIG_CODESTREAM if a valid JPEG XL codestream signature was + * - ::JXL_SIG_INVALID if no valid signature found for JPEG XL decoding. + * - ::JXL_SIG_CODESTREAM if a valid JPEG XL codestream signature was * found. - * - @ref JXL_SIG_CONTAINER if a valid JPEG XL container signature was found. + * - ::JXL_SIG_CONTAINER if a valid JPEG XL container signature was found. */ JXL_EXPORT JxlSignature JxlSignatureCheck(const uint8_t* buf, size_t len); @@ -115,7 +115,7 @@ JXL_EXPORT void JxlDecoderDestroy(JxlDecoder* dec); /** * Return value for @ref JxlDecoderProcessInput. - * The values from @ref JXL_DEC_BASIC_INFO onwards are optional informative + * The values from ::JXL_DEC_BASIC_INFO onwards are optional informative * events that can be subscribed to, they are never returned if they * have not been registered with @ref JxlDecoderSubscribeEvents. */ @@ -123,12 +123,12 @@ typedef enum { /** Function call finished successfully, or decoding is finished and there is * nothing more to be done. * - * Note that @ref JxlDecoderProcessInput will return JXL_DEC_SUCCESS if all - * events that were registered with @ref JxlDecoderSubscribeEvents were + * Note that @ref JxlDecoderProcessInput will return ::JXL_DEC_SUCCESS if + * all events that were registered with @ref JxlDecoderSubscribeEvents were * processed, even before the end of the JPEG XL codestream. * * In this case, the return value @ref JxlDecoderReleaseInput will be the same - * as it was at the last signaled event. E.g. if JXL_DEC_FULL_IMAGE was + * as it was at the last signaled event. E.g. if ::JXL_DEC_FULL_IMAGE was * subscribed to, then all bytes from the end of the JPEG XL codestream * (including possible boxes needed for jpeg reconstruction) will be returned * as unprocessed. @@ -151,14 +151,14 @@ typedef enum { * In most cases, @ref JxlDecoderReleaseInput will return no unprocessed bytes * at this event, the only exceptions are if the previously set input ended * within (a) the raw codestream signature, (b) the signature box, (c) a box - * header, or (d) the first 4 bytes of a brob, ftyp, or jxlp box. In any of - * these cases the number of unprocessed bytes is less than 20. + * header, or (d) the first 4 bytes of a `brob`, `ftyp`, or `jxlp` box. In any + * of these cases the number of unprocessed bytes is less than 20. */ JXL_DEC_NEED_MORE_INPUT = 2, /** The decoder is able to decode a preview image and requests setting a * preview output buffer using @ref JxlDecoderSetPreviewOutBuffer. This occurs - * if @ref JXL_DEC_PREVIEW_IMAGE is requested and it is possible to decode a + * if ::JXL_DEC_PREVIEW_IMAGE is requested and it is possible to decode a * preview image from the codestream and the preview out buffer was not yet * set. There is maximum one preview image in a codestream. * In this case, @ref JxlDecoderReleaseInput will return all bytes from the @@ -167,16 +167,6 @@ typedef enum { */ JXL_DEC_NEED_PREVIEW_OUT_BUFFER = 3, - /** The decoder is able to decode a DC image and requests setting a DC output - * buffer using @ref JxlDecoderSetDCOutBuffer. This occurs if @ref - * JXL_DEC_DC_IMAGE is requested and it is possible to decode a DC image from - * the codestream and the DC out buffer was not yet set. This event re-occurs - * for new frames if there are multiple animation frames. - * @deprecated The DC feature in this form will be removed. For progressive - * rendering, @ref JxlDecoderFlushImage should be used. - */ - JXL_DEC_NEED_DC_OUT_BUFFER = 4, - /** The decoder requests an output buffer to store the full resolution image, * which can be set with @ref JxlDecoderSetImageOutBuffer or with @ref * JxlDecoderSetImageOutCallback. This event re-occurs for new frames if @@ -189,13 +179,13 @@ typedef enum { /** The JPEG reconstruction buffer is too small for reconstructed JPEG * codestream to fit. @ref JxlDecoderSetJPEGBuffer must be called again to * make room for remaining bytes. This event may occur multiple times - * after @ref JXL_DEC_JPEG_RECONSTRUCTION. + * after ::JXL_DEC_JPEG_RECONSTRUCTION. */ JXL_DEC_JPEG_NEED_MORE_OUTPUT = 6, /** The box contents output buffer is too small. @ref JxlDecoderSetBoxBuffer * must be called again to make room for remaining bytes. This event may occur - * multiple times after @ref JXL_DEC_BOX. + * multiple times after ::JXL_DEC_BOX. */ JXL_DEC_BOX_NEED_MORE_OUTPUT = 7, @@ -208,20 +198,10 @@ typedef enum { */ JXL_DEC_BASIC_INFO = 0x40, - /** Informative event by @ref JxlDecoderProcessInput - * "JxlDecoderProcessInput": User extensions of the codestream header. This - * event occurs max once per image and always later than @ref - * JXL_DEC_BASIC_INFO and earlier than any pixel data. - * - * @deprecated The decoder no longer returns this, the header extensions, - * if any, are available at the JXL_DEC_BASIC_INFO event. - */ - JXL_DEC_EXTENSIONS = 0x80, - /** Informative event by @ref JxlDecoderProcessInput * "JxlDecoderProcessInput": Color encoding or ICC profile from the * codestream header. This event occurs max once per image and always later - * than @ref JXL_DEC_BASIC_INFO and earlier than any pixel data. + * than ::JXL_DEC_BASIC_INFO and earlier than any pixel data. * In this case, @ref JxlDecoderReleaseInput will return all bytes from the * end of the image header (which is the start of the first frame) as * unprocessed. @@ -232,7 +212,7 @@ typedef enum { * "JxlDecoderProcessInput": Preview image, a small frame, decoded. This * event can only happen if the image has a preview frame encoded. This event * occurs max once for the codestream and always later than @ref - * JXL_DEC_COLOR_ENCODING and before @ref JXL_DEC_FRAME. + * JXL_DEC_COLOR_ENCODING and before ::JXL_DEC_FRAME. * In this case, @ref JxlDecoderReleaseInput will return all bytes from the * end of the preview frame as unprocessed. */ @@ -243,46 +223,30 @@ typedef enum { * JxlDecoderGetFrameHeader can be used at this point. A note on frames: * a JPEG XL image can have internal frames that are not intended to be * displayed (e.g. used for compositing a final frame), but this only returns - * displayed frames, unless @ref JxlDecoderSetCoalescing was set to JXL_FALSE: - * in that case, the individual layers are returned, without blending. Note - * that even when coalescing is disabled, only frames of type kRegularFrame - * are returned; frames of type kReferenceOnly and kLfFrame are always for - * internal purposes only and cannot be accessed. A displayed frame either has - * an animation duration or is the only or last frame in the image. This event - * occurs max once per displayed frame, always later than @ref - * JXL_DEC_COLOR_ENCODING, and always earlier than any pixel data. While - * JPEG XL supports encoding a single frame as the composition of multiple - * internal sub-frames also called frames, this event is not indicated for the - * internal frames. - * In this case, @ref JxlDecoderReleaseInput will return all bytes from the - * end of the frame header (including ToC) as unprocessed. + * displayed frames, unless @ref JxlDecoderSetCoalescing was set to @ref + * JXL_FALSE "JXL_FALSE": in that case, the individual layers are returned, + * without blending. Note that even when coalescing is disabled, only frames + * of type kRegularFrame are returned; frames of type kReferenceOnly + * and kLfFrame are always for internal purposes only and cannot be accessed. + * A displayed frame either has an animation duration or is the only or last + * frame in the image. This event occurs max once per displayed frame, always + * later than ::JXL_DEC_COLOR_ENCODING, and always earlier than any pixel + * data. While JPEG XL supports encoding a single frame as the composition of + * multiple internal sub-frames also called frames, this event is not + * indicated for the internal frames. In this case, @ref + * JxlDecoderReleaseInput will return all bytes from the end of the frame + * header (including ToC) as unprocessed. */ JXL_DEC_FRAME = 0x400, - /** Informative event by @ref JxlDecoderProcessInput - * "JxlDecoderProcessInput": DC image, 8x8 sub-sampled frame, decoded. It is - * not guaranteed that the decoder will always return DC separately, but when - * it does it will do so before outputting the full frame. @ref - * JxlDecoderSetDCOutBuffer must be used after getting the basic image - * information to be able to get the DC pixels, if not this return status only - * indicates we're past this point in the codestream. This event occurs max - * once per frame and always later than @ref JXL_DEC_FRAME and other header - * events and earlier than full resolution pixel data. - * - * @deprecated The DC feature in this form will be removed. For progressive - * rendering, @ref JxlDecoderFlushImage should be used. - */ - JXL_DEC_DC_IMAGE = 0x800, - /** Informative event by @ref JxlDecoderProcessInput * "JxlDecoderProcessInput": full frame (or layer, in case coalescing is * disabled) is decoded. @ref JxlDecoderSetImageOutBuffer must be used after * getting the basic image information to be able to get the image pixels, if * not this return status only indicates we're past this point in the - * codestream. This event occurs max once per frame and always later than @ref - * JXL_DEC_DC_IMAGE. + * codestream. This event occurs max once per frame. * In this case, @ref JxlDecoderReleaseInput will return all bytes from the - * end of the frame (or if @ref JXL_DEC_JPEG_RECONSTRUCTION is subscribed to, + * end of the frame (or if ::JXL_DEC_JPEG_RECONSTRUCTION is subscribed to, * from the end of the last box that is needed for jpeg reconstruction) as * unprocessed. */ @@ -295,9 +259,9 @@ typedef enum { * is set a byte stream identical to the JPEG codestream used to encode the * image will be written to the JPEG reconstruction buffer instead of pixels * to the image out buffer. This event occurs max once per image and always - * before @ref JXL_DEC_FULL_IMAGE. + * before ::JXL_DEC_FULL_IMAGE. * In this case, @ref JxlDecoderReleaseInput will return all bytes from the - * end of the 'jbrd' box as unprocessed. + * end of the `jbrd` box as unprocessed. */ JXL_DEC_JPEG_RECONSTRUCTION = 0x2000, @@ -326,12 +290,13 @@ typedef enum { * * The buffer set with @ref JxlDecoderSetBoxBuffer must be set again for each * next box to be obtained, or can be left unset to skip outputting this box. - * The output buffer contains the full box data when the next @ref JXL_DEC_BOX - * event or @ref JXL_DEC_SUCCESS occurs. @ref JXL_DEC_BOX occurs for all - * boxes, including non-metadata boxes such as the signature box or codestream - * boxes. To check whether the box is a metadata type for respectively EXIF, - * XMP or JUMBF, use @ref JxlDecoderGetBoxType and check for types "Exif", - * "xml " and "jumb" respectively. + * The output buffer contains the full box data when the + * ::JXL_DEC_BOX_COMPLETE (if subscribed to) or subsequent ::JXL_DEC_SUCCESS + * or ::JXL_DEC_BOX event occurs. ::JXL_DEC_BOX occurs for all boxes, + * including non-metadata boxes such as the signature box or codestream boxes. + * To check whether the box is a metadata type for respectively EXIF, XMP or + * JUMBF, use @ref JxlDecoderGetBoxType and check for types "Exif", "xml " and + * "jumb" respectively. * * In this case, @ref JxlDecoderReleaseInput will return all bytes from the * start of the box header as unprocessed. @@ -354,8 +319,54 @@ typedef enum { * unprocessed. */ JXL_DEC_FRAME_PROGRESSION = 0x8000, + + /** The box being decoded is now complete. This is only emitted if a buffer + * was set for the box. + */ + JXL_DEC_BOX_COMPLETE = 0x10000, } JxlDecoderStatus; +/** Types of progressive detail. + * Setting a progressive detail with value N implies all progressive details + * with smaller or equal value. Currently only the following level of + * progressive detail is implemented: + * - @ref kDC (which implies kFrames) + * - @ref kLastPasses (which implies @ref kDC and @ref kFrames) + * - @ref kPasses (which implies @ref kLastPasses, kDC and @ref kFrames) + */ +typedef enum { + /** + * after completed kRegularFrames + */ + kFrames = 0, + /** + * after completed DC (1:8) + */ + kDC = 1, + /** + * after completed AC passes that are the last pass for their resolution + * target. + */ + kLastPasses = 2, + /** + * after completed AC passes that are not the last pass for their resolution + * target. + */ + kPasses = 3, + /** + * during DC frame when lower resolution are completed (1:32, 1:16) + */ + kDCProgressive = 4, + /** + * after completed groups + */ + kDCGroups = 5, + /** + * after completed groups + */ + kGroups = 6, +} JxlProgressiveDetail; + /** Rewinds decoder to the beginning. The same input must be given again from * the beginning of the file and the decoder will emit events from the beginning * again. When rewinding (as opposed to @ref JxlDecoderReset), the decoder can @@ -363,8 +374,8 @@ typedef enum { * more efficiently with @ref JxlDecoderSkipFrames. Settings such as parallel * runner or subscribed events are kept. After rewind, @ref * JxlDecoderSubscribeEvents can be used again, and it is feasible to leave out - * events that were already handled before, such as @ref JXL_DEC_BASIC_INFO - * and @ref JXL_DEC_COLOR_ENCODING, since they will provide the same information + * events that were already handled before, such as ::JXL_DEC_BASIC_INFO + * and ::JXL_DEC_COLOR_ENCODING, since they will provide the same information * as before. * The difference to @ref JxlDecoderReset is that some state is kept, namely * settings set by a call to @@ -385,14 +396,14 @@ JXL_EXPORT void JxlDecoderRewind(JxlDecoder* dec); * the input, but will not output the frame events. It can be more efficient * when skipping frames, and even more so when using this after @ref * JxlDecoderRewind. If the decoder is already processing a frame (could - * have emitted @ref JXL_DEC_FRAME but not yet @ref JXL_DEC_FULL_IMAGE), it + * have emitted ::JXL_DEC_FRAME but not yet ::JXL_DEC_FULL_IMAGE), it * starts skipping from the next frame. If the amount is larger than the amount * of frames remaining in the image, all remaining frames are skipped. Calling * this function multiple times adds the amount to skip to the already existing * amount. * * A frame here is defined as a frame that without skipping emits events such - * as @ref JXL_DEC_FRAME and @ref JXL_DEC_FULL_IMAGE, frames that are internal + * as ::JXL_DEC_FRAME and ::JXL_DEC_FULL_IMAGE, frames that are internal * to the file format but are not rendered as part of an animation, or are not * the final still frame of a still image, are not counted. * @@ -403,35 +414,18 @@ JXL_EXPORT void JxlDecoderSkipFrames(JxlDecoder* dec, size_t amount); /** * Skips processing the current frame. Can be called after frame processing - * already started, signaled by a @ref JXL_DEC_NEED_IMAGE_OUT_BUFFER event, - * but before the corrsponding @ref JXL_DEC_FULL_IMAGE event. The next signaled - * event will be another @ref JXL_DEC_FRAME, or @ref JXL_DEC_SUCCESS if there + * already started, signaled by a ::JXL_DEC_NEED_IMAGE_OUT_BUFFER event, + * but before the corresponding ::JXL_DEC_FULL_IMAGE event. The next signaled + * event will be another ::JXL_DEC_FRAME, or ::JXL_DEC_SUCCESS if there * are no more frames. If pixel data is required from the already processed part * of the frame, @ref JxlDecoderFlushImage must be called before this. * * @param dec decoder object - * @return @ref JXL_DEC_SUCCESS if there is a frame to skip, and @ref + * @return ::JXL_DEC_SUCCESS if there is a frame to skip, and @ref * JXL_DEC_ERROR if the function was not called during frame processing. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSkipCurrentFrame(JxlDecoder* dec); -/** - * Get the default pixel format for this decoder. - * - * Requires that the decoder can produce JxlBasicInfo. - * - * @param dec @ref JxlDecoder to query when creating the recommended pixel - * format. - * @param format JxlPixelFormat to populate with the recommended settings for - * the data loaded into this decoder. - * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_NEED_MORE_INPUT if the - * basic info isn't yet available, and @ref JXL_DEC_ERROR otherwise. - * - * DEPRECATED: this function will be removed in the future. - */ -JXL_EXPORT JXL_DEPRECATED JxlDecoderStatus -JxlDecoderDefaultPixelFormat(const JxlDecoder* dec, JxlPixelFormat* format); - /** * Set the parallel runner for multithreading. May only be set before starting * decoding. @@ -441,7 +435,7 @@ JxlDecoderDefaultPixelFormat(const JxlDecoder* dec, JxlPixelFormat* format); * be NULL to use the default, single-threaded, runner. A multithreaded * runner should be set to reach fast performance. * @param parallel_runner_opaque opaque pointer for parallel_runner. - * @return @ref JXL_DEC_SUCCESS if the runner was set, @ref JXL_DEC_ERROR + * @return ::JXL_DEC_SUCCESS if the runner was set, ::JXL_DEC_ERROR * otherwise (the previous runner remains set). */ JXL_EXPORT JxlDecoderStatus @@ -465,7 +459,7 @@ JxlDecoderSetParallelRunner(JxlDecoder* dec, JxlParallelRunner parallel_runner, */ JXL_EXPORT size_t JxlDecoderSizeHintBasicInfo(const JxlDecoder* dec); -/** Select for which informative events, i.e. @ref JXL_DEC_BASIC_INFO, etc., the +/** Select for which informative events, i.e. ::JXL_DEC_BASIC_INFO, etc., the * decoder should return with a status. It is not required to subscribe to any * events, data can still be requested from the decoder as soon as it available. * By default, the decoder is subscribed to no events (events_wanted == 0), and @@ -475,7 +469,7 @@ JXL_EXPORT size_t JxlDecoderSizeHintBasicInfo(const JxlDecoder* dec); * * @param dec decoder object * @param events_wanted bitfield of desired events. - * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise. + * @return ::JXL_DEC_SUCCESS if no error, ::JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSubscribeEvents(JxlDecoder* dec, int events_wanted); @@ -485,14 +479,14 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSubscribeEvents(JxlDecoder* dec, * indicating that the decoder must perform a rotation and/or * mirroring to the encoded image data. * - * - If skip_reorientation is JXL_FALSE (the default): the decoder + * - If skip_reorientation is ::JXL_FALSE (the default): the decoder * will apply the transformation from the orientation setting, hence * rendering the image according to its specified intent. When - * producing a JxlBasicInfo, the decoder will always set the + * producing a @ref JxlBasicInfo, the decoder will always set the * orientation field to JXL_ORIENT_IDENTITY (matching the returned * pixel data) and also align xsize and ysize so that they correspond * to the width and the height of the returned pixel data. - * - If skip_reorientation is JXL_TRUE: the decoder will skip + * - If skip_reorientation is ::JXL_TRUE "JXL_TRUE": the decoder will skip * applying the transformation from the orientation setting, returning * the image in the as-in-bitstream pixeldata orientation. * This may be faster to decode since the decoder doesn't have to apply the @@ -509,17 +503,18 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSubscribeEvents(JxlDecoder* dec, * * @param dec decoder object * @param skip_reorientation JXL_TRUE to enable, JXL_FALSE to disable. - * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise. + * @return ::JXL_DEC_SUCCESS if no error, ::JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetKeepOrientation(JxlDecoder* dec, JXL_BOOL skip_reorientation); /** * Enables or disables preserving of associated alpha channels. If - * unpremul_alpha is set to JXL_FALSE then for associated alpha channel, the - * pixel data is returned with premultiplied colors. If it is set to JXL_TRUE, - * The colors will be unpremultiplied based on the alpha channel. This function - * has no effect if the image does not have an associated alpha channel. + * unpremul_alpha is set to ::JXL_FALSE then for associated alpha channel, + * the pixel data is returned with premultiplied colors. If it is set to @ref + * JXL_TRUE, The colors will be unpremultiplied based on the alpha channel. This + * function has no effect if the image does not have an associated alpha + * channel. * * By default, this option is disabled, and the returned pixel data "as is". * @@ -527,20 +522,20 @@ JxlDecoderSetKeepOrientation(JxlDecoder* dec, JXL_BOOL skip_reorientation); * * @param dec decoder object * @param unpremul_alpha JXL_TRUE to enable, JXL_FALSE to disable. - * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise. + * @return ::JXL_DEC_SUCCESS if no error, ::JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetUnpremultiplyAlpha(JxlDecoder* dec, JXL_BOOL unpremul_alpha); /** Enables or disables rendering spot colors. By default, spot colors * are rendered, which is OK for viewing the decoded image. If render_spotcolors - * is JXL_FALSE, then spot colors are not rendered, and have to be retrieved - * separately using @ref JxlDecoderSetExtraChannelBuffer. This is useful for - * e.g. printing applications. + * is ::JXL_FALSE, then spot colors are not rendered, and have to be + * retrieved separately using @ref JxlDecoderSetExtraChannelBuffer. This is + * useful for e.g. printing applications. * * @param dec decoder object * @param render_spotcolors JXL_TRUE to enable (default), JXL_FALSE to disable. - * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise. + * @return ::JXL_DEC_SUCCESS if no error, ::JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetRenderSpotcolors(JxlDecoder* dec, JXL_BOOL render_spotcolors); @@ -556,7 +551,7 @@ JxlDecoderSetRenderSpotcolors(JxlDecoder* dec, JXL_BOOL render_spotcolors); * @param dec decoder object * @param coalescing JXL_TRUE to enable coalescing (default), JXL_FALSE to * disable it. - * @return @ref JXL_DEC_SUCCESS if no error, @ref JXL_DEC_ERROR otherwise. + * @return ::JXL_DEC_SUCCESS if no error, ::JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetCoalescing(JxlDecoder* dec, JXL_BOOL coalescing); @@ -573,34 +568,32 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetCoalescing(JxlDecoder* dec, * * The returned status indicates whether the decoder needs more input bytes, or * more output buffer for a certain type of output data. No matter what the - * returned status is (other than @ref JXL_DEC_ERROR), new information, such + * returned status is (other than ::JXL_DEC_ERROR), new information, such * as @ref JxlDecoderGetBasicInfo, may have become available after this call. - * When the return value is not @ref JXL_DEC_ERROR or @ref JXL_DEC_SUCCESS, the + * When the return value is not ::JXL_DEC_ERROR or ::JXL_DEC_SUCCESS, the * decoding requires more @ref JxlDecoderProcessInput calls to continue. * * @param dec decoder object - * @return @ref JXL_DEC_SUCCESS when decoding finished and all events handled. + * @return ::JXL_DEC_SUCCESS when decoding finished and all events handled. * If you still have more unprocessed input data anyway, then you can still * continue by using @ref JxlDecoderSetInput and calling @ref * JxlDecoderProcessInput again, similar to handling @ref - * JXL_DEC_NEED_MORE_INPUT. @ref JXL_DEC_SUCCESS can occur instead of @ref + * JXL_DEC_NEED_MORE_INPUT. ::JXL_DEC_SUCCESS can occur instead of @ref * JXL_DEC_NEED_MORE_INPUT when, for example, the input data ended right at * the boundary of a box of the container format, all essential codestream * boxes were already decoded, but extra metadata boxes are still present in * the next data. @ref JxlDecoderProcessInput cannot return success if all * codestream boxes have not been seen yet. - * @return @ref JXL_DEC_ERROR when decoding failed, e.g. invalid codestream. + * @return ::JXL_DEC_ERROR when decoding failed, e.g. invalid codestream. * TODO(lode): document the input data mechanism - * @return @ref JXL_DEC_NEED_MORE_INPUT when more input data is necessary. - * @return @ref JXL_DEC_BASIC_INFO when basic info such as image dimensions is + * @return ::JXL_DEC_NEED_MORE_INPUT when more input data is necessary. + * @return ::JXL_DEC_BASIC_INFO when basic info such as image dimensions is * available and this informative event is subscribed to. - * @return @ref JXL_DEC_COLOR_ENCODING when color profile information is + * @return ::JXL_DEC_COLOR_ENCODING when color profile information is * available and this informative event is subscribed to. - * @return @ref JXL_DEC_PREVIEW_IMAGE when preview pixel information is + * @return ::JXL_DEC_PREVIEW_IMAGE when preview pixel information is * available and output in the preview buffer. - * @return @ref JXL_DEC_DC_IMAGE when DC pixel information (8x8 downscaled - * version of the image) is available and output is in the DC buffer. - * @return @ref JXL_DEC_FULL_IMAGE when all pixel information at highest detail + * @return ::JXL_DEC_FULL_IMAGE when all pixel information at highest detail * is available and has been output in the pixel buffer. */ JXL_EXPORT JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec); @@ -616,8 +609,8 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec); * @param dec decoder object * @param data pointer to next bytes to read from * @param size amount of bytes available starting from data - * @return @ref JXL_DEC_ERROR if input was already set without releasing or @ref - * JxlDecoderCloseInput was already called, @ref JXL_DEC_SUCCESS otherwise. + * @return ::JXL_DEC_ERROR if input was already set without releasing or @ref + * JxlDecoderCloseInput was already called, ::JXL_DEC_SUCCESS otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetInput(JxlDecoder* dec, const uint8_t* data, @@ -630,17 +623,17 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetInput(JxlDecoder* dec, * whenever any input is already set and new input needs to be added with @ref * JxlDecoderSetInput, but is not required before @ref JxlDecoderDestroy or @ref * JxlDecoderReset. Calling @ref JxlDecoderReleaseInput when no input is set is - * not an error and returns 0. + * not an error and returns `0`. * * @param dec decoder object * @return The amount of bytes the decoder has not yet processed that are still - * remaining in the data set by @ref JxlDecoderSetInput, or 0 if no input is - * set or @ref JxlDecoderReleaseInput was already called. For a next call - * to @ref JxlDecoderProcessInput, the buffer must start with these - * unprocessed bytes. From this value it is possible to infer the position - * of certain JPEG XL codestream elements (e.g. end of headers, frame - * start/end). See the documentation of individual values of @ref - * JxlDecoderStatus for more information. + * remaining in the data set by @ref JxlDecoderSetInput, or `0` if no input + * is set or @ref JxlDecoderReleaseInput was already called. For a next call to + * @ref JxlDecoderProcessInput, the buffer must start with these unprocessed + * bytes. From this value it is possible to infer the position of certain JPEG + * XL codestream elements (e.g. end of headers, frame start/end). See the + * documentation of individual values of @ref JxlDecoderStatus for more + * information. */ JXL_EXPORT size_t JxlDecoderReleaseInput(JxlDecoder* dec); @@ -649,9 +642,9 @@ JXL_EXPORT size_t JxlDecoderReleaseInput(JxlDecoder* dec); * will be called. This function allows the decoder to determine correctly if it * should return success, need more input or error in certain cases. For * backwards compatibility with a previous version of the API, using this - * function is optional when not using the @ref JXL_DEC_BOX event (the decoder + * function is optional when not using the ::JXL_DEC_BOX event (the decoder * is able to determine the end of the image frames without marking the end), - * but using this function is required when using @ref JXL_DEC_BOX for getting + * but using this function is required when using ::JXL_DEC_BOX for getting * metadata box contents. This function does not replace @ref * JxlDecoderReleaseInput, that function should still be called if its return * value is needed. @@ -671,8 +664,8 @@ JXL_EXPORT void JxlDecoderCloseInput(JxlDecoder* dec); * @param dec decoder object * @param info struct to copy the information into, or NULL to only check * whether the information is available through the return value. - * @return @ref JXL_DEC_SUCCESS if the value is available, @ref - * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR + * @return ::JXL_DEC_SUCCESS if the value is available, @ref + * JXL_DEC_NEED_MORE_INPUT if not yet available, ::JXL_DEC_ERROR * in case of other error conditions. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetBasicInfo(const JxlDecoder* dec, @@ -680,14 +673,14 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetBasicInfo(const JxlDecoder* dec, /** * Outputs information for extra channel at the given index. The index must be - * smaller than num_extra_channels in the associated JxlBasicInfo. + * smaller than num_extra_channels in the associated @ref JxlBasicInfo. * * @param dec decoder object * @param index index of the extra channel to query. * @param info struct to copy the information into, or NULL to only check * whether the information is available through the return value. - * @return @ref JXL_DEC_SUCCESS if the value is available, @ref - * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR + * @return ::JXL_DEC_SUCCESS if the value is available, @ref + * JXL_DEC_NEED_MORE_INPUT if not yet available, ::JXL_DEC_ERROR * in case of other error conditions. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelInfo( @@ -695,16 +688,16 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelInfo( /** * Outputs name for extra channel at the given index in UTF-8. The index must be - * smaller than num_extra_channels in the associated JxlBasicInfo. The buffer - * for name must have at least name_length + 1 bytes allocated, gotten from - * the associated JxlExtraChannelInfo. + * smaller than `num_extra_channels` in the associated @ref JxlBasicInfo. The + * buffer for name must have at least `name_length + 1` bytes allocated, gotten + * from the associated @ref JxlExtraChannelInfo. * * @param dec decoder object * @param index index of the extra channel to query. * @param name buffer to copy the name into * @param size size of the name buffer in bytes - * @return @ref JXL_DEC_SUCCESS if the value is available, @ref - * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR + * @return ::JXL_DEC_SUCCESS if the value is available, @ref + * JXL_DEC_NEED_MORE_INPUT if not yet available, ::JXL_DEC_ERROR * in case of other error conditions. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelName(const JxlDecoder* dec, @@ -734,7 +727,7 @@ typedef enum { * It is often possible to use @ref JxlDecoderGetColorAsICCProfile as an * alternative anyway. The following scenarios are possible: * - The JPEG XL image has an attached ICC Profile, in that case, the encoded - * structured data is not available, this function will return an error + * structured data is not available and this function will return an error * status. @ref JxlDecoderGetColorAsICCProfile should be called instead. * - The JPEG XL image has an encoded structured color profile, and it * represents an RGB or grayscale color space. This function will return it. @@ -743,29 +736,40 @@ typedef enum { * represented, the ICC profile may be a close approximation. It is also not * always feasible to deduce from an ICC profile which named color space it * exactly represents, if any, as it can represent any arbitrary space. + * HDR color spaces such as those using PQ and HLG are also potentially + * problematic, in that: while ICC profiles can encode a transfer function + * that happens to approximate those of PQ and HLG (HLG for only one given + * system gamma at a time, and necessitating a 3D LUT if gamma is to be + * different from `1`), they cannot (before ICCv4.4) semantically signal that + * this is the color space that they represent. Therefore, they will + * typically not actually be interpreted as representing an HDR color space. + * This is especially detrimental to PQ which will then be interpreted as if + * the maximum signal value represented SDR white instead of 10000 cd/m^2, + * meaning that the image will be displayed two orders of magnitude (5-7 EV) + * too dim. * - The JPEG XL image has an encoded structured color profile, and it * indicates an unknown or xyb color space. In that case, @ref * JxlDecoderGetColorAsICCProfile is not available. * - * When rendering an image on a system that supports ICC profiles, @ref - * JxlDecoderGetColorAsICCProfile should be used first. When rendering - * for a specific color space, possibly indicated in the JPEG XL - * image, @ref JxlDecoderGetColorAsEncodedProfile should be used first. + * When rendering an image on a system where ICC-based color management is used, + * @ref JxlDecoderGetColorAsICCProfile should generally be used first as it will + * return a ready-to-use profile (with the aforementioned caveat about HDR). + * When knowledge about the nominal color space is desired if available, @ref + * JxlDecoderGetColorAsEncodedProfile should be used first. * * @param dec decoder object - * @param unused_format deprecated, can be NULL * @param target whether to get the original color profile from the metadata * or the color profile of the decoded pixels. * @param color_encoding struct to copy the information into, or NULL to only * check whether the information is available through the return value. - * @return @ref JXL_DEC_SUCCESS if the data is available and returned, @ref - * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR in + * @return ::JXL_DEC_SUCCESS if the data is available and returned, @ref + * JXL_DEC_NEED_MORE_INPUT if not yet available, ::JXL_DEC_ERROR in * case the encoded structured color profile does not exist in the * codestream. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetColorAsEncodedProfile( - const JxlDecoder* dec, const JxlPixelFormat* unused_format, - JxlColorProfileTarget target, JxlColorEncoding* color_encoding); + const JxlDecoder* dec, JxlColorProfileTarget target, + JxlColorEncoding* color_encoding); /** * Outputs the size in bytes of the ICC profile returned by @ref @@ -779,20 +783,18 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetColorAsEncodedProfile( * depending of what is encoded in the codestream. * * @param dec decoder object - * @param unused_format deprecated, can be NULL * @param target whether to get the original color profile from the metadata * or the color profile of the decoded pixels. * @param size variable to output the size into, or NULL to only check the * return status. - * @return @ref JXL_DEC_SUCCESS if the ICC profile is available, @ref + * @return ::JXL_DEC_SUCCESS if the ICC profile is available, @ref * JXL_DEC_NEED_MORE_INPUT if the decoder has not yet received enough * input data to determine whether an ICC profile is available or what its - * size is, @ref JXL_DEC_ERROR in case the ICC profile is not available and + * size is, ::JXL_DEC_ERROR in case the ICC profile is not available and * cannot be generated. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetICCProfileSize( - const JxlDecoder* dec, const JxlPixelFormat* unused_format, - JxlColorProfileTarget target, size_t* size); + const JxlDecoder* dec, JxlColorProfileTarget target, size_t* size); /** * Outputs ICC profile if available. The profile is only available if @ref @@ -800,51 +802,27 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetICCProfileSize( * at least as many bytes as given by @ref JxlDecoderGetICCProfileSize. * * @param dec decoder object - * @param unused_format deprecated, can be NULL * @param target whether to get the original color profile from the metadata * or the color profile of the decoded pixels. * @param icc_profile buffer to copy the ICC profile into * @param size size of the icc_profile buffer in bytes - * @return @ref JXL_DEC_SUCCESS if the profile was successfully returned is - * available, @ref JXL_DEC_NEED_MORE_INPUT if not yet available, @ref + * @return ::JXL_DEC_SUCCESS if the profile was successfully returned, + * ::JXL_DEC_NEED_MORE_INPUT if not yet available, @ref * JXL_DEC_ERROR if the profile doesn't exist or the output size is not * large enough. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetColorAsICCProfile( - const JxlDecoder* dec, const JxlPixelFormat* unused_format, - JxlColorProfileTarget target, uint8_t* icc_profile, size_t size); + const JxlDecoder* dec, JxlColorProfileTarget target, uint8_t* icc_profile, + size_t size); -/** Sets the color profile to use for @ref JXL_COLOR_PROFILE_TARGET_DATA for the - * special case when the decoder has a choice. This only has effect for a JXL - * image where uses_original_profile is false. If uses_original_profile is true, - * this setting is ignored and the decoder uses a profile related to the image. - * No matter what, the @ref JXL_COLOR_PROFILE_TARGET_DATA must still be queried - * to know the actual data format of the decoded pixels after decoding. - * - * The JXL decoder has no color management system built in, but can convert XYB - * color to any of the ones supported by JxlColorEncoding. Note that if the - * requested color encoding has a narrower gamut, or the white points differ, - * then the resulting image can have significant color distortion. - * - * Can only be set after the @ref JXL_DEC_COLOR_ENCODING event occurred and - * before any other event occurred, and can affect the result of @ref - * JXL_COLOR_PROFILE_TARGET_DATA (but not of @ref - * JXL_COLOR_PROFILE_TARGET_ORIGINAL), so should be used after getting @ref - * JXL_COLOR_PROFILE_TARGET_ORIGINAL but before getting @ref - * JXL_COLOR_PROFILE_TARGET_DATA. The color_encoding must be grayscale if - * num_color_channels from the basic info is 1, RGB if num_color_channels from - * the basic info is 3. - * - * If @ref JxlDecoderSetPreferredColorProfile is not used, then for images for - * which uses_original_profile is false and with ICC color profile, the decoder - * will choose linear sRGB for color images, linear grayscale for grayscale - * images. This function only sets a preference, since for other images the - * decoder has no choice what color profile to use, it is determined by the - * image. +/** Sets the desired output color profile of the decoded image by calling + * @ref JxlDecoderSetOutputColorProfile, passing on @c color_encoding and + * setting @c icc_data to NULL. See @ref JxlDecoderSetOutputColorProfile for + * details. * * @param dec decoder object * @param color_encoding the default color encoding to set - * @return @ref JXL_DEC_SUCCESS if the preference was set successfully, @ref + * @return ::JXL_DEC_SUCCESS if the preference was set successfully, @ref * JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetPreferredColorProfile( @@ -857,12 +835,74 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetPreferredColorProfile( * change from version to version. * @param dec decoder object * @param desired_intensity_target the intended target peak luminance - * @return @ref JXL_DEC_SUCCESS if the preference was set successfully, @ref + * @return ::JXL_DEC_SUCCESS if the preference was set successfully, @ref * JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetDesiredIntensityTarget( JxlDecoder* dec, float desired_intensity_target); +/** + * Sets the desired output color profile of the decoded image either from a + * color encoding or an ICC profile. Valid calls of this function have either @c + * color_encoding or @c icc_data set to NULL and @c icc_size must be `0` if and + * only if @c icc_data is NULL. + * + * Depending on whether a color management system (CMS) has been set the + * behavior is as follows: + * + * If a color management system (CMS) has been set with @ref JxlDecoderSetCms, + * and the CMS supports output to the desired color encoding or ICC profile, + * then it will provide the output in that color encoding or ICC profile. If the + * desired color encoding or the ICC is not supported, then an error will be + * returned. + * + * If no CMS has been set with @ref JxlDecoderSetCms, there are two cases: + * + * (1) Calling this function with a color encoding will convert XYB images to + * the desired color encoding. In this case, if the requested color encoding has + * a narrower gamut, or the white points differ, then the resulting image can + * have significant color distortion. Non-XYB images will not be converted to + * the desired color space. + * + * (2) Calling this function with an ICC profile will result in an error. + * + * If called with an ICC profile (after a call to @ref JxlDecoderSetCms), the + * ICC profile has to be a valid RGB or grayscale color profile. + * + * Can only be set after the ::JXL_DEC_COLOR_ENCODING event occurred and + * before any other event occurred, and should be used before getting + * ::JXL_COLOR_PROFILE_TARGET_DATA. + * + * This function must not be called before @ref JxlDecoderSetCms. + * + * @param dec decoder object + * @param color_encoding the output color encoding + * @param icc_data bytes of the icc profile + * @param icc_size size of the icc profile in bytes + * @return ::JXL_DEC_SUCCESS if the color profile was set successfully, @ref + * JXL_DEC_ERROR otherwise. + */ +JXL_EXPORT JxlDecoderStatus JxlDecoderSetOutputColorProfile( + JxlDecoder* dec, const JxlColorEncoding* color_encoding, + const uint8_t* icc_data, size_t icc_size); + +/** + * Sets the color management system (CMS) that will be used for color + * conversion (if applicable) during decoding. May only be set before starting + * decoding and must not be called after @ref JxlDecoderSetOutputColorProfile. + * + * See @ref JxlDecoderSetOutputColorProfile for how color conversions are done + * depending on whether or not a CMS has been set with @ref JxlDecoderSetCms. + * + * @param dec decoder object. + * @param cms structure representing a CMS implementation. See @ref + * JxlCmsInterface for more details. + */ +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 * for the given format. This is the buffer for @ref @@ -872,25 +912,25 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetDesiredIntensityTarget( * @param dec decoder object * @param format format of pixels * @param size output value, buffer size in bytes - * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such as * information not available yet. */ JXL_EXPORT JxlDecoderStatus JxlDecoderPreviewOutBufferSize( const JxlDecoder* dec, const JxlPixelFormat* format, size_t* size); /** - * Sets the buffer to write the small resolution preview image + * Sets the buffer to write the low-resolution preview image * to. The size of the buffer must be at least as large as given by @ref * JxlDecoderPreviewOutBufferSize. The buffer follows the format described - * by JxlPixelFormat. The preview image dimensions are given by the - * JxlPreviewHeader. The buffer is owned by the caller. + * by @ref JxlPixelFormat. The preview image dimensions are given by the + * @ref JxlPreviewHeader. The buffer is owned by the caller. * * @param dec decoder object * @param format format of pixels. Object owned by user and its contents are * copied internally. * @param buffer buffer type to output the pixel data to * @param size size of buffer in bytes - * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such as * size too small. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetPreviewOutBuffer( @@ -898,14 +938,14 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetPreviewOutBuffer( /** * Outputs the information from the frame, such as duration when have_animation. - * This function can be called when @ref JXL_DEC_FRAME occurred for the current + * This function can be called when ::JXL_DEC_FRAME occurred for the current * frame, even when have_animation in the JxlBasicInfo is JXL_FALSE. * * @param dec decoder object * @param header struct to copy the information into, or NULL to only check * whether the information is available through the return value. - * @return @ref JXL_DEC_SUCCESS if the value is available, @ref - * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR in + * @return ::JXL_DEC_SUCCESS if the value is available, @ref + * JXL_DEC_NEED_MORE_INPUT if not yet available, ::JXL_DEC_ERROR in * case of other error conditions. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetFrameHeader(const JxlDecoder* dec, @@ -913,14 +953,14 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetFrameHeader(const JxlDecoder* dec, /** * Outputs name for the current frame. The buffer for name must have at least - * name_length + 1 bytes allocated, gotten from the associated JxlFrameHeader. + * `name_length + 1` bytes allocated, gotten from the associated JxlFrameHeader. * * @param dec decoder object * @param name buffer to copy the name into * @param size size of the name buffer in bytes, including zero termination - * character, so this must be at least JxlFrameHeader.name_length + 1. - * @return @ref JXL_DEC_SUCCESS if the value is available, @ref - * JXL_DEC_NEED_MORE_INPUT if not yet available, @ref JXL_DEC_ERROR in + * character, so this must be at least @ref JxlFrameHeader.name_length + 1. + * @return ::JXL_DEC_SUCCESS if the value is available, @ref + * JXL_DEC_NEED_MORE_INPUT if not yet available, ::JXL_DEC_ERROR in * case of other error conditions. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetFrameName(const JxlDecoder* dec, @@ -928,70 +968,32 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetFrameName(const JxlDecoder* dec, /** * Outputs the blend information for the current frame for a specific extra - * channel. This function can be called when @ref JXL_DEC_FRAME occurred for the - * current frame, even when have_animation in the JxlBasicInfo is JXL_FALSE. - * This information is only useful if coalescing is disabled; otherwise the - * decoder will have performed blending already. + * channel. This function can be called once the ::JXL_DEC_FRAME event occurred + * for the current frame, even if the `have_animation` field in the @ref + * JxlBasicInfo is @ref JXL_FALSE. This information is only useful if coalescing + * is disabled; otherwise the decoder will have performed blending already. * * @param dec decoder object * @param index the index of the extra channel * @param blend_info struct to copy the information into - * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error + * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelBlendInfo( const JxlDecoder* dec, size_t index, JxlBlendInfo* blend_info); -/** - * Returns the minimum size in bytes of the DC image output buffer - * for the given format. This is the buffer for @ref JxlDecoderSetDCOutBuffer. - * Requires the basic image information is available in the decoder. - * - * @param dec decoder object - * @param format format of pixels - * @param size output value, buffer size in bytes - * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as - * information not available yet. - * - * @deprecated The DC feature in this form will be removed. Use @ref - * JxlDecoderFlushImage for progressive rendering. - */ -JXL_EXPORT JXL_DEPRECATED JxlDecoderStatus JxlDecoderDCOutBufferSize( - const JxlDecoder* dec, const JxlPixelFormat* format, size_t* size); - -/** - * Sets the buffer to write the lower resolution (8x8 sub-sampled) DC image - * to. The size of the buffer must be at least as large as given by @ref - * JxlDecoderDCOutBufferSize. The buffer follows the format described by - * JxlPixelFormat. The DC image has dimensions ceil(xsize / 8) * ceil(ysize / - * 8). The buffer is owned by the caller. - * - * @param dec decoder object - * @param format format of pixels. Object owned by user and its contents are - * copied internally. - * @param buffer buffer type to output the pixel data to - * @param size size of buffer in bytes - * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as - * size too small. - * - * @deprecated The DC feature in this form will be removed. Use @ref - * JxlDecoderFlushImage for progressive rendering. - */ -JXL_EXPORT JXL_DEPRECATED JxlDecoderStatus JxlDecoderSetDCOutBuffer( - JxlDecoder* dec, const JxlPixelFormat* format, void* buffer, size_t size); - /** * Returns the minimum size in bytes of the image output pixel buffer for the * given format. This is the buffer for @ref JxlDecoderSetImageOutBuffer. * Requires that the basic image information is available in the decoder in the * case of coalescing enabled (default). In case coalescing is disabled, this - * can only be called after the @ref JXL_DEC_FRAME event occurs. In that case, + * can only be called after the ::JXL_DEC_FRAME event occurs. In that case, * it will return the size required to store the possibly cropped frame (which * can be larger or smaller than the image dimensions). * * @param dec decoder object * @param format format of the pixels. * @param size output value, buffer size in bytes - * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such as * information not available yet. */ JXL_EXPORT JxlDecoderStatus JxlDecoderImageOutBufferSize( @@ -999,18 +1001,18 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderImageOutBufferSize( /** * Sets the buffer to write the full resolution image to. This can be set when - * the @ref JXL_DEC_FRAME event occurs, must be set when the @ref + * the ::JXL_DEC_FRAME event occurs, must be set when the @ref * JXL_DEC_NEED_IMAGE_OUT_BUFFER event occurs, and applies only for the * current frame. The size of the buffer must be at least as large as given * by @ref JxlDecoderImageOutBufferSize. The buffer follows the format described - * by JxlPixelFormat. The buffer is owned by the caller. + * by @ref JxlPixelFormat. The buffer is owned by the caller. * * @param dec decoder object * @param format format of the pixels. Object owned by user and its contents * are copied internally. * @param buffer buffer type to output the pixel data to * @param size size of buffer in bytes - * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such as * size too small. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetImageOutBuffer( @@ -1081,15 +1083,15 @@ typedef void (*JxlImageOutDestroyCallback)(void* run_opaque); /** * Sets pixel output callback. This is an alternative to @ref - * JxlDecoderSetImageOutBuffer. This can be set when the @ref JXL_DEC_FRAME - * event occurs, must be set when the @ref JXL_DEC_NEED_IMAGE_OUT_BUFFER event + * JxlDecoderSetImageOutBuffer. This can be set when the ::JXL_DEC_FRAME + * event occurs, must be set when the ::JXL_DEC_NEED_IMAGE_OUT_BUFFER event * occurs, and applies only for the current frame. Only one of @ref * JxlDecoderSetImageOutBuffer or @ref JxlDecoderSetImageOutCallback may be used * for the same frame, not both at the same time. * * The callback will be called multiple times, to receive the image * data in small chunks. The callback receives a horizontal stripe of pixel - * data, 1 pixel high, xsize pixels wide, called a scanline. The xsize here is + * data, `1` pixel high, xsize pixels wide, called a scanline. The xsize here is * not the same as the full image width, the scanline may be a partial section, * and xsize may differ between calls. The user can then process and/or copy the * partial scanline to an image buffer. The callback may be called @@ -1118,7 +1120,7 @@ typedef void (*JxlImageOutDestroyCallback)(void* run_opaque); * data. * @param opaque optional user data, which will be passed on to the callback, * may be NULL. - * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such + * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such * as @ref JxlDecoderSetImageOutBuffer already set. */ JXL_EXPORT JxlDecoderStatus @@ -1141,7 +1143,7 @@ JxlDecoderSetImageOutCallback(JxlDecoder* dec, const JxlPixelFormat* format, * @param init_opaque optional user data passed to @c init_callback, may be NULL * (unlike the return value from @c init_callback which may only be NULL if * initialization failed). - * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such + * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such * as @ref JxlDecoderSetImageOutBuffer having already been called. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetMultithreadedImageOutCallback( @@ -1156,12 +1158,12 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetMultithreadedImageOutCallback( * * @param dec decoder object * @param format format of the pixels. The num_channels value is ignored and is - * always treated to be 1. + * always treated to be `1`. * @param size output value, buffer size in bytes * @param index which extra channel to get, matching the index used in @ref * JxlDecoderGetExtraChannelInfo. Must be smaller than num_extra_channels in - * the associated JxlBasicInfo. - * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * the associated @ref JxlBasicInfo. + * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such as * information not available yet or invalid index. */ JXL_EXPORT JxlDecoderStatus JxlDecoderExtraChannelBufferSize( @@ -1170,13 +1172,13 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderExtraChannelBufferSize( /** * Sets the buffer to write an extra channel to. This can be set when - * the @ref JXL_DEC_FRAME or @ref JXL_DEC_NEED_IMAGE_OUT_BUFFER event occurs, + * the ::JXL_DEC_FRAME or ::JXL_DEC_NEED_IMAGE_OUT_BUFFER event occurs, * and applies only for the current frame. The size of the buffer must be at * least as large as given by @ref JxlDecoderExtraChannelBufferSize. The buffer - * follows the format described by JxlPixelFormat, but where num_channels is 1. - * The buffer is owned by the caller. The amount of extra channels is given by - * the num_extra_channels field in the associated JxlBasicInfo, and the - * information of individual extra channels can be queried with @ref + * follows the format described by @ref JxlPixelFormat, but where num_channels + * is `1`. The buffer is owned by the caller. The amount of extra channels is + * given by the num_extra_channels field in the associated @ref JxlBasicInfo, + * and the information of individual extra channels can be queried with @ref * JxlDecoderGetExtraChannelInfo. To get multiple extra channels, this function * must be called multiple times, once for each wanted index. Not all images * have extra channels. The alpha channel is an extra channel and can be gotten @@ -1189,13 +1191,13 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderExtraChannelBufferSize( * @param dec decoder object * @param format format of the pixels. Object owned by user and its contents * are copied internally. The num_channels value is ignored and is always - * treated to be 1. + * treated to be `1`. * @param buffer buffer type to output the pixel data to * @param size size of buffer in bytes * @param index which extra channel to get, matching the index used in @ref * JxlDecoderGetExtraChannelInfo. Must be smaller than num_extra_channels in - * the associated JxlBasicInfo. - * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * the associated @ref JxlBasicInfo. + * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such as * size too small or invalid index. */ JXL_EXPORT JxlDecoderStatus @@ -1216,8 +1218,8 @@ JxlDecoderSetExtraChannelBuffer(JxlDecoder* dec, const JxlPixelFormat* format, * @param dec decoder object * @param data pointer to next bytes to write to * @param size amount of bytes available starting from data - * @return @ref JXL_DEC_ERROR if output buffer was already set and @ref - * JxlDecoderReleaseJPEGBuffer was not called on it, @ref JXL_DEC_SUCCESS + * @return ::JXL_DEC_ERROR if output buffer was already set and @ref + * JxlDecoderReleaseJPEGBuffer was not called on it, ::JXL_DEC_SUCCESS * otherwise */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetJPEGBuffer(JxlDecoder* dec, @@ -1232,11 +1234,11 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetJPEGBuffer(JxlDecoder* dec, * JxlDecoderDestroy or @ref JxlDecoderReset. * * Calling @ref JxlDecoderReleaseJPEGBuffer when no buffer is set is - * not an error and returns 0. + * not an error and returns `0`. * * @param dec decoder object * @return the amount of bytes the decoder has not yet written to of the data - * set by @ref JxlDecoderSetJPEGBuffer, or 0 if no buffer is set or @ref + * set by @ref JxlDecoderSetJPEGBuffer, or `0` if no buffer is set or @ref * JxlDecoderReleaseJPEGBuffer was already called. */ JXL_EXPORT size_t JxlDecoderReleaseJPEGBuffer(JxlDecoder* dec); @@ -1252,15 +1254,15 @@ JXL_EXPORT size_t JxlDecoderReleaseJPEGBuffer(JxlDecoder* dec); * JxlDecoderReleaseBoxBuffer, bytes that the decoder has already output * should not be included, only the remaining bytes output must be set. * - * The @ref JxlDecoderReleaseBoxBuffer must be used at the next @ref JXL_DEC_BOX - * event or final @ref JXL_DEC_SUCCESS event to compute the size of the output + * The @ref JxlDecoderReleaseBoxBuffer must be used at the next ::JXL_DEC_BOX + * event or final ::JXL_DEC_SUCCESS event to compute the size of the output * box bytes. * * @param dec decoder object * @param data pointer to next bytes to write to * @param size amount of bytes available starting from data - * @return @ref JXL_DEC_ERROR if output buffer was already set and @ref - * JxlDecoderReleaseBoxBuffer was not called on it, @ref JXL_DEC_SUCCESS + * @return ::JXL_DEC_ERROR if output buffer was already set and @ref + * JxlDecoderReleaseBoxBuffer was not called on it, ::JXL_DEC_SUCCESS * otherwise */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetBoxBuffer(JxlDecoder* dec, @@ -1275,11 +1277,11 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetBoxBuffer(JxlDecoder* dec, * JxlDecoderDestroy or @ref JxlDecoderReset. * * Calling @ref JxlDecoderReleaseBoxBuffer when no buffer is set is - * not an error and returns 0. + * not an error and returns `0`. * * @param dec decoder object * @return the amount of bytes the decoder has not yet written to of the data - * set by @ref JxlDecoderSetBoxBuffer, or 0 if no buffer is set or @ref + * set by @ref JxlDecoderSetBoxBuffer, or `0` if no buffer is set or @ref * JxlDecoderReleaseBoxBuffer was already called. */ JXL_EXPORT size_t JxlDecoderReleaseBoxBuffer(JxlDecoder* dec); @@ -1293,23 +1295,23 @@ JXL_EXPORT size_t JxlDecoderReleaseBoxBuffer(JxlDecoder* dec); * finished. * * The default mode is raw. This setting can only be changed before decoding, or - * directly after a @ref JXL_DEC_BOX event, and is remembered until the decoder + * directly after a ::JXL_DEC_BOX event, and is remembered until the decoder * is reset or destroyed. * * Enabling decompressed mode requires Brotli support from the library. * * @param dec decoder object - * @param decompress JXL_TRUE to transparently decompress, JXL_FALSE to get - * boxes in raw mode. - * @return @ref JXL_DEC_ERROR if decompressed mode is set and Brotli is not - * available, @ref JXL_DEC_SUCCESS otherwise. + * @param decompress ::JXL_TRUE to transparently decompress, ::JXL_FALSE + * to get boxes in raw mode. + * @return ::JXL_DEC_ERROR if decompressed mode is set and Brotli is not + * available, ::JXL_DEC_SUCCESS otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetDecompressBoxes(JxlDecoder* dec, JXL_BOOL decompress); /** - * Outputs the type of the current box, after a @ref JXL_DEC_BOX event occured, - * as 4 characters without null termination character. In case of a compressed + * Outputs the type of the current box, after a ::JXL_DEC_BOX event occurred, + * as `4` characters without null termination character. In case of a compressed * "brob" box, this will return "brob" if the decompressed argument is * JXL_FALSE, or the underlying box type if the decompressed argument is * JXL_TRUE. @@ -1325,15 +1327,15 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetDecompressBoxes(JxlDecoder* dec, * - "xml ": a box with XML data, in particular XMP metadata. * - "jumb": a JUMBF superbox (JPEG Universal Metadata Box Format, ISO/IEC * 19566-5). - * - "JXL ": mandatory signature box, must come first, 12 bytes long including - * the box header - * - "ftyp": a second mandatory signature box, must come second, 20 bytes long - * including the box header - * - "jxll": a JXL level box. This indicates if the codestream is level 5 or - * level 10 compatible. If not present, it is level 5. Level 10 allows more - * features such as very high image resolution and bit-depths above 16 bits - * per channel. Added automatically by the encoder when - * JxlEncoderSetCodestreamLevel is used + * - "JXL ": mandatory signature box, must come first, `12` bytes long + * including the box header + * - "ftyp": a second mandatory signature box, must come second, `20` bytes + * long including the box header + * - "jxll": a JXL level box. This indicates if the codestream is level `5` or + * level `10` compatible. If not present, it is level `5`. Level `10` allows + * more features such as very high image resolution and bit-depths above `16` + * bits per channel. Added automatically by the encoder when + * @ref JxlEncoderSetCodestreamLevel is used * - "jxlc": a box with the image codestream, in case the codestream is not * split across multiple boxes. The codestream contains the JPEG XL image * itself, including the basic info such as image dimensions, ICC color @@ -1348,7 +1350,7 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetDecompressBoxes(JxlDecoder* dec, * animation allowing the decoder to jump to individual frames more * efficiently. * - "jbrd": JPEG reconstruction box, contains the information required to - * byte-for-byte losslessly recontruct a JPEG-1 image. The JPEG DCT + * byte-for-byte losslessly reconstruct a JPEG-1 image. The JPEG DCT * coefficients (pixel content) themselves as well as the ICC profile are * encoded in the JXL codestream (jxlc or jxlp) itself. EXIF, XMP and JUMBF * metadata is encoded in the corresponding boxes. The jbrd box itself @@ -1369,8 +1371,8 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetDecompressBoxes(JxlDecoder* dec, * @param type buffer to copy the type into * @param decompressed which box type to get: JXL_FALSE to get the raw box type, * which can be "brob", JXL_TRUE, get the underlying box type. - * @return @ref JXL_DEC_SUCCESS if the value is available, @ref JXL_DEC_ERROR if - * not, for example the JXL file does not use the container format. + * @return ::JXL_DEC_SUCCESS if the value is available, ::JXL_DEC_ERROR if + * not, for example the JPEG XL file does not use the container format. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetBoxType(JxlDecoder* dec, JxlBoxType type, @@ -1378,24 +1380,32 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetBoxType(JxlDecoder* dec, /** * Returns the size of a box as it appears in the container file, after the @ref - * JXL_DEC_BOX event. For a non-compressed box, this is the size of the - * contents, excluding the 4 bytes indicating the box type. For a compressed - * "brob" box, this is the size of the compressed box contents plus the - * additional 4 byte indicating the underlying box type, but excluding the 4 - * bytes indicating "brob". This function gives the size of the data that will - * be written in the output buffer when getting boxes in the default raw - * compressed mode. When @ref JxlDecoderSetDecompressBoxes is enabled, the - * return value of function does not change, and the decompressed size is not - * known before it has already been decompressed and output. + * JXL_DEC_BOX event. This includes all the box headers. * * @param dec decoder object * @param size raw size of the box in bytes - * @return @ref JXL_DEC_ERROR if no box size is available, @ref JXL_DEC_SUCCESS + * @return ::JXL_DEC_ERROR if no box size is available, ::JXL_DEC_SUCCESS * otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetBoxSizeRaw(const JxlDecoder* dec, uint64_t* size); +/** + * Returns the size of the contents of a box, after the @ref + * JXL_DEC_BOX event. This does not include any of the headers of the box. For + * compressed "brob" boxes, this is the size of the compressed content. Even + * when @ref JxlDecoderSetDecompressBoxes is enabled, the return value of + * function does not change, and the decompressed size is not known before it + * has already been decompressed and output. + * + * @param dec decoder object + * @param size size of the payload of the box in bytes + * @return @ref JXL_DEC_ERROR if no box size is available, @ref JXL_DEC_SUCCESS + * otherwise. + */ +JXL_EXPORT JxlDecoderStatus JxlDecoderGetBoxSizeContents(const JxlDecoder* dec, + uint64_t* size); + /** * Configures at which progressive steps in frame decoding these @ref * JXL_DEC_FRAME_PROGRESSION event occurs. The default value for the level @@ -1404,7 +1414,7 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetBoxSizeRaw(const JxlDecoder* dec, * @param dec decoder object * @param detail at which level of detail to trigger @ref * JXL_DEC_FRAME_PROGRESSION - * @return @ref JXL_DEC_SUCCESS on success, @ref JXL_DEC_ERROR on error, such as + * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such as * an invalid value for the progressive detail. */ JXL_EXPORT JxlDecoderStatus @@ -1412,11 +1422,11 @@ JxlDecoderSetProgressiveDetail(JxlDecoder* dec, JxlProgressiveDetail detail); /** * Returns the intended downsampling ratio for the progressive frame produced - * by @ref JxlDecoderFlushImage after the latest @ref JXL_DEC_FRAME_PROGRESSION + * by @ref JxlDecoderFlushImage after the latest ::JXL_DEC_FRAME_PROGRESSION * event. * * @param dec decoder object - * @return The intended downsampling ratio, can be 1, 2, 4 or 8. + * @return The intended downsampling ratio, can be `1`, `2`, `4` or `8`. */ JXL_EXPORT size_t JxlDecoderGetIntendedDownsamplingRatio(JxlDecoder* dec); @@ -1426,19 +1436,34 @@ JXL_EXPORT size_t JxlDecoderGetIntendedDownsamplingRatio(JxlDecoder* dec); * JxlDecoderSetImageOutBuffer will contain partial image data. * * Can be called when @ref JxlDecoderProcessInput returns @ref - * JXL_DEC_NEED_MORE_INPUT, after the @ref JXL_DEC_FRAME event already occurred - * and before the @ref JXL_DEC_FULL_IMAGE event occurred for a frame. + * JXL_DEC_NEED_MORE_INPUT, after the ::JXL_DEC_FRAME event already occurred + * and before the ::JXL_DEC_FULL_IMAGE event occurred for a frame. * * @param dec decoder object - * @return @ref JXL_DEC_SUCCESS if image data was flushed to the output buffer, - * or @ref JXL_DEC_ERROR when no flush was done, e.g. if not enough image + * @return ::JXL_DEC_SUCCESS if image data was flushed to the output buffer, + * or ::JXL_DEC_ERROR when no flush was done, e.g. if not enough image * data was available yet even for flush, or no output buffer was set yet. * This error is not fatal, it only indicates no flushed image is available * right now. Regular decoding can still be performed. */ JXL_EXPORT JxlDecoderStatus JxlDecoderFlushImage(JxlDecoder* dec); -#if defined(__cplusplus) || defined(c_plusplus) +/** + * Sets the bit depth of the output buffer or callback. + * + * Can be called after @ref JxlDecoderSetImageOutBuffer or @ref + * JxlDecoderSetImageOutCallback. For float pixel data types, only the default + * ::JXL_BIT_DEPTH_FROM_PIXEL_FORMAT setting is supported. + * + * @param dec decoder object + * @param bit_depth the bit depth setting of the pixel output + * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such as + * incompatible custom bit depth and pixel data type. + */ +JXL_EXPORT JxlDecoderStatus +JxlDecoderSetImageOutBitDepth(JxlDecoder* dec, const JxlBitDepth* bit_depth); + +#ifdef __cplusplus } #endif diff --git a/media/libjxl/src/lib/include/jxl/decode_cxx.h b/media/libjxl/src/lib/include/jxl/decode_cxx.h index ed5c393476..6089538dbe 100644 --- a/media/libjxl/src/lib/include/jxl/decode_cxx.h +++ b/media/libjxl/src/lib/include/jxl/decode_cxx.h @@ -3,7 +3,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -/// @addtogroup libjxl_decoder +/// @addtogroup libjxl_cpp /// @{ /// /// @file decode_cxx.h @@ -15,11 +15,12 @@ #ifndef JXL_DECODE_CXX_H_ #define JXL_DECODE_CXX_H_ +#include +#include + #include -#include "jxl/decode.h" - -#if !(defined(__cplusplus) || defined(c_plusplus)) +#ifndef __cplusplus #error "This a C++ only header. Use jxl/decode.h from C sources." #endif diff --git a/media/libjxl/src/lib/include/jxl/encode.h b/media/libjxl/src/lib/include/jxl/encode.h index 4813e3b7cf..633a98e417 100644 --- a/media/libjxl/src/lib/include/jxl/encode.h +++ b/media/libjxl/src/lib/include/jxl/encode.h @@ -13,13 +13,19 @@ #ifndef JXL_ENCODE_H_ #define JXL_ENCODE_H_ -#include "jxl/cms_interface.h" -#include "jxl/codestream_header.h" -#include "jxl/jxl_export.h" -#include "jxl/memory_manager.h" -#include "jxl/parallel_runner.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include // TODO(eustas): remove before v1.0 +#include +#include -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -35,8 +41,8 @@ JXL_EXPORT uint32_t JxlEncoderVersion(void); /** * Opaque structure that holds the JPEG XL encoder. * - * Allocated and initialized with JxlEncoderCreate(). - * Cleaned up and deallocated with JxlEncoderDestroy(). + * Allocated and initialized with @ref JxlEncoderCreate(). + * Cleaned up and deallocated with @ref JxlEncoderDestroy(). */ typedef struct JxlEncoderStruct JxlEncoder; @@ -44,16 +50,12 @@ typedef struct JxlEncoderStruct JxlEncoder; * Settings and metadata for a single image frame. This includes encoder options * for a frame such as compression quality and speed. * - * Allocated and initialized with JxlEncoderFrameSettingsCreate(). + * Allocated and initialized with @ref JxlEncoderFrameSettingsCreate(). * Cleaned up and deallocated when the encoder is destroyed with - * JxlEncoderDestroy(). + * @ref JxlEncoderDestroy(). */ typedef struct JxlEncoderFrameSettingsStruct JxlEncoderFrameSettings; -/** DEPRECATED: Use JxlEncoderFrameSettings instead. - */ -typedef JxlEncoderFrameSettings JxlEncoderOptions; - /** * Return value for multiple encoder functions. */ @@ -71,13 +73,6 @@ typedef enum { */ JXL_ENC_NEED_MORE_OUTPUT = 2, - /** DEPRECATED: the encoder does not return this status and there is no need - * to handle or expect it. - * Instead, JXL_ENC_ERROR is returned with error condition - * JXL_ENC_ERR_NOT_SUPPORTED. - */ - JXL_ENC_NOT_SUPPORTED = 3, - } JxlEncoderStatus; /** @@ -131,7 +126,7 @@ typedef enum { typedef enum { /** Sets encoder effort/speed level without affecting decoding speed. Valid * values are, from faster to slower speed: 1:lightning 2:thunder 3:falcon - * 4:cheetah 5:hare 6:wombat 7:squirrel 8:kitten 9:tortoise. + * 4:cheetah 5:hare 6:wombat 7:squirrel 8:kitten 9:tortoise 10:glacier. * Default: squirrel (7). */ JXL_ENC_FRAME_SETTING_EFFORT = 0, @@ -150,7 +145,7 @@ typedef enum { */ JXL_ENC_FRAME_SETTING_RESAMPLING = 2, - /** Similar to JXL_ENC_FRAME_SETTING_RESAMPLING, but for extra channels. + /** Similar to ::JXL_ENC_FRAME_SETTING_RESAMPLING, but for extra channels. * Integer option, use -1 for the default behavior (depends on encoder * implementation), 1 for no downsampling (1x1), 2 for 2x2 downsampling, 4 for * 4x4 downsampling, 8 for 8x8 downsampling. @@ -163,7 +158,7 @@ typedef enum { * downsampled resolution, not the full image resolution. The downsampled * resolution is given by ceil(xsize / resampling), ceil(ysize / resampling) * with xsize and ysize the dimensions given in the basic info, and resampling - * the factor set with @ref JXL_ENC_FRAME_SETTING_RESAMPLING. + * the factor set with ::JXL_ENC_FRAME_SETTING_RESAMPLING. * Use 0 to disable, 1 to enable. Default value is 0. */ JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED = 4, @@ -176,8 +171,8 @@ typedef enum { JXL_ENC_FRAME_SETTING_PHOTON_NOISE = 5, /** Enables adaptive noise generation. This setting is not recommended for - * use, please use JXL_ENC_FRAME_SETTING_PHOTON_NOISE instead. Use -1 for the - * default (encoder chooses), 0 to disable, 1 to enable. + * use, please use ::JXL_ENC_FRAME_SETTING_PHOTON_NOISE instead. Use -1 for + * the default (encoder chooses), 0 to disable, 1 to enable. */ JXL_ENC_FRAME_SETTING_NOISE = 6, @@ -325,19 +320,79 @@ typedef enum { * 1 = index this frame within the Frame Index Box. * If any frames are indexed, the first frame needs to * be indexed, too. If the first frame is not indexed, and - * a later frame is attempted to be indexed, JXL_ENC_ERROR will occur. + * a later frame is attempted to be indexed, ::JXL_ENC_ERROR will occur. * If non-keyframes, i.e., frames with cropping, blending or patches are - * attempted to be indexed, JXL_ENC_ERROR will occur. + * attempted to be indexed, ::JXL_ENC_ERROR will occur. */ JXL_ENC_FRAME_INDEX_BOX = 31, - /** Sets brotli encode effort for use in JPEG recompression and compressed - * metadata boxes (brob). Can be -1 (default) or 0 (fastest) to 11 (slowest). - * Default is based on the general encode effort in case of JPEG + /** Sets brotli encode effort for use in JPEG recompression and + * compressed metadata boxes (brob). Can be -1 (default) or 0 (fastest) to 11 + * (slowest). Default is based on the general encode effort in case of JPEG * recompression, and 4 for brob boxes. */ JXL_ENC_FRAME_SETTING_BROTLI_EFFORT = 32, + /** Enables or disables brotli compression of metadata boxes derived from + * a JPEG frame when using @ref JxlEncoderAddJPEGFrame. This has no effect on + * boxes added using @ref JxlEncoderAddBox. -1 = default, 0 = disable + * compression, 1 = enable compression. + */ + JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES = 33, + + /** Control what kind of buffering is used, when using chunked image frames. + * -1 = default (let the encoder decide) + * 0 = buffers everything, basically the same as non-streamed code path + (mainly for testing) + * 1 = buffers everything for images that are smaller than 2048 x 2048, and + * uses streaming input and output for larger images + * 2 = uses streaming input and output for all images that are larger than + * one group, i.e. 256 x 256 pixels by default + * 3 = currently same as 2 + * + * When using streaming input and output the encoder minimizes memory usage at + * the cost of compression density. Also note that images produced with + * streaming mode might not be progressively decodeable. + */ + JXL_ENC_FRAME_SETTING_BUFFERING = 34, + + /** Keep or discard Exif metadata boxes derived from a JPEG frame when using + * @ref JxlEncoderAddJPEGFrame. This has no effect on boxes added using + * @ref JxlEncoderAddBox. When @ref JxlEncoderStoreJPEGMetadata is set to 1, + * this option cannot be set to 0. Even when Exif metadata is discarded, the + * orientation will still be applied. 0 = discard Exif metadata, 1 = keep Exif + * metadata (default). + */ + JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF = 35, + + /** Keep or discard XMP metadata boxes derived from a JPEG frame when using + * @ref JxlEncoderAddJPEGFrame. This has no effect on boxes added using + * @ref JxlEncoderAddBox. When @ref JxlEncoderStoreJPEGMetadata is set to 1, + * this option cannot be set to 0. 0 = discard XMP metadata, 1 = keep XMP + * metadata (default). + */ + JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP = 36, + + /** Keep or discard JUMBF metadata boxes derived from a JPEG frame when using + * @ref JxlEncoderAddJPEGFrame. This has no effect on boxes added using + * @ref JxlEncoderAddBox. 0 = discard JUMBF metadata, 1 = keep JUMBF metadata + * (default). + */ + JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF = 37, + + /** If this mode is disabled, the encoder will not make any image quality + * decisions that are computed based on the full image, but stored only once + * (e.g. the X quant multiplier in the frame header). Used mainly for testing + * equivalence of streaming and non-streaming code. + * 0 = disabled, 1 = enabled (default) + */ + JXL_ENC_FRAME_SETTING_USE_FULL_IMAGE_HEURISTICS = 38, + + /** Disable perceptual optimizations. 0 = optimizations enabled (default), 1 = + * optimizations disabled. + */ + JXL_ENC_FRAME_SETTING_DISABLE_PERCEPTUAL_HEURISTICS = 39, + /** Enum value not to be used as an option. This value is added to force the * C compiler to have the enum to take a known size. */ @@ -346,7 +401,7 @@ typedef enum { } JxlEncoderFrameSettingId; /** - * Creates an instance of JxlEncoder and initializes it. + * Creates an instance of @ref JxlEncoder and initializes it. * * @p memory_manager will be used for all the library dynamic allocations made * from this instance. The parameter may be NULL, in which case the default @@ -355,21 +410,21 @@ typedef enum { * @param memory_manager custom allocator function. It may be NULL. The memory * manager will be copied internally. * @return @c NULL if the instance can not be allocated or initialized - * @return pointer to initialized JxlEncoder otherwise + * @return pointer to initialized @ref JxlEncoder otherwise */ JXL_EXPORT JxlEncoder* JxlEncoderCreate(const JxlMemoryManager* memory_manager); /** - * Re-initializes a JxlEncoder instance, so it can be re-used for encoding + * Re-initializes a @ref JxlEncoder instance, so it can be re-used for encoding * another image. All state and settings are reset as if the object was - * newly created with JxlEncoderCreate, but the memory manager is kept. + * newly created with @ref JxlEncoderCreate, but the memory manager is kept. * * @param enc instance to be re-initialized. */ JXL_EXPORT void JxlEncoderReset(JxlEncoder* enc); /** - * Deinitializes and frees JxlEncoder instance. + * Deinitializes and frees a @ref JxlEncoder instance. * * @param enc instance to be cleaned up and deallocated. */ @@ -381,8 +436,8 @@ JXL_EXPORT void JxlEncoderDestroy(JxlEncoder* enc); * left unset, the default CMS implementation will be used. * * @param enc encoder object. - * @param cms structure representing a CMS implementation. See JxlCmsInterface - * for more details. + * @param cms structure representing a CMS implementation. See @ref + * JxlCmsInterface for more details. */ JXL_EXPORT void JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms); @@ -395,7 +450,7 @@ JXL_EXPORT void JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms); * be NULL to use the default, single-threaded, runner. A multithreaded * runner should be set to reach fast performance. * @param parallel_runner_opaque opaque pointer for parallel_runner. - * @return JXL_ENC_SUCCESS if the runner was set, JXL_ENC_ERROR + * @return ::JXL_ENC_SUCCESS if the runner was set, ::JXL_ENC_ERROR * otherwise (the previous runner remains set). */ JXL_EXPORT JxlEncoderStatus @@ -403,16 +458,16 @@ JxlEncoderSetParallelRunner(JxlEncoder* enc, JxlParallelRunner parallel_runner, void* parallel_runner_opaque); /** - * Get the (last) error code in case JXL_ENC_ERROR was returned. + * Get the (last) error code in case ::JXL_ENC_ERROR was returned. * * @param enc encoder object. - * @return the JxlEncoderError that caused the (last) JXL_ENC_ERROR to be - * returned. + * @return the @ref JxlEncoderError that caused the (last) ::JXL_ENC_ERROR to + * be returned. */ JXL_EXPORT JxlEncoderError JxlEncoderGetError(JxlEncoder* enc); /** - * Encodes JPEG XL file using the available bytes. @p *avail_out indicates how + * Encodes a JPEG XL file using the available bytes. @p *avail_out indicates how * many output bytes are available, and @p *next_out points to the input bytes. * *avail_out will be decremented by the amount of bytes that have been * processed by the encoder and *next_out will be incremented by the same @@ -420,8 +475,13 @@ JXL_EXPORT JxlEncoderError JxlEncoderGetError(JxlEncoder* enc); * bytes. * * The returned status indicates whether the encoder needs more output bytes. - * When the return value is not JXL_ENC_ERROR or JXL_ENC_SUCCESS, the encoding - * requires more JxlEncoderProcessOutput calls to continue. + * When the return value is not ::JXL_ENC_ERROR or ::JXL_ENC_SUCCESS, the + * encoding requires more @ref JxlEncoderProcessOutput calls to continue. + * + * The caller must guarantee that *avail_out >= 32 when calling + * @ref JxlEncoderProcessOutput; otherwise, ::JXL_ENC_NEED_MORE_OUTPUT will + * be returned. It is guaranteed that, if *avail_out >= 32, at least one byte of + * output will be written. * * This encodes the frames and/or boxes added so far. If the last frame or last * box has been added, @ref JxlEncoderCloseInput, @ref JxlEncoderCloseFrames @@ -432,9 +492,9 @@ JXL_EXPORT JxlEncoderError JxlEncoderGetError(JxlEncoder* enc); * @param enc encoder object. * @param next_out pointer to next bytes to write to. * @param avail_out amount of bytes available starting from *next_out. - * @return JXL_ENC_SUCCESS when encoding finished and all events handled. - * @return JXL_ENC_ERROR when encoding failed, e.g. invalid input. - * @return JXL_ENC_NEED_MORE_OUTPUT more output buffer is necessary. + * @return ::JXL_ENC_SUCCESS when encoding finished and all events handled. + * @return ::JXL_ENC_ERROR when encoding failed, e.g. invalid input. + * @return ::JXL_ENC_NEED_MORE_OUTPUT more output buffer is necessary. */ JXL_EXPORT JxlEncoderStatus JxlEncoderProcessOutput(JxlEncoder* enc, uint8_t** next_out, @@ -455,13 +515,14 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderProcessOutput(JxlEncoder* enc, * time duration of 0, making them form a composite still. See @ref * JxlFrameHeader for more information. * - * This information is stored in the JxlEncoderFrameSettings and so is used for - * any frame encoded with these JxlEncoderFrameSettings. It is ok to change - * between @ref JxlEncoderAddImageFrame calls, each added image frame will have - * the frame header that was set in the options at the time of calling - * JxlEncoderAddImageFrame. + * This information is stored in the @ref JxlEncoderFrameSettings and so is used + * for any frame encoded with these @ref JxlEncoderFrameSettings. It is ok to + * change between @ref JxlEncoderAddImageFrame calls, each added image frame + * will have the frame header that was set in the options at the time of calling + * @ref JxlEncoderAddImageFrame. * - * The is_last and name_length fields of the JxlFrameHeader are ignored, use + * The is_last and name_length fields of the @ref JxlFrameHeader are ignored, + * use * @ref JxlEncoderCloseFrames to indicate last frame, and @ref * JxlEncoderSetFrameName to indicate the name and its length instead. * Calling this function will clear any name that was previously set with @ref @@ -471,7 +532,7 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderProcessOutput(JxlEncoder* enc, * includes reference to the encoder object. * @param frame_header frame header data to set. Object owned by the caller and * does not need to be kept in memory, its information is copied internally. - * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error + * @return ::JXL_ENC_SUCCESS on success, ::JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameHeader(JxlEncoderFrameSettings* frame_settings, @@ -486,7 +547,7 @@ JxlEncoderSetFrameHeader(JxlEncoderFrameSettings* frame_settings, * includes reference to the encoder object. * @param index index of the extra channel to use. * @param blend_info blend info to set for the extra channel - * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error + * @return ::JXL_ENC_SUCCESS on success, ::JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBlendInfo( JxlEncoderFrameSettings* frame_settings, size_t index, @@ -496,8 +557,9 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBlendInfo( * Sets the name of the animation frame. This function is optional, frames are * not required to have a name. This setting is a part of the frame header, and * the same principles as for @ref JxlEncoderSetFrameHeader apply. The - * name_length field of JxlFrameHeader is ignored by the encoder, this function - * determines the name length instead as the length in bytes of the C string. + * name_length field of @ref JxlFrameHeader is ignored by the encoder, this + * function determines the name length instead as the length in bytes of the C + * string. * * The maximum possible name length is 1071 bytes (excluding terminating null * character). @@ -509,21 +571,39 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBlendInfo( * includes reference to the encoder object. * @param frame_name name of the next frame to be encoded, as a UTF-8 encoded C * string (zero terminated). Owned by the caller, and copied internally. - * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error + * @return ::JXL_ENC_SUCCESS on success, ::JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameName( JxlEncoderFrameSettings* frame_settings, const char* frame_name); +/** + * Sets the bit depth of the input buffer. + * + * For float pixel formats, only the default @ref + JXL_BIT_DEPTH_FROM_PIXEL_FORMAT + * setting is allowed, while for unsigned pixel formats, + * ::JXL_BIT_DEPTH_FROM_CODESTREAM setting is also allowed. See the comment + on + * @ref JxlEncoderAddImageFrame for the effects of the bit depth setting. + + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. + * @param bit_depth the bit depth setting of the pixel input + * @return ::JXL_ENC_SUCCESS on success, ::JXL_ENC_ERROR on error + */ +JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameBitDepth( + JxlEncoderFrameSettings* frame_settings, const JxlBitDepth* bit_depth); + /** * Sets the buffer to read JPEG encoded bytes from for the next frame to encode. * - * If JxlEncoderSetBasicInfo has not yet been called, calling - * JxlEncoderAddJPEGFrame will implicitly call it with the parameters of the - * added JPEG frame. + * If @ref JxlEncoderSetBasicInfo has not yet been called, calling + * @ref JxlEncoderAddJPEGFrame will implicitly call it with the parameters of + * the added JPEG frame. * - * If JxlEncoderSetColorEncoding or JxlEncoderSetICCProfile has not yet been - * called, calling JxlEncoderAddJPEGFrame will implicitly call it with the - * parameters of the added JPEG frame. + * If @ref JxlEncoderSetColorEncoding or @ref JxlEncoderSetICCProfile has not + * yet been called, calling @ref JxlEncoderAddJPEGFrame will implicitly call it + * with the parameters of the added JPEG frame. * * If the encoder is set to store JPEG reconstruction metadata using @ref * JxlEncoderStoreJPEGMetadata and a single JPEG frame is added, it will be @@ -533,12 +613,16 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameName( * JxlEncoderCloseFrames must be called before the next * @ref JxlEncoderProcessOutput call. * + * Note, this can only be used to add JPEG frames for lossless compression. To + * encode with lossy compression, the JPEG must be decoded manually and a pixel + * buffer added using JxlEncoderAddImageFrame. + * * @param frame_settings set of options and metadata for this frame. Also * includes reference to the encoder object. * @param buffer bytes to read JPEG from. Owned by the caller and its contents * are copied internally. * @param size size of buffer in bytes. - * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error + * @return ::JXL_ENC_SUCCESS on success, ::JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderAddJPEGFrame(const JxlEncoderFrameSettings* frame_settings, @@ -546,26 +630,36 @@ JxlEncoderAddJPEGFrame(const JxlEncoderFrameSettings* frame_settings, /** * Sets the buffer to read pixels from for the next image to encode. Must call - * JxlEncoderSetBasicInfo before JxlEncoderAddImageFrame. + * @ref JxlEncoderSetBasicInfo before @ref JxlEncoderAddImageFrame. * * Currently only some data types for pixel formats are supported: - * - JXL_TYPE_UINT8, with range 0..255 - * - JXL_TYPE_UINT16, with range 0..65535 - * - JXL_TYPE_FLOAT16, with nominal range 0..1 - * - JXL_TYPE_FLOAT, with nominal range 0..1 + * - ::JXL_TYPE_UINT8, with range 0..255 + * - ::JXL_TYPE_UINT16, with range 0..65535 + * - ::JXL_TYPE_FLOAT16, with nominal range 0..1 + * - ::JXL_TYPE_FLOAT, with nominal range 0..1 * * Note: the sample data type in pixel_format is allowed to be different from - * what is described in the JxlBasicInfo. The type in pixel_format describes the - * format of the uncompressed pixel buffer. The bits_per_sample and - * exponent_bits_per_sample in the JxlBasicInfo describes what will actually be - * encoded in the JPEG XL codestream. For example, to encode a 12-bit image, you - * would set bits_per_sample to 12, and you could use e.g. JXL_TYPE_UINT16 - * (where the values are rescaled to 16-bit, i.e. multiplied by 65535/4095) or - * JXL_TYPE_FLOAT (where the values are rescaled to 0..1, i.e. multiplied - * by 1.f/4095.f). While it is allowed, it is obviously not recommended to use a - * pixel_format with lower precision than what is specified in the JxlBasicInfo. + * what is described in the @ref JxlBasicInfo. The type in pixel_format, + * together with an optional @ref JxlBitDepth parameter set by @ref + * JxlEncoderSetFrameBitDepth describes the format of the uncompressed pixel + * buffer. The bits_per_sample and exponent_bits_per_sample in the @ref + * JxlBasicInfo describes what will actually be encoded in the JPEG XL + * codestream. For example, to encode a 12-bit image, you would set + * bits_per_sample to 12, while the input frame buffer can be in the following + * formats: + * - if pixel format is in ::JXL_TYPE_UINT16 with default bit depth setting + * (i.e. ::JXL_BIT_DEPTH_FROM_PIXEL_FORMAT), input sample values are + * rescaled to 16-bit, i.e. multiplied by 65535/4095; + * - if pixel format is in ::JXL_TYPE_UINT16 with @ref + * JXL_BIT_DEPTH_FROM_CODESTREAM bit depth setting, input sample values are + * provided unscaled; + * - if pixel format is in ::JXL_TYPE_FLOAT, input sample values are + * rescaled to 0..1, i.e. multiplied by 1.f/4095.f. While it is allowed, it is + * obviously not recommended to use a pixel_format with lower precision than + * what is specified in the @ref JxlBasicInfo. * - * We support interleaved channels as described by the JxlPixelFormat: + * We support interleaved channels as described by the @ref JxlPixelFormat + * "JxlPixelFormat": * - single-channel data, e.g. grayscale * - single-channel + alpha * - trichromatic, e.g. RGB @@ -577,10 +671,11 @@ JxlEncoderAddJPEGFrame(const JxlEncoderFrameSettings* frame_settings, * set to all-opaque (an alpha value of 1.0 everywhere). * * The pixels are assumed to be encoded in the original profile that is set with - * JxlEncoderSetColorEncoding or JxlEncoderSetICCProfile. If none of these - * functions were used, the pixels are assumed to be nonlinear sRGB for integer - * data types (JXL_TYPE_UINT8, JXL_TYPE_UINT16), and linear sRGB for floating - * point data types (JXL_TYPE_FLOAT16, JXL_TYPE_FLOAT). + * @ref JxlEncoderSetColorEncoding or @ref JxlEncoderSetICCProfile. If none of + * these functions were used, the pixels are assumed to be nonlinear sRGB for + * integer data types (::JXL_TYPE_UINT8, ::JXL_TYPE_UINT16), and linear + * sRGB for floating point data types (::JXL_TYPE_FLOAT16, @ref + * JXL_TYPE_FLOAT). * * Sample values in floating-point pixel formats are allowed to be outside the * nominal range, e.g. to represent out-of-sRGB-gamut colors in the @@ -599,16 +694,274 @@ JxlEncoderAddJPEGFrame(const JxlEncoderFrameSettings* frame_settings, * and its contents are copied internally. * @param size size of buffer in bytes. This size should match what is implied * by the frame dimensions and the pixel format. - * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error + * @return ::JXL_ENC_SUCCESS on success, ::JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderAddImageFrame( const JxlEncoderFrameSettings* frame_settings, const JxlPixelFormat* pixel_format, const void* buffer, size_t size); +/** + * The @ref JxlEncoderOutputProcessor structure provides an interface for the + * encoder's output processing. Users of the library, who want to do streaming + * encoding, should implement the required callbacks for buffering, writing, + * seeking (if supported), and setting a finalized position during the encoding + * process. + * + * At a high level, the processor can be in one of two states: + * - With an active buffer: This indicates that a buffer has been acquired using + * `get_buffer` and encoded data can be written to it. + * - Without an active buffer: In this state, no data can be written. A new + * buffer must be acquired after releasing any previously active buffer. + * + * The library will not acquire more than one buffer at a given time. + * + * The state of the processor includes `position` and `finalized position`, + * which have the following meaning. + * + * - position: Represents the current position, in bytes, within the output + * stream where the encoded data will be written next. This position moves + * forward with each `release_buffer` call as data is written, and can also be + * adjusted through the optional seek callback, if provided. At this position + * the next write will occur. + * + * - finalized position: A position in the output stream that ensures all bytes + * before this point are finalized and won't be changed by later writes. + * + * All fields but `seek` are required, `seek` is optional and can be NULL. + */ +struct JxlEncoderOutputProcessor { + /** + * Required. + * An opaque pointer that the client can use to store custom data. + * This data will be passed to the associated callback functions. + */ + void* opaque; + + /** + * Required. + * Acquires a buffer at the current position into which the library will write + * the output data. + * + * If the `size` argument points to 0 and the returned value is NULL, this + * will be interpreted as asking the output writing to stop. In such a case, + * the library will return an error. The client is expected to set the size of + * the returned buffer based on the suggested `size` when this function is + * called. + * + * @param opaque user supplied parameters to the callback + * @param size points to a suggested buffer size when called; must be set to + * the size of the returned buffer once the function returns. + * @return a pointer to the acquired buffer or NULL to indicate a stop + * condition. + */ + void* (*get_buffer)(void* opaque, size_t* size); + + /** + * Required. + * Notifies the user of library that the current buffer's data has been + * written and can be released. This function should advance the current + * position of the buffer by `written_bytes` number of bytes. + * + * @param opaque user supplied parameters to the callback + * @param written_bytes the number of bytes written to the buffer. + */ + void (*release_buffer)(void* opaque, size_t written_bytes); + + /** + * Optional, can be NULL + * Seeks to a specific position in the output. This function is optional and + * can be set to NULL if the output doesn't support seeking. Can only be done + * when there is no buffer. Cannot be used to seek before the finalized + * position. + * + * @param opaque user supplied parameters to the callback + * @param position the position to seek to, in bytes. + */ + void (*seek)(void* opaque, uint64_t position); + + /** + * Required. + * Sets a finalized position on the output data, at a specific position. + * Seeking will never request a position before the finalized position. + * + * Will only be called if there is no active buffer. + * + * @param opaque user supplied parameters to the callback + * @param finalized_position the position, in bytes, where the finalized + * position should be set. + */ + void (*set_finalized_position)(void* opaque, uint64_t finalized_position); +}; + +/** + * Sets the output processor for the encoder. This processor determines how the + * encoder will handle buffering, writing, seeking (if supported), and + * setting a finalized position during the encoding process. + * + * This should not be used when using @ref JxlEncoderProcessOutput. + * + * @param enc encoder object. + * @param output_processor the struct containing the callbacks for managing + * output. + * @return ::JXL_ENC_SUCCESS on success, ::JXL_ENC_ERROR on error. + */ +JXL_EXPORT JxlEncoderStatus JxlEncoderSetOutputProcessor( + JxlEncoder* enc, struct JxlEncoderOutputProcessor output_processor); + +/** + * Flushes any buffered input in the encoder, ensuring that all available input + * data has been processed and written to the output. + * + * This function can only be used after @ref JxlEncoderSetOutputProcessor. + * Before making the last call to @ref JxlEncoderFlushInput, users should call + * @ref JxlEncoderCloseInput to signal the end of input data. + * + * This should not be used when using @ref JxlEncoderProcessOutput. + * + * @param enc encoder object. + * @return ::JXL_ENC_SUCCESS on success, ::JXL_ENC_ERROR on error. + */ +JXL_EXPORT JxlEncoderStatus JxlEncoderFlushInput(JxlEncoder* enc); + +/** + * This struct provides callback functions to pass pixel data in a streaming + * manner instead of requiring the entire frame data in memory at once. + */ +struct JxlChunkedFrameInputSource { + /** + * A pointer to any user-defined data or state. This can be used to pass + * information to the callback functions. + */ + void* opaque; + + /** + * Get the pixel format that color channel data will be provided in. + * When called, `pixel_format` points to a suggested pixel format; if + * color channel data can be given in this pixel format, processing might + * be more efficient. + * + * This function will be called exactly once, before any call to + * get_color_channel_at. + * + * @param opaque user supplied parameters to the callback + * @param pixel_format format for pixels + */ + void (*get_color_channels_pixel_format)(void* opaque, + JxlPixelFormat* pixel_format); + + /** + * Callback to retrieve a rectangle of color channel data at a specific + * location. It is guaranteed that xpos and ypos are multiples of 8. xsize, + * ysize will be multiples of 8, unless the resulting rectangle would be out + * of image bounds. Moreover, xsize and ysize will be at most 2048. The + * returned data will be assumed to be in the format returned by the + * (preceding) call to get_color_channels_pixel_format, except the `align` + * parameter of the pixel format will be ignored. Instead, the `i`-th row will + * be assumed to start at position `return_value + i * *row_offset`, with the + * value of `*row_offset` decided by the callee. + * + * Note that multiple calls to `get_color_channel_data_at` may happen before a + * call to `release_buffer`. + * + * @param opaque user supplied parameters to the callback + * @param xpos horizontal position for the data. + * @param ypos vertical position for the data. + * @param xsize horizontal size of the requested rectangle of data. + * @param ysize vertical size of the requested rectangle of data. + * @param row_offset pointer to a the byte offset between consecutive rows of + * the retrieved pixel data. + * @return pointer to the retrieved pixel data. + */ + 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); + + /** + * Get the pixel format that extra channel data will be provided in. + * When called, `pixel_format` points to a suggested pixel format; if + * extra channel data can be given in this pixel format, processing might + * be more efficient. + * + * This function will be called exactly once per index, before any call to + * get_extra_channel_data_at with that given index. + * + * @param opaque user supplied parameters to the callback + * @param ec_index zero-indexed index of the extra channel + * @param pixel_format format for extra channel data + */ + void (*get_extra_channel_pixel_format)(void* opaque, size_t ec_index, + JxlPixelFormat* pixel_format); + + /** + * Callback to retrieve a rectangle of extra channel `ec_index` data at a + * specific location. It is guaranteed that xpos and ypos are multiples of + * 8. xsize, ysize will be multiples of 8, unless the resulting rectangle + * would be out of image bounds. Moreover, xsize and ysize will be at most + * 2048. The returned data will be assumed to be in the format returned by the + * (preceding) call to get_extra_channels_pixel_format_at with the + * corresponding extra channel index `ec_index`, except the `align` parameter + * of the pixel format will be ignored. Instead, the `i`-th row will be + * assumed to start at position `return_value + i * *row_offset`, with the + * value of `*row_offset` decided by the callee. + * + * Note that multiple calls to `get_extra_channel_data_at` may happen before a + * call to `release_buffer`. + * + * @param opaque user supplied parameters to the callback + * @param xpos horizontal position for the data. + * @param ypos vertical position for the data. + * @param xsize horizontal size of the requested rectangle of data. + * @param ysize vertical size of the requested rectangle of data. + * @param row_offset pointer to a the byte offset between consecutive rows of + * the retrieved pixel data. + * @return pointer to the retrieved pixel data. + */ + const void* (*get_extra_channel_data_at)(void* opaque, size_t ec_index, + size_t xpos, size_t ypos, + size_t xsize, size_t ysize, + size_t* row_offset); + + /** + * Releases the buffer `buf` (obtained through a call to + * `get_color_channel_data_at` or `get_extra_channel_data_at`). This function + * will be called exactly once per call to `get_color_channel_data_at` or + * `get_extra_channel_data_at`. + * + * @param opaque user supplied parameters to the callback + * @param buf pointer returned by `get_color_channel_data_at` or + * `get_extra_channel_data_at` + */ + void (*release_buffer)(void* opaque, const void* buf); +}; + +/** + * @brief Adds a frame to the encoder using a chunked input source. + * + * This function gives a way to encode a frame by providing pixel data in a + * chunked or streaming manner, which can be especially useful when dealing with + * large images that may not fit entirely in memory or when trying to optimize + * memory usage. The input data is provided through callbacks defined in the + * @ref JxlChunkedFrameInputSource struct. Once the frame data has been + * completely retrieved, this function will flush the input and close it if it + * is the last frame. + * + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. + * @param is_last_frame indicates if this is the last frame. + * @param chunked_frame_input struct providing callback methods for retrieving + * pixel data in chunks. + * + * @return Returns a status indicating the success or failure of adding the + * frame. + */ +JXL_EXPORT JxlEncoderStatus JxlEncoderAddChunkedFrame( + const JxlEncoderFrameSettings* frame_settings, JXL_BOOL is_last_frame, + struct JxlChunkedFrameInputSource chunked_frame_input); + /** * Sets the buffer to read pixels from for an extra channel at a given index. * The index must be smaller than the num_extra_channels in the associated - * JxlBasicInfo. Must call @ref JxlEncoderSetExtraChannelInfo before + * @ref JxlBasicInfo. Must call @ref JxlEncoderSetExtraChannelInfo before @ref * JxlEncoderSetExtraChannelBuffer. * * TODO(firsching): mention what data types in pixel formats are supported. @@ -626,15 +979,15 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderAddImageFrame( * @param size size of buffer in bytes. This size should match what is implied * by the frame dimensions and the pixel format. * @param index index of the extra channel to use. - * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error + * @return ::JXL_ENC_SUCCESS on success, ::JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer( const JxlEncoderFrameSettings* frame_settings, const JxlPixelFormat* pixel_format, const void* buffer, size_t size, uint32_t index); -/** Adds a metadata box to the file format. JxlEncoderProcessOutput must be used - * to effectively write the box to the output. @ref JxlEncoderUseBoxes must +/** Adds a metadata box to the file format. @ref JxlEncoderProcessOutput must be + * used to effectively write the box to the output. @ref JxlEncoderUseBoxes must * be enabled before using this function. * * Boxes allow inserting application-specific data and metadata (Exif, XML/XMP, @@ -661,10 +1014,10 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer( * the encoder encodes the size header itself. Most boxes are written * automatically by the encoder as needed ("JXL ", "ftyp", "jxll", "jxlc", * "jxlp", "jxli", "jbrd"), and this function only needs to be called to add - * optional metadata when encoding from pixels (using JxlEncoderAddImageFrame). - * When recompressing JPEG files (using JxlEncoderAddJPEGFrame), if the input - * JPEG contains EXIF, XMP or JUMBF metadata, the corresponding boxes are - * already added automatically. + * optional metadata when encoding from pixels (using @ref + * JxlEncoderAddImageFrame). When recompressing JPEG files (using @ref + * JxlEncoderAddJPEGFrame), if the input JPEG contains EXIF, XMP or JUMBF + * metadata, the corresponding boxes are already added automatically. * * Box types are given by 4 characters. The following boxes can be added with * this function: @@ -697,9 +1050,9 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer( * @param size size of the box contents. * @param compress_box Whether to compress this box as a "brob" box. Requires * Brotli support. - * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error, such as when - * using this function without JxlEncoderUseContainer, or adding a box type - * that would result in an invalid file format. + * @return ::JXL_ENC_SUCCESS on success, ::JXL_ENC_ERROR on error, such as + * when using this function without @ref JxlEncoderUseContainer, or adding a box + * type that would result in an invalid file format. */ JXL_EXPORT JxlEncoderStatus JxlEncoderAddBox(JxlEncoder* enc, const JxlBoxType type, @@ -726,9 +1079,9 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderUseBoxes(JxlEncoder* enc); * the stream will be finished. It is not necessary to use this function if * @ref JxlEncoderUseBoxes is not used. Further frames may still be added. * - * Must be called between JxlEncoderAddBox of the last box - * and the next call to JxlEncoderProcessOutput, or @ref JxlEncoderProcessOutput - * won't output the last box correctly. + * Must be called between @ref JxlEncoderAddBox of the last box + * and the next call to @ref JxlEncoderProcessOutput, or @ref + * JxlEncoderProcessOutput won't output the last box correctly. * * NOTE: if you don't need to close frames and boxes at separate times, you can * use @ref JxlEncoderCloseInput instead to close both at once. @@ -752,10 +1105,10 @@ JXL_EXPORT void JxlEncoderCloseBoxes(JxlEncoder* enc); JXL_EXPORT void JxlEncoderCloseFrames(JxlEncoder* enc); /** - * Closes any input to the encoder, equivalent to calling JxlEncoderCloseFrames - * as well as calling JxlEncoderCloseBoxes if needed. No further input of any - * kind may be given to the encoder, but further @ref JxlEncoderProcessOutput - * calls should be done to create the final output. + * Closes any input to the encoder, equivalent to calling @ref + * JxlEncoderCloseFrames as well as calling @ref JxlEncoderCloseBoxes if needed. + * No further input of any kind may be given to the encoder, but further @ref + * JxlEncoderProcessOutput calls should be done to create the final output. * * The requirements of both @ref JxlEncoderCloseFrames and @ref * JxlEncoderCloseBoxes apply to this function. Either this function or the @@ -769,39 +1122,39 @@ JXL_EXPORT void JxlEncoderCloseInput(JxlEncoder* enc); /** * Sets the original color encoding of the image encoded by this encoder. This - * is an alternative to JxlEncoderSetICCProfile and only one of these two must - * be used. This one sets the color encoding as a @ref JxlColorEncoding, while - * the other sets it as ICC binary data. - * Must be called after JxlEncoderSetBasicInfo. + * is an alternative to @ref JxlEncoderSetICCProfile and only one of these two + * must be used. This one sets the color encoding as a @ref JxlColorEncoding, + * while the other sets it as ICC binary data. Must be called after @ref + * JxlEncoderSetBasicInfo. * * @param enc encoder object. * @param color color encoding. Object owned by the caller and its contents are * copied internally. - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR or - * JXL_ENC_NOT_SUPPORTED otherwise + * @return ::JXL_ENC_SUCCESS if the operation was successful, @ref + * JXL_ENC_ERROR otherwise */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetColorEncoding(JxlEncoder* enc, const JxlColorEncoding* color); /** * Sets the original color encoding of the image encoded by this encoder as an - * ICC color profile. This is an alternative to JxlEncoderSetColorEncoding and - * only one of these two must be used. This one sets the color encoding as ICC - * binary data, while the other defines it as a @ref JxlColorEncoding. - * Must be called after JxlEncoderSetBasicInfo. + * ICC color profile. This is an alternative to @ref JxlEncoderSetColorEncoding + * and only one of these two must be used. This one sets the color encoding as + * ICC binary data, while the other defines it as a @ref JxlColorEncoding. Must + * be called after @ref JxlEncoderSetBasicInfo. * * @param enc encoder object. * @param icc_profile bytes of the original ICC profile * @param size size of the icc_profile buffer in bytes - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR or - * JXL_ENC_NOT_SUPPORTED otherwise + * @return ::JXL_ENC_SUCCESS if the operation was successful, @ref + * JXL_ENC_ERROR otherwise */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetICCProfile(JxlEncoder* enc, const uint8_t* icc_profile, size_t size); /** - * Initializes a JxlBasicInfo struct to default values. + * Initializes a @ref JxlBasicInfo struct to default values. * For forwards-compatibility, this function has to be called before values * are assigned to the struct fields. * The default values correspond to an 8-bit RGB image, no alpha or any @@ -812,7 +1165,7 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetICCProfile(JxlEncoder* enc, JXL_EXPORT void JxlEncoderInitBasicInfo(JxlBasicInfo* info); /** - * Initializes a JxlFrameHeader struct to default values. + * Initializes a @ref JxlFrameHeader struct to default values. * For forwards-compatibility, this function has to be called before values * are assigned to the struct fields. * The default values correspond to a frame with no animation duration and the @@ -824,7 +1177,7 @@ JXL_EXPORT void JxlEncoderInitBasicInfo(JxlBasicInfo* info); JXL_EXPORT void JxlEncoderInitFrameHeader(JxlFrameHeader* frame_header); /** - * Initializes a JxlBlendInfo struct to default values. + * Initializes a @ref JxlBlendInfo struct to default values. * For forwards-compatibility, this function has to be called before values * are assigned to the struct fields. * @@ -835,23 +1188,43 @@ JXL_EXPORT void JxlEncoderInitBlendInfo(JxlBlendInfo* blend_info); /** * Sets the global metadata of the image encoded by this encoder. * - * If the JxlBasicInfo contains information of extra channels beyond an alpha - * channel, then @ref JxlEncoderSetExtraChannelInfo must be called between - * JxlEncoderSetBasicInfo and @ref JxlEncoderAddImageFrame. In order to indicate - * extra channels, the value of `info.num_extra_channels` should be set to the - * number of extra channels, also counting the alpha channel if present. + * If the @ref JxlBasicInfo contains information of extra channels beyond an + * alpha channel, then @ref JxlEncoderSetExtraChannelInfo must be called between + * @ref JxlEncoderSetBasicInfo and @ref JxlEncoderAddImageFrame. In order to + * indicate extra channels, the value of `info.num_extra_channels` should be set + * to the number of extra channels, also counting the alpha channel if present. * * @param enc encoder object. * @param info global image metadata. Object owned by the caller and its * contents are copied internally. - * @return JXL_ENC_SUCCESS if the operation was successful, - * JXL_ENC_ERROR or JXL_ENC_NOT_SUPPORTED otherwise + * @return ::JXL_ENC_SUCCESS if the operation was successful, + * ::JXL_ENC_ERROR otherwise */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc, const JxlBasicInfo* info); /** - * Initializes a JxlExtraChannelInfo struct to default values. + * Sets the upsampling method the decoder will use in case there are frames + * with ::JXL_ENC_FRAME_SETTING_RESAMPLING set. This is useful in combination + * with the ::JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED option, to control + * the type of upsampling that will be used. + * + * @param enc encoder object. + * @param factor upsampling factor to configure (1, 2, 4 or 8; for 1 this + * function has no effect at all) + * @param mode upsampling mode to use for this upsampling: + * -1: default (good for photographic images, no signaling overhead) + * 0: nearest neighbor (good for pixel art) + * 1: 'pixel dots' (same as NN for 2x, diamond-shaped 'pixel dots' for 4x/8x) + * @return ::JXL_ENC_SUCCESS if the operation was successful, + * ::JXL_ENC_ERROR otherwise + */ +JXL_EXPORT JxlEncoderStatus JxlEncoderSetUpsamplingMode(JxlEncoder* enc, + int64_t factor, + int64_t mode); + +/** + * Initializes a @ref JxlExtraChannelInfo struct to default values. * For forwards-compatibility, this function has to be called before values * are assigned to the struct fields. * The default values correspond to an 8-bit channel of the provided type. @@ -865,23 +1238,24 @@ JXL_EXPORT void JxlEncoderInitExtraChannelInfo(JxlExtraChannelType type, /** * Sets information for the extra channel at the given index. The index - * must be smaller than num_extra_channels in the associated JxlBasicInfo. + * must be smaller than num_extra_channels in the associated @ref JxlBasicInfo. * * @param enc encoder object * @param index index of the extra channel to set. * @param info global extra channel metadata. Object owned by the caller and its * contents are copied internally. - * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error + * @return ::JXL_ENC_SUCCESS on success, ::JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelInfo( JxlEncoder* enc, size_t index, const JxlExtraChannelInfo* info); /** * Sets the name for the extra channel at the given index in UTF-8. The index - * must be smaller than the num_extra_channels in the associated JxlBasicInfo. + * must be smaller than the num_extra_channels in the associated @ref + * JxlBasicInfo. * * TODO(lode): remove size parameter for consistency with - * JxlEncoderSetFrameName + * @ref JxlEncoderSetFrameName * * @param enc encoder object * @param index index of the extra channel to set. @@ -897,17 +1271,18 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelName(JxlEncoder* enc, /** * Sets a frame-specific option of integer type to the encoder options. - * The JxlEncoderFrameSettingId argument determines which option is set. + * The @ref JxlEncoderFrameSettingId argument determines which option is set. * * @param frame_settings set of options and metadata for this frame. Also * includes reference to the encoder object. * @param option ID of the option to set. * @param value Integer value to set for this option. - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR in - * case of an error, such as invalid or unknown option id, or invalid integer - * value for the given option. If an error is returned, the state of the - * JxlEncoderFrameSettings object is still valid and is the same as before this - * function was called. + * @return ::JXL_ENC_SUCCESS if the operation was successful, @ref + * JXL_ENC_ERROR in case of an error, such as invalid or unknown option id, or + * invalid integer value for the given option. If an error is returned, the + * state of the + * @ref JxlEncoderFrameSettings object is still valid and is the same as before + * this function was called. */ JXL_EXPORT JxlEncoderStatus JxlEncoderFrameSettingsSetOption( JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option, @@ -915,17 +1290,18 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderFrameSettingsSetOption( /** * Sets a frame-specific option of float type to the encoder options. - * The JxlEncoderFrameSettingId argument determines which option is set. + * The @ref JxlEncoderFrameSettingId argument determines which option is set. * * @param frame_settings set of options and metadata for this frame. Also * includes reference to the encoder object. * @param option ID of the option to set. * @param value Float value to set for this option. - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR in - * case of an error, such as invalid or unknown option id, or invalid integer - * value for the given option. If an error is returned, the state of the - * JxlEncoderFrameSettings object is still valid and is the same as before this - * function was called. + * @return ::JXL_ENC_SUCCESS if the operation was successful, @ref + * JXL_ENC_ERROR in case of an error, such as invalid or unknown option id, or + * invalid integer value for the given option. If an error is returned, the + * state of the + * @ref JxlEncoderFrameSettings object is still valid and is the same as before + * this function was called. */ JXL_EXPORT JxlEncoderStatus JxlEncoderFrameSettingsSetFloatOption( JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option, @@ -937,7 +1313,7 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderFrameSettingsSetFloatOption( * When using @ref JxlEncoderUseBoxes, @ref JxlEncoderStoreJPEGMetadata or @ref * JxlEncoderSetCodestreamLevel with level 10, the encoder will automatically * also use the container format, it is not necessary to use - * JxlEncoderUseContainer for those use cases. + * @ref JxlEncoderUseContainer for those use cases. * * By default this setting is disabled. * @@ -963,8 +1339,8 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderUseContainer(JxlEncoder* enc, * * @param enc encoder object. * @param store_jpeg_metadata true if the encoder should store JPEG metadata. - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR - * otherwise. + * @return ::JXL_ENC_SUCCESS if the operation was successful, @ref + * JXL_ENC_ERROR otherwise. */ JXL_EXPORT JxlEncoderStatus JxlEncoderStoreJPEGMetadata(JxlEncoder* enc, JXL_BOOL store_jpeg_metadata); @@ -979,8 +1355,8 @@ JxlEncoderStoreJPEGMetadata(JxlEncoder* enc, JXL_BOOL store_jpeg_metadata); * 268435456 pixels total with a maximum width or height of 262144 pixels, * maximum 16-bit color channel depth, maximum 120 frames per second for * animation, maximum ICC color profile size of 4 MiB, it allows all color - * models and extra channel types except CMYK and the JXL_CHANNEL_BLACK extra - * channel, and a maximum of 4 extra channels in addition to the 3 color + * models and extra channel types except CMYK and the JXL_CHANNEL_BLACK + * extra channel, and a maximum of 4 extra channels in addition to the 3 color * channels. It also sets boundaries to certain internally used coding tools. * * Level 10: this level removes or increases the bounds of most of the level @@ -1000,8 +1376,8 @@ JxlEncoderStoreJPEGMetadata(JxlEncoder* enc, JXL_BOOL store_jpeg_metadata); * * @param enc encoder object. * @param level the level value to set, must be -1, 5, or 10. - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR - * otherwise. + * @return ::JXL_ENC_SUCCESS if the operation was successful, @ref + * JXL_ENC_ERROR otherwise. */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetCodestreamLevel(JxlEncoder* enc, int level); @@ -1015,9 +1391,10 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetCodestreamLevel(JxlEncoder* enc, * the JPEG XL file. * * If this returns 5, nothing needs to be done and the codestream can be - * compatible with any decoder. If this returns 10, JxlEncoderSetCodestreamLevel - * has to be used to set the codestream level to 10, or the encoder can be - * configured differently to allow using the more compatible level 5. + * compatible with any decoder. If this returns 10, @ref + * JxlEncoderSetCodestreamLevel has to be used to set the codestream level to + * 10, or the encoder can be configured differently to allow using the more + * compatible level 5. * * @param enc encoder object. * @return -1 if no level can support the configuration (e.g. image dimensions @@ -1036,53 +1413,22 @@ JXL_EXPORT int JxlEncoderGetRequiredCodestreamLevel(const JxlEncoder* enc); * * When disabled, those options are not overridden, but since those options * could still have been manually set to a combination that operates losslessly, - * using this function with lossless set to JXL_DEC_FALSE does not guarantee - * lossy encoding, though the default set of options is lossy. + * using this function with lossless set to ::JXL_FALSE does not + * guarantee lossy encoding, though the default set of options is lossy. * * @param frame_settings set of options and metadata for this frame. Also * includes reference to the encoder object. * @param lossless whether to override options for lossless mode - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR - * otherwise. + * @return ::JXL_ENC_SUCCESS if the operation was successful, @ref + * JXL_ENC_ERROR otherwise. */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameLossless( JxlEncoderFrameSettings* frame_settings, JXL_BOOL lossless); -/** DEPRECATED: use JxlEncoderSetFrameLossless instead. - */ -JXL_EXPORT JxlEncoderStatus -JxlEncoderOptionsSetLossless(JxlEncoderFrameSettings*, JXL_BOOL); - -/** - * @param frame_settings set of options and metadata for this frame. Also - * includes reference to the encoder object. - * @param effort the effort value to set. - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR - * otherwise. - * - * DEPRECATED: use JxlEncoderFrameSettingsSetOption(frame_settings, - * JXL_ENC_FRAME_SETTING_EFFORT, effort) instead. - */ -JXL_EXPORT JXL_DEPRECATED JxlEncoderStatus -JxlEncoderOptionsSetEffort(JxlEncoderFrameSettings* frame_settings, int effort); - -/** - * @param frame_settings set of options and metadata for this frame. Also - * includes reference to the encoder object. - * @param tier the decoding speed tier to set. - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR - * otherwise. - * - * DEPRECATED: use JxlEncoderFrameSettingsSetOption(frame_settings, - * JXL_ENC_FRAME_SETTING_DECODING_SPEED, tier) instead. - */ -JXL_EXPORT JXL_DEPRECATED JxlEncoderStatus JxlEncoderOptionsSetDecodingSpeed( - JxlEncoderFrameSettings* frame_settings, int tier); - /** * Sets the distance level for lossy compression: target max butteraugli - * distance, lower = higher quality. Range: 0 .. 15. - * 0.0 = mathematically lossless (however, use JxlEncoderSetFrameLossless + * distance, lower = higher quality. Range: 0 .. 25. + * 0.0 = mathematically lossless (however, use @ref JxlEncoderSetFrameLossless * instead to use true lossless, as setting distance to 0 alone is not the only * requirement). 1.0 = visually lossless. Recommended range: 0.5 .. 3.0. Default * value: 1.0. @@ -1090,26 +1436,75 @@ JXL_EXPORT JXL_DEPRECATED JxlEncoderStatus JxlEncoderOptionsSetDecodingSpeed( * @param frame_settings set of options and metadata for this frame. Also * includes reference to the encoder object. * @param distance the distance value to set. - * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR - * otherwise. + * @return ::JXL_ENC_SUCCESS if the operation was successful, @ref + * JXL_ENC_ERROR otherwise. */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameDistance( JxlEncoderFrameSettings* frame_settings, float distance); -/** DEPRECATED: use JxlEncoderSetFrameDistance instead. +/** + * Sets the distance level for lossy compression of extra channels. + * The distance is as in @ref JxlEncoderSetFrameDistance (lower = higher + * quality). If not set, or if set to the special value -1, the distance that + * was set with + * @ref JxlEncoderSetFrameDistance will be used. + * + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. + * @param index index of the extra channel to set a distance value for. + * @param distance the distance value to set. + * @return ::JXL_ENC_SUCCESS if the operation was successful, @ref + * JXL_ENC_ERROR otherwise. */ -JXL_EXPORT JXL_DEPRECATED JxlEncoderStatus -JxlEncoderOptionsSetDistance(JxlEncoderFrameSettings*, float); +JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelDistance( + JxlEncoderFrameSettings* frame_settings, size_t index, float distance); + +/** + * Maps JPEG-style quality factor to distance. + * + * This function takes in input a JPEG-style quality factor `quality` and + * produces as output a `distance` value suitable to be used with @ref + * JxlEncoderSetFrameDistance and @ref JxlEncoderSetExtraChannelDistance. + * + * The `distance` value influences the level of compression, with lower values + * indicating higher quality: + * - 0.0 implies lossless compression (however, note that calling @ref + * JxlEncoderSetFrameLossless is required). + * - 1.0 represents a visually lossy compression, which is also the default + * setting. + * + * The `quality` parameter, ranging up to 100, is inversely related to + * 'distance': + * - A `quality` of 100.0 maps to a `distance` of 0.0 (lossless). + * - A `quality` of 90.0 corresponds to a `distance` of 1.0. + * + * Recommended Range: + * - `distance`: 0.5 to 3.0. + * - corresponding `quality`: approximately 96 to 68. + * + * Allowed Range: + * - `distance`: 0.0 to 25.0. + * - corresponding `quality`: 100.0 to 0.0. + * + * Note: the `quality` parameter has no consistent psychovisual meaning + * across different codecs and libraries. Using the mapping defined by @ref + * JxlEncoderDistanceFromQuality will result in a visual quality roughly + * equivalent to what would be obtained with `libjpeg-turbo` with the same + * `quality` parameter, but that is by no means guaranteed; do not assume that + * the same quality value will result in similar file sizes and image quality + * across different codecs. + */ +JXL_EXPORT float JxlEncoderDistanceFromQuality(float quality); /** * Create a new set of encoder options, with all values initially copied from * the @p source options, or set to default if @p source is NULL. * * The returned pointer is an opaque struct tied to the encoder and it will be - * deallocated by the encoder when JxlEncoderDestroy() is called. For functions - * taking both a @ref JxlEncoder and a @ref JxlEncoderFrameSettings, only - * JxlEncoderFrameSettings created with this function for the same encoder - * instance can be used. + * deallocated by the encoder when @ref JxlEncoderDestroy() is called. For + * functions taking both a @ref JxlEncoder and a @ref JxlEncoderFrameSettings, + * only @ref JxlEncoderFrameSettings created with this function for the same + * encoder instance can be used. * * @param enc encoder object. * @param source source options to copy initial values from, or NULL to get @@ -1119,11 +1514,6 @@ JxlEncoderOptionsSetDistance(JxlEncoderFrameSettings*, float); JXL_EXPORT JxlEncoderFrameSettings* JxlEncoderFrameSettingsCreate( JxlEncoder* enc, const JxlEncoderFrameSettings* source); -/** DEPRECATED: use JxlEncoderFrameSettingsCreate instead. - */ -JXL_EXPORT JXL_DEPRECATED JxlEncoderFrameSettings* JxlEncoderOptionsCreate( - JxlEncoder*, const JxlEncoderFrameSettings*); - /** * Sets a color encoding to be sRGB. * @@ -1142,7 +1532,68 @@ JXL_EXPORT void JxlColorEncodingSetToSRGB(JxlColorEncoding* color_encoding, JXL_EXPORT void JxlColorEncodingSetToLinearSRGB( JxlColorEncoding* color_encoding, JXL_BOOL is_gray); -#if defined(__cplusplus) || defined(c_plusplus) +/** + * Enables usage of expert options. + * + * At the moment, the only expert option is setting an effort value of 11, + * which gives the best compression for pixel-lossless modes but is very slow. + * + * @param enc encoder object. + */ +JXL_EXPORT void JxlEncoderAllowExpertOptions(JxlEncoder* enc); + +/** + * Function type for @ref JxlEncoderSetDebugImageCallback. + * + * The callback may be called simultaneously by different threads when using a + * threaded parallel runner, on different debug images. + * + * @param opaque optional user data, as given to @ref + * JxlEncoderSetDebugImageCallback. + * @param label label of debug image, can be used in filenames + * @param xsize width of debug image + * @param ysize height of debug image + * @param color color encoding of debug image + * @param pixels pixel data of debug image as big-endian 16-bit unsigned + * samples. The memory is not owned by the user, and is only valid during the + * time the callback is running. + */ +typedef void (*JxlDebugImageCallback)(void* opaque, const char* label, + size_t xsize, size_t ysize, + const JxlColorEncoding* color, + const uint16_t* pixels); + +/** + * Sets the given debug image callback that will be used by the encoder to + * output various debug images during encoding. + * + * This only has any effect if the encoder was compiled with the appropriate + * debug build flags. + * + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. + * @param callback used to return the debug image + * @param opaque user supplied parameter to the image callback + */ +JXL_EXPORT void JxlEncoderSetDebugImageCallback( + JxlEncoderFrameSettings* frame_settings, JxlDebugImageCallback callback, + void* opaque); + +/** + * Sets the given stats object for gathering various statistics during encoding. + * + * This only has any effect if the encoder was compiled with the appropriate + * debug build flags. + * + * @param frame_settings set of options and metadata for this frame. Also + * includes reference to the encoder object. + * @param stats object that can be used to query the gathered stats (created + * by @ref JxlEncoderStatsCreate) + */ +JXL_EXPORT void JxlEncoderCollectStats(JxlEncoderFrameSettings* frame_settings, + JxlEncoderStats* stats); + +#ifdef __cplusplus } #endif diff --git a/media/libjxl/src/lib/include/jxl/encode_cxx.h b/media/libjxl/src/lib/include/jxl/encode_cxx.h index 494c03c7e3..8e552357ac 100644 --- a/media/libjxl/src/lib/include/jxl/encode_cxx.h +++ b/media/libjxl/src/lib/include/jxl/encode_cxx.h @@ -3,7 +3,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -/// @addtogroup libjxl_encoder +/// @addtogroup libjxl_cpp ///@{ /// /// @file encode_cxx.h @@ -15,11 +15,12 @@ #ifndef JXL_ENCODE_CXX_H_ #define JXL_ENCODE_CXX_H_ +#include +#include + #include -#include "jxl/encode.h" - -#if !(defined(__cplusplus) || defined(c_plusplus)) +#ifndef __cplusplus #error "This a C++ only header. Use jxl/encode.h from C sources." #endif diff --git a/media/libjxl/src/lib/include/jxl/gain_map.h b/media/libjxl/src/lib/include/jxl/gain_map.h new file mode 100644 index 0000000000..e1b314b56c --- /dev/null +++ b/media/libjxl/src/lib/include/jxl/gain_map.h @@ -0,0 +1,129 @@ +/* Copyright (c) the JPEG XL Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +/** @addtogroup libjxl_metadata + * @{ + * @file gain_map.h + * @brief Utility functions to manipulate jhgm (gain map) boxes. + */ + +#ifndef JXL_GAIN_MAP_H_ +#define JXL_GAIN_MAP_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Gain map bundle + * + * This structure is used to serialize gain map data to and from an input + * buffer. It holds pointers to sections within the buffer, and different parts + * of the gain map data such as metadata, ICC profile data, and the gain map + * itself. + * + * The pointers in this structure do not take ownership of the memory they point + * to. Instead, they reference specific locations within the provided buffer. It + * is the caller's responsibility to ensure that the buffer remains valid and is + * not deallocated as long as these pointers are in use. The structure should be + * considered as providing a view into the buffer, not as an owner of the data. + */ +typedef struct { + /** Version number of the gain map bundle. */ + uint8_t jhgm_version; + /** Size of the gain map metadata in bytes. */ + uint16_t gain_map_metadata_size; + /** Pointer to the gain map metadata, which is a binary + * blob following ISO 21496-1. This pointer references data within the input + * buffer. */ + const uint8_t* gain_map_metadata; + /** Indicates whether a color encoding is present. */ + JXL_BOOL has_color_encoding; + /** If has_color_encoding is true, this field contains the + * uncompressed color encoding data. */ + JxlColorEncoding color_encoding; + /** Size of the alternative ICC profile in bytes (compressed + * size). */ + uint32_t alt_icc_size; + /** Pointer to the compressed ICC profile. This pointer references + * data within the input buffer. */ + const uint8_t* alt_icc; + /** Size of the gain map in bytes. */ + uint32_t gain_map_size; + /** Pointer to the gain map data, which is a JPEG XL naked + * codestream. This pointer references data within the input buffer.*/ + const uint8_t* gain_map; +} JxlGainMapBundle; + +/** + * Calculates the total size required to serialize the gain map bundle into a + * binary buffer. This function accounts for all the necessary space to + * serialize fields such as gain map metadata, color encoding, compressed ICC + * profile data, and the gain map itself. + * + * @param[in] map_bundle Pointer to the JxlGainMapBundle containing all + * necessary data to compute the size. + * @param[out] bundle_size The size in bytes required to serialize the bundle. + * @return Whether setting the size was successful. + */ +JXL_EXPORT JXL_BOOL JxlGainMapGetBundleSize(const JxlGainMapBundle* map_bundle, + size_t* bundle_size); + +/** + * Serializes the gain map bundle into a preallocated buffer. The function + * ensures that all parts of the bundle such as metadata, color encoding, + * compressed ICC profile, and the gain map are correctly encoded into the + * buffer. First call `JxlGainMapGetBundleSize` to get the size needed for + * the buffer. + * + * @param[in] map_bundle Pointer to the `JxlGainMapBundle` to serialize. + * @param[out] output_buffer Pointer to the buffer where the serialized data + * will be written. + * @param[in] output_buffer_size The size of the output buffer in bytes. Must be + * large enough to hold the entire serialized data. + * @param[out] bytes_written The number of bytes written to the output buffer. + * @return Whether writing the bundle was successful. + */ +JXL_EXPORT JXL_BOOL JxlGainMapWriteBundle(const JxlGainMapBundle* map_bundle, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* bytes_written); + +/** + * Deserializes a gain map bundle from a provided buffer and populates a + * `JxlGainMapBundle` structure with the data extracted. This function assumes + * the buffer contains a valid serialized gain map bundle. After successful + * execution, the `JxlGainMapBundle` structure will reference three different + * sections within the buffer: + * - gain_map_metadata + * - alt_icc + * - gain_map + * These sections will be accompanied by their respective sizes. Users must + * ensure that the buffer remains valid as long as these pointers are in use. + * @param[in,out] map_bundle Pointer to a preallocated `JxlGainMapBundle` where + * the deserialized data will be stored. + * @param[in] input_buffer Pointer to the buffer containing the serialized gain + * map bundle data. + * @param[in] input_buffer_size The size of the input buffer in bytes. + * @param[out] bytes_read The number of bytes read from the input buffer. + * @return Whether reading the bundle was successful. + */ +JXL_EXPORT JXL_BOOL JxlGainMapReadBundle(JxlGainMapBundle* map_bundle, + const uint8_t* input_buffer, + size_t input_buffer_size, + size_t* bytes_read); + +#ifdef __cplusplus +} +#endif + +#endif /* JXL_GAIN_MAP_H_ */ + +/** @} */ diff --git a/media/libjxl/src/lib/include/jxl/memory_manager.h b/media/libjxl/src/lib/include/jxl/memory_manager.h index 52640a8beb..05b4abe90b 100644 --- a/media/libjxl/src/lib/include/jxl/memory_manager.h +++ b/media/libjxl/src/lib/include/jxl/memory_manager.h @@ -15,7 +15,7 @@ #include -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -55,7 +55,8 @@ typedef struct JxlMemoryManagerStruct { /** Memory allocation function. This can be NULL if and only if also the * free() member in this class is NULL. All dynamic memory will be allocated - * and freed with these functions if they are not NULL. */ + * and freed with these functions if they are not NULL, otherwise with the + * standard malloc/free. */ jpegxl_alloc_func alloc; /** Free function matching the alloc() member. */ jpegxl_free_func free; @@ -63,7 +64,7 @@ typedef struct JxlMemoryManagerStruct { /* TODO(deymo): Add cache-aligned alloc/free functions here. */ } JxlMemoryManager; -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } #endif diff --git a/media/libjxl/src/lib/include/jxl/parallel_runner.h b/media/libjxl/src/lib/include/jxl/parallel_runner.h index 45394e972c..742f9598e9 100644 --- a/media/libjxl/src/lib/include/jxl/parallel_runner.h +++ b/media/libjxl/src/lib/include/jxl/parallel_runner.h @@ -4,7 +4,7 @@ * license that can be found in the LICENSE file. */ -/** @addtogroup libjxl_common +/** @addtogroup libjxl_threads * @{ */ /** @@ -40,45 +40,52 @@ #include #include -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif /** Return code used in the JxlParallel* functions as return value. A value - * of 0 means success and any other value means error. The special value - * JXL_PARALLEL_RET_RUNNER_ERROR can be used by the runner to indicate any - * other error. + * of ::JXL_PARALLEL_RET_SUCCESS means success and any other value means error. + * The special value ::JXL_PARALLEL_RET_RUNNER_ERROR can be used by the runner + * to indicate any other error. */ typedef int JxlParallelRetCode; /** - * General error returned by the JxlParallelRunInit function to indicate - * an error. + * Code returned by the @ref JxlParallelRunInit function to indicate success. + */ +#define JXL_PARALLEL_RET_SUCCESS (0) + +/** + * Code returned by the @ref JxlParallelRunInit function to indicate a general + * error. */ #define JXL_PARALLEL_RET_RUNNER_ERROR (-1) /** - * Parallel run initialization callback. See JxlParallelRunner for details. + * Parallel run initialization callback. See @ref JxlParallelRunner for details. * * This function MUST be called by the JxlParallelRunner only once, on the - * same thread that called JxlParallelRunner, before any parallel execution. - * The purpose of this call is to provide the maximum number of threads that the - * JxlParallelRunner will use, which can be used by JPEG XL to allocate + * same thread that called @ref JxlParallelRunner, before any parallel + * execution. The purpose of this call is to provide the maximum number of + * threads that the + * @ref JxlParallelRunner will use, which can be used by JPEG XL to allocate * per-thread storage if needed. * * @param jpegxl_opaque the @p jpegxl_opaque handle provided to - * JxlParallelRunner() must be passed here. + * @ref JxlParallelRunner() must be passed here. * @param num_threads the maximum number of threads. This value must be * positive. * @return 0 if the initialization process was successful. * @return an error code if there was an error, which should be returned by - * JxlParallelRunner(). + * @ref JxlParallelRunner(). */ typedef JxlParallelRetCode (*JxlParallelRunInit)(void* jpegxl_opaque, size_t num_threads); /** - * Parallel run data processing callback. See JxlParallelRunner for details. + * Parallel run data processing callback. See @ref JxlParallelRunner for + * details. * * This function MUST be called once for every number in the range [start_range, * end_range) (including start_range but not including end_range) passing this @@ -86,11 +93,11 @@ typedef JxlParallelRetCode (*JxlParallelRunInit)(void* jpegxl_opaque, * different threads in parallel. * * @param jpegxl_opaque the @p jpegxl_opaque handle provided to - * JxlParallelRunner() must be passed here. + * @ref JxlParallelRunner() must be passed here. * @param value the number in the range [start_range, end_range) of the call. * @param thread_id the thread number where this function is being called from. * This must be lower than the @p num_threads value passed to - * JxlParallelRunInit. + * @ref JxlParallelRunInit. */ typedef void (*JxlParallelRunFunction)(void* jpegxl_opaque, uint32_t value, size_t thread_id); @@ -103,11 +110,12 @@ typedef void (*JxlParallelRunFunction)(void* jpegxl_opaque, uint32_t value, * number in the range [start_range, end_range) (including start_range but not * including end_range) possibly from different multiple threads in parallel. * - * The JxlParallelRunner function does not need to be re-entrant. This means - * that the same JxlParallelRunner function with the same runner_opaque - * provided parameter will not be called from the library from either @p init or + * The @ref JxlParallelRunner function does not need to be re-entrant. This + * means that the same @ref JxlParallelRunner function with the same + * runner_opaque provided parameter will not be called from the library from + * either @p init or * @p func in the same decoder or encoder instance. However, a single decoding - * or encoding instance may call the provided JxlParallelRunner multiple + * or encoding instance may call the provided @ref JxlParallelRunner multiple * times for different parts of the decoding or encoding process. * * @return 0 if the @p init call succeeded (returned 0) and no other error @@ -120,7 +128,7 @@ typedef JxlParallelRetCode (*JxlParallelRunner)( void* runner_opaque, void* jpegxl_opaque, JxlParallelRunInit init, JxlParallelRunFunction func, uint32_t start_range, uint32_t end_range); -/* The following is an example of a JxlParallelRunner that doesn't use any +/* The following is an example of a @ref JxlParallelRunner that doesn't use any * multi-threading. Note that this implementation doesn't store any state * between multiple calls of the ExampleSequentialRunner function, so the * runner_opaque value is not used. @@ -143,11 +151,11 @@ typedef JxlParallelRetCode (*JxlParallelRunner)( // order. (*func)(jpegxl_opaque, i, 0); } - return 0; + return JXL_PARALLEL_RET_SUCCESS; } */ -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } #endif diff --git a/media/libjxl/src/lib/include/jxl/resizable_parallel_runner.h b/media/libjxl/src/lib/include/jxl/resizable_parallel_runner.h index f6344bdfdd..b0bf68091e 100644 --- a/media/libjxl/src/lib/include/jxl/resizable_parallel_runner.h +++ b/media/libjxl/src/lib/include/jxl/resizable_parallel_runner.h @@ -16,7 +16,7 @@ * created can be changed after creation of the thread pool; the threads * (including the main thread) are re-used for every * ResizableParallelRunner::Runner call. Only one concurrent - * JxlResizableParallelRunner call per instance is allowed at a time. + * @ref JxlResizableParallelRunner call per instance is allowed at a time. * * This is a scalable, lower-overhead thread pool runner, especially suitable * for data-parallel computations in the fork-join model, where clients need to @@ -30,33 +30,31 @@ #ifndef JXL_RESIZABLE_PARALLEL_RUNNER_H_ #define JXL_RESIZABLE_PARALLEL_RUNNER_H_ +#include +#include +#include #include #include -#include #include -#include "jxl/jxl_threads_export.h" -#include "jxl/memory_manager.h" -#include "jxl/parallel_runner.h" - -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif -/** Parallel runner internally using std::thread. Use as JxlParallelRunner. +/** Parallel runner internally using std::thread. Use as @ref JxlParallelRunner. */ JXL_THREADS_EXPORT JxlParallelRetCode JxlResizableParallelRunner( void* runner_opaque, void* jpegxl_opaque, JxlParallelRunInit init, JxlParallelRunFunction func, uint32_t start_range, uint32_t end_range); -/** Creates the runner for JxlResizableParallelRunner. Use as the opaque +/** Creates the runner for @ref JxlResizableParallelRunner. Use as the opaque * runner. The runner will execute tasks on the calling thread until * @ref JxlResizableParallelRunnerSetThreads is called. */ JXL_THREADS_EXPORT void* JxlResizableParallelRunnerCreate( const JxlMemoryManager* memory_manager); -/** Changes the number of threads for JxlResizableParallelRunner. +/** Changes the number of threads for @ref JxlResizableParallelRunner. */ JXL_THREADS_EXPORT void JxlResizableParallelRunnerSetThreads( void* runner_opaque, size_t num_threads); @@ -66,11 +64,11 @@ JXL_THREADS_EXPORT void JxlResizableParallelRunnerSetThreads( JXL_THREADS_EXPORT uint32_t JxlResizableParallelRunnerSuggestThreads(uint64_t xsize, uint64_t ysize); -/** Destroys the runner created by JxlResizableParallelRunnerCreate. +/** Destroys the runner created by @ref JxlResizableParallelRunnerCreate. */ JXL_THREADS_EXPORT void JxlResizableParallelRunnerDestroy(void* runner_opaque); -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } #endif diff --git a/media/libjxl/src/lib/include/jxl/resizable_parallel_runner_cxx.h b/media/libjxl/src/lib/include/jxl/resizable_parallel_runner_cxx.h index 9a310c81aa..344c96cc9e 100644 --- a/media/libjxl/src/lib/include/jxl/resizable_parallel_runner_cxx.h +++ b/media/libjxl/src/lib/include/jxl/resizable_parallel_runner_cxx.h @@ -3,7 +3,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -/// @addtogroup libjxl_threads +/// @addtogroup libjxl_cpp /// @{ /// /// @file resizable_parallel_runner_cxx.h @@ -16,11 +16,12 @@ #ifndef JXL_RESIZABLE_PARALLEL_RUNNER_CXX_H_ #define JXL_RESIZABLE_PARALLEL_RUNNER_CXX_H_ +#include +#include + #include -#include "jxl/resizable_parallel_runner.h" - -#if !(defined(__cplusplus) || defined(c_plusplus)) +#ifndef __cplusplus #error \ "This a C++ only header. Use jxl/jxl_resizable_parallel_runner.h from C" \ "sources." diff --git a/media/libjxl/src/lib/include/jxl/stats.h b/media/libjxl/src/lib/include/jxl/stats.h new file mode 100644 index 0000000000..35930b4da1 --- /dev/null +++ b/media/libjxl/src/lib/include/jxl/stats.h @@ -0,0 +1,103 @@ +/* Copyright (c) the JPEG XL Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file. + */ + +/** @addtogroup libjxl_encoder + * @{ + * @file stats.h + * @brief API to collect various statistics from JXL encoder. + */ + +#ifndef JXL_STATS_H_ +#define JXL_STATS_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Opaque structure that holds the encoder statistics. + * + * Allocated and initialized with @ref JxlEncoderStatsCreate(). + * Cleaned up and deallocated with @ref JxlEncoderStatsDestroy(). + */ +typedef struct JxlEncoderStatsStruct JxlEncoderStats; + +/** + * Creates an instance of JxlEncoderStats and initializes it. + * + * @return pointer to initialized @ref JxlEncoderStats instance + */ +JXL_EXPORT JxlEncoderStats* JxlEncoderStatsCreate(void); + +/** + * Deinitializes and frees JxlEncoderStats instance. + * + * @param stats instance to be cleaned up and deallocated. No-op if stats is + * null pointer. + */ +JXL_EXPORT void JxlEncoderStatsDestroy(JxlEncoderStats* stats); + +/** Data type for querying @ref JxlEncoderStats object + */ +typedef enum { + JXL_ENC_STAT_HEADER_BITS, + JXL_ENC_STAT_TOC_BITS, + JXL_ENC_STAT_DICTIONARY_BITS, + JXL_ENC_STAT_SPLINES_BITS, + JXL_ENC_STAT_NOISE_BITS, + JXL_ENC_STAT_QUANT_BITS, + JXL_ENC_STAT_MODULAR_TREE_BITS, + JXL_ENC_STAT_MODULAR_GLOBAL_BITS, + JXL_ENC_STAT_DC_BITS, + JXL_ENC_STAT_MODULAR_DC_GROUP_BITS, + JXL_ENC_STAT_CONTROL_FIELDS_BITS, + JXL_ENC_STAT_COEF_ORDER_BITS, + JXL_ENC_STAT_AC_HISTOGRAM_BITS, + JXL_ENC_STAT_AC_BITS, + JXL_ENC_STAT_MODULAR_AC_GROUP_BITS, + JXL_ENC_STAT_NUM_SMALL_BLOCKS, + JXL_ENC_STAT_NUM_DCT4X8_BLOCKS, + JXL_ENC_STAT_NUM_AFV_BLOCKS, + JXL_ENC_STAT_NUM_DCT8_BLOCKS, + JXL_ENC_STAT_NUM_DCT8X32_BLOCKS, + JXL_ENC_STAT_NUM_DCT16_BLOCKS, + JXL_ENC_STAT_NUM_DCT16X32_BLOCKS, + JXL_ENC_STAT_NUM_DCT32_BLOCKS, + JXL_ENC_STAT_NUM_DCT32X64_BLOCKS, + JXL_ENC_STAT_NUM_DCT64_BLOCKS, + JXL_ENC_STAT_NUM_BUTTERAUGLI_ITERS, + JXL_ENC_NUM_STATS, +} JxlEncoderStatsKey; + +/** Returns the value of the statistics corresponding the given key. + * + * @param stats object that was passed to the encoder with a + * @ref JxlEncoderCollectStats function + * @param key the particular statistics to query + * + * @return the value of the statistics + */ +JXL_EXPORT size_t JxlEncoderStatsGet(const JxlEncoderStats* stats, + JxlEncoderStatsKey key); + +/** Updates the values of the given stats object with that of an other. + * + * @param stats object whose values will be updated (usually added together) + * @param other stats object whose values will be merged with stats + */ +JXL_EXPORT void JxlEncoderStatsMerge(JxlEncoderStats* stats, + const JxlEncoderStats* other); + +#ifdef __cplusplus +} +#endif + +#endif /* JXL_STATS_H_ */ + +/** @}*/ diff --git a/media/libjxl/src/lib/include/jxl/thread_parallel_runner.h b/media/libjxl/src/lib/include/jxl/thread_parallel_runner.h index 581ff73277..fbfe9e2074 100644 --- a/media/libjxl/src/lib/include/jxl/thread_parallel_runner.h +++ b/media/libjxl/src/lib/include/jxl/thread_parallel_runner.h @@ -30,41 +30,39 @@ #ifndef JXL_THREAD_PARALLEL_RUNNER_H_ #define JXL_THREAD_PARALLEL_RUNNER_H_ +#include +#include +#include #include #include -#include #include -#include "jxl/jxl_threads_export.h" -#include "jxl/memory_manager.h" -#include "jxl/parallel_runner.h" - -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif -/** Parallel runner internally using std::thread. Use as JxlParallelRunner. +/** Parallel runner internally using std::thread. Use as @ref JxlParallelRunner. */ JXL_THREADS_EXPORT JxlParallelRetCode JxlThreadParallelRunner( void* runner_opaque, void* jpegxl_opaque, JxlParallelRunInit init, JxlParallelRunFunction func, uint32_t start_range, uint32_t end_range); -/** Creates the runner for JxlThreadParallelRunner. Use as the opaque +/** Creates the runner for @ref JxlThreadParallelRunner. Use as the opaque * runner. */ JXL_THREADS_EXPORT void* JxlThreadParallelRunnerCreate( const JxlMemoryManager* memory_manager, size_t num_worker_threads); -/** Destroys the runner created by JxlThreadParallelRunnerCreate. +/** Destroys the runner created by @ref JxlThreadParallelRunnerCreate. */ JXL_THREADS_EXPORT void JxlThreadParallelRunnerDestroy(void* runner_opaque); /** Returns a default num_worker_threads value for - * JxlThreadParallelRunnerCreate. + * @ref JxlThreadParallelRunnerCreate. */ -JXL_THREADS_EXPORT size_t JxlThreadParallelRunnerDefaultNumWorkerThreads(); +JXL_THREADS_EXPORT size_t JxlThreadParallelRunnerDefaultNumWorkerThreads(void); -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } #endif diff --git a/media/libjxl/src/lib/include/jxl/thread_parallel_runner_cxx.h b/media/libjxl/src/lib/include/jxl/thread_parallel_runner_cxx.h index a71d18c20a..7f35ca00e2 100644 --- a/media/libjxl/src/lib/include/jxl/thread_parallel_runner_cxx.h +++ b/media/libjxl/src/lib/include/jxl/thread_parallel_runner_cxx.h @@ -3,7 +3,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -/// @addtogroup libjxl_threads +/// @addtogroup libjxl_cpp /// @{ /// /// @file thread_parallel_runner_cxx.h @@ -15,11 +15,13 @@ #ifndef JXL_THREAD_PARALLEL_RUNNER_CXX_H_ #define JXL_THREAD_PARALLEL_RUNNER_CXX_H_ +#include +#include + +#include #include -#include "jxl/thread_parallel_runner.h" - -#if !(defined(__cplusplus) || defined(c_plusplus)) +#ifndef __cplusplus #error \ "This a C++ only header. Use jxl/jxl_thread_parallel_runner.h from C" \ "sources." diff --git a/media/libjxl/src/lib/include/jxl/types.h b/media/libjxl/src/lib/include/jxl/types.h index 1f8197864d..1844375f68 100644 --- a/media/libjxl/src/lib/include/jxl/types.h +++ b/media/libjxl/src/lib/include/jxl/types.h @@ -16,9 +16,7 @@ #include #include -#include "jxl/jxl_export.h" - -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus extern "C" { #endif @@ -33,13 +31,17 @@ extern "C" { #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) +/** Converts JXL_BOOL to C++ bool. */ +#define FROM_JXL_BOOL(C) (static_cast(C)) /** 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 * (within gamut, may go outside this range for wide color gamut). Floating - * point output, either JXL_TYPE_FLOAT or JXL_TYPE_FLOAT16, is recommended + * point output, either ::JXL_TYPE_FLOAT or ::JXL_TYPE_FLOAT16, is recommended * for HDR and wide gamut images when color profile conversion is required. */ JXL_TYPE_FLOAT = 0, @@ -55,14 +57,6 @@ typedef enum { JXL_TYPE_FLOAT16 = 5, } JxlDataType; -/* DEPRECATED: bit-packed 1-bit data type. Use JXL_TYPE_UINT8 instead. - */ -static const int JXL_DEPRECATED JXL_TYPE_BOOLEAN = 1; - -/* DEPRECATED: uint32_t data type. Use JXL_TYPE_FLOAT instead. - */ -static const int JXL_DEPRECATED JXL_TYPE_UINT32 = 4; - /** Ordering of multi-byte data. */ typedef enum { @@ -81,7 +75,6 @@ typedef enum { * for pixels. This is not necessarily the same as the data type encoded in the * codestream. The channels are interleaved per pixel. The pixels are * organized row by row, left to right, top to bottom. - * TODO(lode): implement padding / alignment (row stride) * TODO(lode): support different channel orders if needed (RGB, BGR, ...) */ typedef struct { @@ -100,8 +93,7 @@ typedef struct { JxlDataType data_type; /** Whether multi-byte data types are represented in big endian or little - * endian format. This applies to JXL_TYPE_UINT16, JXL_TYPE_UINT32 - * and JXL_TYPE_FLOAT. + * endian format. This applies to ::JXL_TYPE_UINT16 and ::JXL_TYPE_FLOAT. */ JxlEndianness endianness; @@ -111,38 +103,49 @@ typedef struct { size_t align; } JxlPixelFormat; +/** Settings for the interpretation of UINT input and output buffers. + * (buffers using a FLOAT data type are not affected by this) + */ +typedef enum { + /** This is the default setting, where the encoder expects the input pixels + * to use the full range of the pixel format data type (e.g. for UINT16, the + * input range is 0 .. 65535 and the value 65535 is mapped to 1.0 when + * converting to float), and the decoder uses the full range to output + * pixels. If the bit depth in the basic info is different from this, the + * encoder expects the values to be rescaled accordingly (e.g. multiplied by + * 65535/4095 for a 12-bit image using UINT16 input data type). */ + JXL_BIT_DEPTH_FROM_PIXEL_FORMAT = 0, + + /** If this setting is selected, the encoder expects the input pixels to be + * in the range defined by the bits_per_sample value of the basic info (e.g. + * for 12-bit images using UINT16 input data types, the allowed range is + * 0 .. 4095 and the value 4095 is mapped to 1.0 when converting to float), + * and the decoder outputs pixels in this range. */ + JXL_BIT_DEPTH_FROM_CODESTREAM = 1, + + /** This setting can only be used in the decoder to select a custom range for + * pixel output */ + JXL_BIT_DEPTH_CUSTOM = 2, +} JxlBitDepthType; + +/** Data type for describing the interpretation of the input and output buffers + * in terms of the range of allowed input and output pixel values. */ +typedef struct { + /** Bit depth setting, see comment on @ref JxlBitDepthType */ + JxlBitDepthType type; + + /** Custom bits per sample */ + uint32_t bits_per_sample; + + /** Custom exponent bits per sample */ + uint32_t exponent_bits_per_sample; +} JxlBitDepth; + /** Data type holding the 4-character type name of an ISOBMFF box. */ typedef char JxlBoxType[4]; -/** Types of progressive detail. - * Setting a progressive detail with value N implies all progressive details - * with smaller or equal value. Currently only the following level of - * progressive detail is implemented: - * - kDC (which implies kFrames) - * - kLastPasses (which implies kDC and kFrames) - * - kPasses (which implies kLastPasses, kDC and kFrames) - */ -typedef enum { - // after completed kRegularFrames - kFrames = 0, - // after completed DC (1:8) - kDC = 1, - // after completed AC passes that are the last pass for their resolution - // target. - kLastPasses = 2, - // after completed AC passes that are not the last pass for their resolution - // target. - kPasses = 3, - // during DC frame when lower resolution are completed (1:32, 1:16) - kDCProgressive = 4, - // after completed groups - kDCGroups = 5, - // after completed groups - kGroups = 6, -} JxlProgressiveDetail; - -#if defined(__cplusplus) || defined(c_plusplus) +#ifdef __cplusplus } #endif diff --git a/media/libjxl/src/lib/jpegli.cmake b/media/libjxl/src/lib/jpegli.cmake new file mode 100644 index 0000000000..a471c8b2ab --- /dev/null +++ b/media/libjxl/src/lib/jpegli.cmake @@ -0,0 +1,158 @@ +# 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(jxl_lists.cmake) + +set(JPEGLI_INTERNAL_LIBS + hwy + Threads::Threads + ${ATOMICS_LIBRARIES} +) + +# JPEGLIB setup +set(BITS_IN_JSAMPLE 8) +set(MEM_SRCDST_SUPPORTED 1) + +if(JPEGLI_LIBJPEG_LIBRARY_SOVERSION STREQUAL "62") + set(JPEG_LIB_VERSION 62) +elseif(JPEGLI_LIBJPEG_LIBRARY_SOVERSION STREQUAL "7") + set(JPEG_LIB_VERSION 70) +elseif(JPEGLI_LIBJPEG_LIBRARY_SOVERSION STREQUAL "8") + set(JPEG_LIB_VERSION 80) +endif() + +configure_file( + ../third_party/libjpeg-turbo/jconfig.h.in include/jpegli/jconfig.h) +configure_file( + ../third_party/libjpeg-turbo/jpeglib.h include/jpegli/jpeglib.h COPYONLY) +configure_file( + ../third_party/libjpeg-turbo/jmorecfg.h include/jpegli/jmorecfg.h COPYONLY) + +add_library(jpegli-static STATIC EXCLUDE_FROM_ALL "${JPEGXL_INTERNAL_JPEGLI_SOURCES}") +target_compile_options(jpegli-static PRIVATE "${JPEGXL_INTERNAL_FLAGS}") +target_compile_options(jpegli-static PUBLIC ${JPEGXL_COVERAGE_FLAGS}) +set_property(TARGET jpegli-static PROPERTY POSITION_INDEPENDENT_CODE ON) +target_include_directories(jpegli-static PRIVATE + "$" + "$" + "$" + "${JXL_HWY_INCLUDE_DIRS}" +) +target_include_directories(jpegli-static PUBLIC + "$" +) +target_link_libraries(jpegli-static PUBLIC ${JPEGLI_INTERNAL_LIBS}) + +# +# Tests for jpegli-static +# + +find_package(JPEG) +if(JPEG_FOUND AND BUILD_TESTING) +# TODO(eustas): merge into jxl_tests.cmake? + +add_library(jpegli_libjpeg_util-obj OBJECT + ${JPEGXL_INTERNAL_JPEGLI_LIBJPEG_HELPER_FILES} +) +target_include_directories(jpegli_libjpeg_util-obj PRIVATE + "${PROJECT_SOURCE_DIR}" + "${JPEG_INCLUDE_DIRS}" +) +target_compile_options(jpegli_libjpeg_util-obj PRIVATE + "${JPEGXL_INTERNAL_FLAGS}" "${JPEGXL_COVERAGE_FLAGS}") + +# Individual test binaries: +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests) +foreach (TESTFILE IN LISTS JPEGXL_INTERNAL_JPEGLI_TESTS) + # The TESTNAME is the name without the extension or directory. + get_filename_component(TESTNAME ${TESTFILE} NAME_WE) + add_executable(${TESTNAME} ${TESTFILE} + $ + ${JPEGXL_INTERNAL_JPEGLI_TESTLIB_FILES} + ) + target_compile_options(${TESTNAME} PRIVATE + ${JPEGXL_INTERNAL_FLAGS} + # Add coverage flags to the test binary so code in the private headers of + # the library is also instrumented when running tests that execute it. + ${JPEGXL_COVERAGE_FLAGS} + ) + target_compile_definitions(${TESTNAME} PRIVATE + -DTEST_DATA_PATH="${JPEGXL_TEST_DATA_PATH}") + target_include_directories(${TESTNAME} PRIVATE + "${PROJECT_SOURCE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_BINARY_DIR}/include" + ) + target_link_libraries(${TESTNAME} + hwy + jpegli-static + GTest::GTest + GTest::Main + ${JPEG_LIBRARIES} + ) + set_target_properties(${TESTNAME} PROPERTIES LINK_FLAGS "${JPEGXL_COVERAGE_LINK_FLAGS}") + # Output test targets in the test directory. + set_target_properties(${TESTNAME} PROPERTIES PREFIX "tests/") + if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set_target_properties(${TESTNAME} PROPERTIES COMPILE_FLAGS "-Wno-error") + endif () + # 240 seconds because some build types (e.g. coverage) can be quite slow. + gtest_discover_tests(${TESTNAME} DISCOVERY_TIMEOUT 240) +endforeach () +endif() + +# +# Build libjpeg.so that links to libjpeg-static +# + +if (JPEGXL_ENABLE_JPEGLI_LIBJPEG AND NOT APPLE AND NOT WIN32 AND NOT EMSCRIPTEN) +add_library(jpegli-libjpeg-obj OBJECT "${JPEGXL_INTERNAL_JPEGLI_WRAPPER_SOURCES}") +target_compile_options(jpegli-libjpeg-obj PRIVATE ${JPEGXL_INTERNAL_FLAGS}) +target_compile_options(jpegli-libjpeg-obj PUBLIC ${JPEGXL_COVERAGE_FLAGS}) +set_property(TARGET jpegli-libjpeg-obj PROPERTY POSITION_INDEPENDENT_CODE ON) +target_include_directories(jpegli-libjpeg-obj PRIVATE + "$" + "$" +) +target_compile_definitions(jpegli-libjpeg-obj PUBLIC + ${JPEGLI_LIBJPEG_OBJ_COMPILE_DEFINITIONS} +) +set(JPEGLI_LIBJPEG_INTERNAL_OBJECTS $) + +file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/jpegli) +add_library(jpeg SHARED ${JPEGLI_LIBJPEG_INTERNAL_OBJECTS}) +target_link_libraries(jpeg PUBLIC ${JPEGXL_COVERAGE_FLAGS}) +target_link_libraries(jpeg PRIVATE jpegli-static) +set_target_properties(jpeg PROPERTIES + VERSION ${JPEGLI_LIBJPEG_LIBRARY_VERSION} + SOVERSION ${JPEGLI_LIBJPEG_LIBRARY_SOVERSION} + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/jpegli" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/jpegli") + +# Add a jpeg.version file as a version script to tag symbols with the +# appropriate version number. +set_target_properties(jpeg PROPERTIES + LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/jpegli/jpeg.version.${JPEGLI_LIBJPEG_LIBRARY_SOVERSION}) +set_property(TARGET jpeg APPEND_STRING PROPERTY + LINK_FLAGS " -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/jpegli/jpeg.version.${JPEGLI_LIBJPEG_LIBRARY_SOVERSION}") + +if (JPEGXL_INSTALL_JPEGLI_LIBJPEG) + install(TARGETS jpeg + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install( + DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include/jpegli/" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +endif() + +# This hides the default visibility symbols from static libraries bundled into +# the shared library. In particular this prevents exposing symbols from hwy +# in the shared library. +if(LINKER_SUPPORT_EXCLUDE_LIBS) + set_property(TARGET jpeg APPEND_STRING PROPERTY + LINK_FLAGS " ${LINKER_EXCLUDE_LIBS_FLAG}") +endif() +endif() diff --git a/media/libjxl/src/lib/jpegli/README.md b/media/libjxl/src/lib/jpegli/README.md new file mode 100644 index 0000000000..7241d1eaab --- /dev/null +++ b/media/libjxl/src/lib/jpegli/README.md @@ -0,0 +1,52 @@ +:warning: **Important Update:** Development continues at https://github.com/google/jpegli + + +# Improved JPEG encoder and decoder implementation + +This subdirectory contains a JPEG encoder and decoder implementation that is +API and ABI compatible with libjpeg62. + +## Building + +When building the parent libjxl project, two binaries, `tools/cjpegli` and +`tools/djpegli` will be built, as well as a +`lib/jpegli/libjpeg.so.62.3.0` shared library that can be used as a drop-in +replacement for the system library with the same name. + +## Encoder improvements + +Improvements and new features used by the encoder include: + +* Support for 16-bit unsigned and 32-bit floating point input buffers. + +* Color space conversions, chroma subsampling and DCT are all done in floating + point precision, the conversion to integers happens first when producing + the final quantized DCT coefficients. + +* The desired quality can be indicated by a distance parameter that is + analogous to the distance parameter of JPEG XL. The quantization tables + are chosen based on the distance and the chroma subsampling mode, with + different positions in the quantization matrix scaling differently, and the + red and blue chrominance channels have separate quantization tables. + +* Adaptive dead-zone quantization. On noisy parts of the image, quantization + thresholds for zero coefficients are higher than on smoother parts of the + image. + +* Support for more efficient compression of JPEGs with an ICC profile + representing the XYB colorspace. These JPEGs will not be converted to the + YCbCr colorspace, but specialized quantization tables will be chosen for + the original X, Y, B channels. + +## Decoder improvements + +* Support for 16-bit unsigned and 32-bit floating point output buffers. + +* Non-zero DCT coefficients are dequantized to the expectation value of their + respective quantization intervals assuming a Laplacian distribution of the + original unquantized DCT coefficients. + +* After dequantization, inverse DCT, chroma upsampling and color space + conversions are all done in floating point precision, the conversion to + integer samples happens only in the final output phase (unless output to + floating point was requested). diff --git a/media/libjxl/src/lib/jpegli/adaptive_quantization.cc b/media/libjxl/src/lib/jpegli/adaptive_quantization.cc new file mode 100644 index 0000000000..11367ee660 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/adaptive_quantization.cc @@ -0,0 +1,563 @@ +// 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/jpegli/adaptive_quantization.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jpegli/adaptive_quantization.cc" +#include +#include + +#include "lib/jpegli/encode_internal.h" +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/status.h" +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { +namespace { + +// These templates are not found via ADL. +using hwy::HWY_NAMESPACE::AbsDiff; +using hwy::HWY_NAMESPACE::Add; +using hwy::HWY_NAMESPACE::And; +using hwy::HWY_NAMESPACE::Div; +using hwy::HWY_NAMESPACE::Floor; +using hwy::HWY_NAMESPACE::GetLane; +using hwy::HWY_NAMESPACE::Max; +using hwy::HWY_NAMESPACE::Min; +using hwy::HWY_NAMESPACE::Mul; +using hwy::HWY_NAMESPACE::MulAdd; +using hwy::HWY_NAMESPACE::NegMulAdd; +using hwy::HWY_NAMESPACE::Rebind; +using hwy::HWY_NAMESPACE::ShiftLeft; +using hwy::HWY_NAMESPACE::ShiftRight; +using hwy::HWY_NAMESPACE::Sqrt; +using hwy::HWY_NAMESPACE::Sub; +using hwy::HWY_NAMESPACE::ZeroIfNegative; + +constexpr float kInputScaling = 1.0f / 255.0f; + +// Primary template: default to actual division. +template +struct FastDivision { + HWY_INLINE V operator()(const V n, const V d) const { return n / d; } +}; +// Partial specialization for float vectors. +template +struct FastDivision { + // One Newton-Raphson iteration. + static HWY_INLINE V ReciprocalNR(const V x) { + const auto rcp = ApproximateReciprocal(x); + const auto sum = Add(rcp, rcp); + const auto x_rcp = Mul(x, rcp); + return NegMulAdd(x_rcp, rcp, sum); + } + + V operator()(const V n, const V d) const { +#if JXL_TRUE // Faster on SKX + return Div(n, d); +#else + return n * ReciprocalNR(d); +#endif + } +}; + +// Approximates smooth functions via rational polynomials (i.e. dividing two +// polynomials). Evaluates polynomials via Horner's scheme, which is faster than +// Clenshaw recurrence for Chebyshev polynomials. LoadDup128 allows us to +// specify constants (replicated 4x) independently of the lane count. +template +HWY_INLINE HWY_MAYBE_UNUSED V EvalRationalPolynomial(const D d, const V x, + const T (&p)[NP], + const T (&q)[NQ]) { + constexpr size_t kDegP = NP / 4 - 1; + constexpr size_t kDegQ = NQ / 4 - 1; + auto yp = LoadDup128(d, &p[kDegP * 4]); + auto yq = LoadDup128(d, &q[kDegQ * 4]); + // We use pointer arithmetic to refer to &p[(kDegP - n) * 4] to avoid a + // compiler warning that the index is out of bounds since we are already + // checking that it is not out of bounds with (kDegP >= n) and the access + // will be optimized away. Similarly with q and kDegQ. + HWY_FENCE; + if (kDegP >= 1) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 1) * 4))); + if (kDegQ >= 1) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 1) * 4))); + HWY_FENCE; + if (kDegP >= 2) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 2) * 4))); + if (kDegQ >= 2) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 2) * 4))); + HWY_FENCE; + if (kDegP >= 3) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 3) * 4))); + if (kDegQ >= 3) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 3) * 4))); + HWY_FENCE; + if (kDegP >= 4) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 4) * 4))); + if (kDegQ >= 4) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 4) * 4))); + HWY_FENCE; + if (kDegP >= 5) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 5) * 4))); + if (kDegQ >= 5) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 5) * 4))); + HWY_FENCE; + if (kDegP >= 6) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 6) * 4))); + if (kDegQ >= 6) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 6) * 4))); + HWY_FENCE; + if (kDegP >= 7) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 7) * 4))); + if (kDegQ >= 7) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 7) * 4))); + + return FastDivision()(yp, yq); +} + +// Computes base-2 logarithm like std::log2. Undefined if negative / NaN. +// L1 error ~3.9E-6 +template +V FastLog2f(const DF df, V x) { + // 2,2 rational polynomial approximation of std::log1p(x) / std::log(2). + HWY_ALIGN const float p[4 * (2 + 1)] = {HWY_REP4(-1.8503833400518310E-06f), + HWY_REP4(1.4287160470083755E+00f), + HWY_REP4(7.4245873327820566E-01f)}; + HWY_ALIGN const float q[4 * (2 + 1)] = {HWY_REP4(9.9032814277590719E-01f), + HWY_REP4(1.0096718572241148E+00f), + HWY_REP4(1.7409343003366853E-01f)}; + + const Rebind di; + const auto x_bits = BitCast(di, x); + + // Range reduction to [-1/3, 1/3] - 3 integer, 2 float ops + const auto exp_bits = Sub(x_bits, Set(di, 0x3f2aaaab)); // = 2/3 + // Shifted exponent = log2; also used to clear mantissa. + const auto exp_shifted = ShiftRight<23>(exp_bits); + const auto mantissa = BitCast(df, Sub(x_bits, ShiftLeft<23>(exp_shifted))); + const auto exp_val = ConvertTo(df, exp_shifted); + return Add(EvalRationalPolynomial(df, Sub(mantissa, Set(df, 1.0f)), p, q), + exp_val); +} + +// max relative error ~3e-7 +template +V FastPow2f(const DF df, V x) { + const Rebind di; + auto floorx = Floor(x); + auto exp = + BitCast(df, ShiftLeft<23>(Add(ConvertTo(di, floorx), Set(di, 127)))); + auto frac = Sub(x, floorx); + auto num = Add(frac, Set(df, 1.01749063e+01)); + num = MulAdd(num, frac, Set(df, 4.88687798e+01)); + num = MulAdd(num, frac, Set(df, 9.85506591e+01)); + num = Mul(num, exp); + auto den = MulAdd(frac, Set(df, 2.10242958e-01), Set(df, -2.22328856e-02)); + den = MulAdd(den, frac, Set(df, -1.94414990e+01)); + den = MulAdd(den, frac, Set(df, 9.85506633e+01)); + return Div(num, den); +} + +inline float FastPow2f(float f) { + HWY_CAPPED(float, 1) D; + return GetLane(FastPow2f(D, Set(D, f))); +} + +// The following functions modulate an exponent (out_val) and return the updated +// value. Their descriptor is limited to 8 lanes for 8x8 blocks. + +template +V ComputeMask(const D d, const V out_val) { + const auto kBase = Set(d, -0.74174993f); + const auto kMul4 = Set(d, 3.2353257320940401f); + const auto kMul2 = Set(d, 12.906028311180409f); + const auto kOffset2 = Set(d, 305.04035728311436f); + const auto kMul3 = Set(d, 5.0220313103171232f); + const auto kOffset3 = Set(d, 2.1925739705298404f); + const auto kOffset4 = Mul(Set(d, 0.25f), kOffset3); + const auto kMul0 = Set(d, 0.74760422233706747f); + const auto k1 = Set(d, 1.0f); + + // Avoid division by zero. + const auto v1 = Max(Mul(out_val, kMul0), Set(d, 1e-3f)); + const auto v2 = Div(k1, Add(v1, kOffset2)); + const auto v3 = Div(k1, MulAdd(v1, v1, kOffset3)); + const auto v4 = Div(k1, MulAdd(v1, v1, kOffset4)); + // TODO(jyrki): + // A log or two here could make sense. In butteraugli we have effectively + // log(log(x + C)) for this kind of use, as a single log is used in + // saturating visual masking and here the modulation values are exponential, + // another log would counter that. + return Add(kBase, MulAdd(kMul4, v4, MulAdd(kMul2, v2, Mul(kMul3, v3)))); +} + +// mul and mul2 represent a scaling difference between jxl and butteraugli. +const float kSGmul = 226.0480446705883f; +const float kSGmul2 = 1.0f / 73.377132366608819f; +const float kLog2 = 0.693147181f; +// Includes correction factor for std::log -> log2. +const float kSGRetMul = kSGmul2 * 18.6580932135f * kLog2; +const float kSGVOffset = 7.14672470003f; + +template +V RatioOfDerivativesOfCubicRootToSimpleGamma(const D d, V v) { + // The opsin space in jxl is the cubic root of photons, i.e., v * v * v + // is related to the number of photons. + // + // SimpleGamma(v * v * v) is the psychovisual space in butteraugli. + // This ratio allows quantization to move from jxl's opsin space to + // butteraugli's log-gamma space. + static const float kEpsilon = 1e-2; + static const float kNumOffset = kEpsilon / kInputScaling / kInputScaling; + static const float kNumMul = kSGRetMul * 3 * kSGmul; + static const float kVOffset = (kSGVOffset * kLog2 + kEpsilon) / kInputScaling; + static const float kDenMul = kLog2 * kSGmul * kInputScaling * kInputScaling; + + v = ZeroIfNegative(v); + const auto num_mul = Set(d, kNumMul); + const auto num_offset = Set(d, kNumOffset); + const auto den_offset = Set(d, kVOffset); + const auto den_mul = Set(d, kDenMul); + + const auto v2 = Mul(v, v); + + const auto num = MulAdd(num_mul, v2, num_offset); + const auto den = MulAdd(Mul(den_mul, v), v2, den_offset); + return invert ? Div(num, den) : Div(den, num); +} + +template +float RatioOfDerivativesOfCubicRootToSimpleGamma(float v) { + using DScalar = HWY_CAPPED(float, 1); + auto vscalar = Load(DScalar(), &v); + return GetLane( + RatioOfDerivativesOfCubicRootToSimpleGamma(DScalar(), vscalar)); +} + +// TODO(veluca): this function computes an approximation of the derivative of +// SimpleGamma with (f(x+eps)-f(x))/eps. Consider two-sided approximation or +// exact derivatives. For reference, SimpleGamma was: +/* +template +V SimpleGamma(const D d, V v) { + // A simple HDR compatible gamma function. + const auto mul = Set(d, kSGmul); + const auto kRetMul = Set(d, kSGRetMul); + const auto kRetAdd = Set(d, kSGmul2 * -20.2789020414f); + const auto kVOffset = Set(d, kSGVOffset); + + v *= mul; + + // This should happen rarely, but may lead to a NaN, which is rather + // undesirable. Since negative photons don't exist we solve the NaNs by + // clamping here. + // TODO(veluca): with FastLog2f, this no longer leads to NaNs. + v = ZeroIfNegative(v); + return kRetMul * FastLog2f(d, v + kVOffset) + kRetAdd; +} +*/ + +template +V GammaModulation(const D d, const size_t x, const size_t y, + const RowBuffer& input, const V out_val) { + static const float kBias = 0.16f / kInputScaling; + static const float kScale = kInputScaling / 64.0f; + auto overall_ratio = Zero(d); + const auto bias = Set(d, kBias); + const auto scale = Set(d, kScale); + const float* const JXL_RESTRICT block_start = input.Row(y) + x; + for (size_t dy = 0; dy < 8; ++dy) { + const float* const JXL_RESTRICT row_in = block_start + dy * input.stride(); + for (size_t dx = 0; dx < 8; dx += Lanes(d)) { + const auto iny = Add(Load(d, row_in + dx), bias); + const auto ratio_g = + RatioOfDerivativesOfCubicRootToSimpleGamma(d, iny); + overall_ratio = Add(overall_ratio, ratio_g); + } + } + overall_ratio = Mul(SumOfLanes(d, overall_ratio), scale); + // ideally -1.0, but likely optimal correction adds some entropy, so slightly + // less than that. + // ln(2) constant folded in because we want std::log but have FastLog2f. + const auto kGamma = Set(d, -0.15526878023684174f * 0.693147180559945f); + return MulAdd(kGamma, FastLog2f(d, overall_ratio), out_val); +} + +// Change precision in 8x8 blocks that have high frequency content. +template +V HfModulation(const D d, const size_t x, const size_t y, + const RowBuffer& input, const V out_val) { + // Zero out the invalid differences for the rightmost value per row. + const Rebind du; + HWY_ALIGN constexpr uint32_t kMaskRight[8] = {~0u, ~0u, ~0u, ~0u, + ~0u, ~0u, ~0u, 0}; + + auto sum = Zero(d); // sum of absolute differences with right and below + static const float kSumCoeff = -2.0052193233688884f * kInputScaling / 112.0; + auto sumcoeff = Set(d, kSumCoeff); + + const float* const JXL_RESTRICT block_start = input.Row(y) + x; + for (size_t dy = 0; dy < 8; ++dy) { + const float* JXL_RESTRICT row_in = block_start + dy * input.stride(); + const float* JXL_RESTRICT row_in_next = + dy == 7 ? row_in : row_in + input.stride(); + + for (size_t dx = 0; dx < 8; dx += Lanes(d)) { + const auto p = Load(d, row_in + dx); + const auto pr = LoadU(d, row_in + dx + 1); + const auto mask = BitCast(d, Load(du, kMaskRight + dx)); + sum = Add(sum, And(mask, AbsDiff(p, pr))); + const auto pd = Load(d, row_in_next + dx); + sum = Add(sum, AbsDiff(p, pd)); + } + } + + sum = SumOfLanes(d, sum); + return MulAdd(sum, sumcoeff, out_val); +} + +void PerBlockModulations(const float y_quant_01, const RowBuffer& input, + const size_t yb0, const size_t yblen, + RowBuffer* aq_map) { + static const float kAcQuant = 0.841f; + float base_level = 0.48f * kAcQuant; + float kDampenRampStart = 9.0f; + float kDampenRampEnd = 65.0f; + float dampen = 1.0f; + if (y_quant_01 >= kDampenRampStart) { + dampen = 1.0f - ((y_quant_01 - kDampenRampStart) / + (kDampenRampEnd - kDampenRampStart)); + if (dampen < 0) { + dampen = 0; + } + } + const float mul = kAcQuant * dampen; + const float add = (1.0f - dampen) * base_level; + for (size_t iy = 0; iy < yblen; iy++) { + const size_t yb = yb0 + iy; + const size_t y = yb * 8; + float* const JXL_RESTRICT row_out = aq_map->Row(yb); + const HWY_CAPPED(float, 8) df; + for (size_t ix = 0; ix < aq_map->xsize(); ix++) { + size_t x = ix * 8; + auto out_val = Set(df, row_out[ix]); + out_val = ComputeMask(df, out_val); + out_val = HfModulation(df, x, y, input, out_val); + out_val = GammaModulation(df, x, y, input, out_val); + // We want multiplicative quantization field, so everything + // until this point has been modulating the exponent. + row_out[ix] = FastPow2f(GetLane(out_val) * 1.442695041f) * mul + add; + } + } +} + +template +V MaskingSqrt(const D d, V v) { + static const float kLogOffset = 28; + static const float kMul = 211.50759899638012f; + const auto mul_v = Set(d, kMul * 1e8); + const auto offset_v = Set(d, kLogOffset); + return Mul(Set(d, 0.25f), Sqrt(MulAdd(v, Sqrt(mul_v), offset_v))); +} + +template +void Sort4(V& min0, V& min1, V& min2, V& min3) { + const auto tmp0 = Min(min0, min1); + const auto tmp1 = Max(min0, min1); + const auto tmp2 = Min(min2, min3); + const auto tmp3 = Max(min2, min3); + const auto tmp4 = Max(tmp0, tmp2); + const auto tmp5 = Min(tmp1, tmp3); + min0 = Min(tmp0, tmp2); + min1 = Min(tmp4, tmp5); + min2 = Max(tmp4, tmp5); + min3 = Max(tmp1, tmp3); +} + +template +void UpdateMin4(const V v, V& min0, V& min1, V& min2, V& min3) { + const auto tmp0 = Max(min0, v); + const auto tmp1 = Max(min1, tmp0); + const auto tmp2 = Max(min2, tmp1); + min0 = Min(min0, v); + min1 = Min(min1, tmp0); + min2 = Min(min2, tmp1); + min3 = Min(min3, tmp2); +} + +// Computes a linear combination of the 4 lowest values of the 3x3 neighborhood +// of each pixel. Output is downsampled 2x. +void FuzzyErosion(const RowBuffer& pre_erosion, const size_t yb0, + const size_t yblen, RowBuffer* tmp, + RowBuffer* aq_map) { + int xsize_blocks = aq_map->xsize(); + int xsize = pre_erosion.xsize(); + HWY_FULL(float) d; + const auto mul0 = Set(d, 0.125f); + const auto mul1 = Set(d, 0.075f); + const auto mul2 = Set(d, 0.06f); + const auto mul3 = Set(d, 0.05f); + for (size_t iy = 0; iy < 2 * yblen; ++iy) { + size_t y = 2 * yb0 + iy; + const float* JXL_RESTRICT rowt = pre_erosion.Row(y - 1); + const float* JXL_RESTRICT rowm = pre_erosion.Row(y); + const float* JXL_RESTRICT rowb = pre_erosion.Row(y + 1); + float* row_out = tmp->Row(y); + for (int x = 0; x < xsize; x += Lanes(d)) { + int xm1 = x - 1; + int xp1 = x + 1; + auto min0 = LoadU(d, rowm + x); + auto min1 = LoadU(d, rowm + xm1); + auto min2 = LoadU(d, rowm + xp1); + auto min3 = LoadU(d, rowt + xm1); + Sort4(min0, min1, min2, min3); + UpdateMin4(LoadU(d, rowt + x), min0, min1, min2, min3); + UpdateMin4(LoadU(d, rowt + xp1), min0, min1, min2, min3); + UpdateMin4(LoadU(d, rowb + xm1), min0, min1, min2, min3); + UpdateMin4(LoadU(d, rowb + x), min0, min1, min2, min3); + UpdateMin4(LoadU(d, rowb + xp1), min0, min1, min2, min3); + const auto v = Add(Add(Mul(mul0, min0), Mul(mul1, min1)), + Add(Mul(mul2, min2), Mul(mul3, min3))); + Store(v, d, row_out + x); + } + if (iy % 2 == 1) { + const float* JXL_RESTRICT row_out0 = tmp->Row(y - 1); + float* JXL_RESTRICT aq_out = aq_map->Row(yb0 + iy / 2); + for (int bx = 0, x = 0; bx < xsize_blocks; ++bx, x += 2) { + aq_out[bx] = + (row_out[x] + row_out[x + 1] + row_out0[x] + row_out0[x + 1]); + } + } + } +} + +void ComputePreErosion(const RowBuffer& input, const size_t xsize, + const size_t y0, const size_t ylen, int border, + float* diff_buffer, RowBuffer* pre_erosion) { + const size_t xsize_out = xsize / 4; + const size_t y0_out = y0 / 4; + + // The XYB gamma is 3.0 to be able to decode faster with two muls. + // Butteraugli's gamma is matching the gamma of human eye, around 2.6. + // We approximate the gamma difference by adding one cubic root into + // the adaptive quantization. This gives us a total gamma of 2.6666 + // for quantization uses. + static const float match_gamma_offset = 0.019 / kInputScaling; + + const HWY_CAPPED(float, 8) df; + + static const float limit = 0.2f; + // Computes image (padded to multiple of 8x8) of local pixel differences. + // Subsample both directions by 4. + for (size_t iy = 0; iy < ylen; ++iy) { + size_t y = y0 + iy; + const float* row_in = input.Row(y); + const float* row_in1 = input.Row(y + 1); + const float* row_in2 = input.Row(y - 1); + float* JXL_RESTRICT row_out = diff_buffer; + const auto match_gamma_offset_v = Set(df, match_gamma_offset); + const auto quarter = Set(df, 0.25f); + for (size_t x = 0; x < xsize; x += Lanes(df)) { + const auto in = LoadU(df, row_in + x); + const auto in_r = LoadU(df, row_in + x + 1); + const auto in_l = LoadU(df, row_in + x - 1); + const auto in_t = LoadU(df, row_in2 + x); + const auto in_b = LoadU(df, row_in1 + x); + const auto base = Mul(quarter, Add(Add(in_r, in_l), Add(in_t, in_b))); + const auto gammacv = + RatioOfDerivativesOfCubicRootToSimpleGamma( + df, Add(in, match_gamma_offset_v)); + auto diff = Mul(gammacv, Sub(in, base)); + diff = Mul(diff, diff); + diff = Min(diff, Set(df, limit)); + diff = MaskingSqrt(df, diff); + if ((iy & 3) != 0) { + diff = Add(diff, LoadU(df, row_out + x)); + } + StoreU(diff, df, row_out + x); + } + if (iy % 4 == 3) { + size_t y_out = y0_out + iy / 4; + float* row_d_out = pre_erosion->Row(y_out); + for (size_t x = 0; x < xsize_out; x++) { + row_d_out[x] = (row_out[x * 4] + row_out[x * 4 + 1] + + row_out[x * 4 + 2] + row_out[x * 4 + 3]) * + 0.25f; + } + pre_erosion->PadRow(y_out, xsize_out, border); + } + } +} + +} // namespace + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace jpegli { +HWY_EXPORT(ComputePreErosion); +HWY_EXPORT(FuzzyErosion); +HWY_EXPORT(PerBlockModulations); + +namespace { + +constexpr int kPreErosionBorder = 1; + +} // namespace + +void ComputeAdaptiveQuantField(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + if (!m->use_adaptive_quantization) { + return; + } + int y_channel = cinfo->jpeg_color_space == JCS_RGB ? 1 : 0; + jpeg_component_info* y_comp = &cinfo->comp_info[y_channel]; + int y_quant_01 = cinfo->quant_tbl_ptrs[y_comp->quant_tbl_no]->quantval[1]; + if (m->next_iMCU_row == 0) { + m->input_buffer[y_channel].CopyRow(-1, 0, 1); + } + if (m->next_iMCU_row + 1 == cinfo->total_iMCU_rows) { + size_t last_row = m->ysize_blocks * DCTSIZE - 1; + m->input_buffer[y_channel].CopyRow(last_row + 1, last_row, 1); + } + const RowBuffer& input = m->input_buffer[y_channel]; + const size_t xsize_blocks = y_comp->width_in_blocks; + const size_t xsize = xsize_blocks * DCTSIZE; + const size_t yb0 = m->next_iMCU_row * cinfo->max_v_samp_factor; + const size_t yblen = cinfo->max_v_samp_factor; + size_t y0 = yb0 * DCTSIZE; + size_t ylen = cinfo->max_v_samp_factor * DCTSIZE; + if (y0 == 0) { + ylen += 4; + } else { + y0 += 4; + } + if (m->next_iMCU_row + 1 == cinfo->total_iMCU_rows) { + ylen -= 4; + } + HWY_DYNAMIC_DISPATCH(ComputePreErosion) + (input, xsize, y0, ylen, kPreErosionBorder, m->diff_buffer, &m->pre_erosion); + if (y0 == 0) { + m->pre_erosion.CopyRow(-1, 0, kPreErosionBorder); + } + if (m->next_iMCU_row + 1 == cinfo->total_iMCU_rows) { + size_t last_row = m->ysize_blocks * 2 - 1; + m->pre_erosion.CopyRow(last_row + 1, last_row, kPreErosionBorder); + } + HWY_DYNAMIC_DISPATCH(FuzzyErosion) + (m->pre_erosion, yb0, yblen, &m->fuzzy_erosion_tmp, &m->quant_field); + HWY_DYNAMIC_DISPATCH(PerBlockModulations) + (y_quant_01, input, yb0, yblen, &m->quant_field); + for (int y = 0; y < cinfo->max_v_samp_factor; ++y) { + float* row = m->quant_field.Row(yb0 + y); + for (size_t x = 0; x < xsize_blocks; ++x) { + row[x] = std::max(0.0f, (0.6f / row[x]) - 1.0f); + } + } +} + +} // namespace jpegli +#endif // HWY_ONCE diff --git a/media/libjxl/src/lib/jpegli/adaptive_quantization.h b/media/libjxl/src/lib/jpegli/adaptive_quantization.h new file mode 100644 index 0000000000..d8537e85df --- /dev/null +++ b/media/libjxl/src/lib/jpegli/adaptive_quantization.h @@ -0,0 +1,17 @@ +// 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_JPEGLI_ADAPTIVE_QUANTIZATION_H_ +#define LIB_JPEGLI_ADAPTIVE_QUANTIZATION_H_ + +#include "lib/jpegli/common.h" + +namespace jpegli { + +void ComputeAdaptiveQuantField(j_compress_ptr cinfo); + +} // namespace jpegli + +#endif // LIB_JPEGLI_ADAPTIVE_QUANTIZATION_H_ diff --git a/media/libjxl/src/lib/jpegli/bit_writer.cc b/media/libjxl/src/lib/jpegli/bit_writer.cc new file mode 100644 index 0000000000..9788f35b8d --- /dev/null +++ b/media/libjxl/src/lib/jpegli/bit_writer.cc @@ -0,0 +1,60 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "lib/jpegli/bit_writer.h" + +#include "lib/jpegli/encode_internal.h" + +namespace jpegli { + +void JpegBitWriterInit(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + JpegBitWriter* bw = &m->bw; + size_t buffer_size = m->blocks_per_iMCU_row * (DCTSIZE2 * 16 + 8) + (1 << 16); + bw->cinfo = cinfo; + bw->data = Allocate(cinfo, buffer_size, JPOOL_IMAGE); + bw->len = buffer_size; + bw->pos = 0; + bw->output_pos = 0; + bw->put_buffer = 0; + bw->free_bits = 64; + bw->healthy = true; +} + +bool EmptyBitWriterBuffer(JpegBitWriter* bw) { + while (bw->output_pos < bw->pos) { + j_compress_ptr cinfo = bw->cinfo; + if (cinfo->dest->free_in_buffer == 0 && + !(*cinfo->dest->empty_output_buffer)(cinfo)) { + return false; + } + size_t buflen = bw->pos - bw->output_pos; + size_t copylen = std::min(cinfo->dest->free_in_buffer, buflen); + memcpy(cinfo->dest->next_output_byte, bw->data + bw->output_pos, copylen); + bw->output_pos += copylen; + cinfo->dest->free_in_buffer -= copylen; + cinfo->dest->next_output_byte += copylen; + } + bw->output_pos = bw->pos = 0; + return true; +} + +void JumpToByteBoundary(JpegBitWriter* bw) { + size_t n_bits = bw->free_bits & 7u; + if (n_bits > 0) { + WriteBits(bw, n_bits, (1u << n_bits) - 1); + } + bw->put_buffer <<= bw->free_bits; + while (bw->free_bits <= 56) { + int c = (bw->put_buffer >> 56) & 0xFF; + EmitByte(bw, c); + bw->put_buffer <<= 8; + bw->free_bits += 8; + } + bw->put_buffer = 0; + bw->free_bits = 64; +} + +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/bit_writer.h b/media/libjxl/src/lib/jpegli/bit_writer.h new file mode 100644 index 0000000000..3adf1eaca1 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/bit_writer.h @@ -0,0 +1,98 @@ +// 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_JPEGLI_BIT_WRITER_H_ +#define LIB_JPEGLI_BIT_WRITER_H_ + +#include +#include + +#include "lib/jpegli/common.h" +#include "lib/jxl/base/byte_order.h" +#include "lib/jxl/base/compiler_specific.h" + +namespace jpegli { + +// Handles the packing of bits into output bytes. +struct JpegBitWriter { + j_compress_ptr cinfo; + uint8_t* data; + size_t len; + size_t pos; + size_t output_pos; + uint64_t put_buffer; + int free_bits; + bool healthy; +}; + +void JpegBitWriterInit(j_compress_ptr cinfo); + +bool EmptyBitWriterBuffer(JpegBitWriter* bw); + +void JumpToByteBoundary(JpegBitWriter* bw); + +// Returns non-zero if and only if x has a zero byte, i.e. one of +// x & 0xff, x & 0xff00, ..., x & 0xff00000000000000 is zero. +static JXL_INLINE uint64_t HasZeroByte(uint64_t x) { + return (x - 0x0101010101010101ULL) & ~x & 0x8080808080808080ULL; +} + +/** + * Writes the given byte to the output, writes an extra zero if byte is 0xFF. + * + * This method is "careless" - caller must make sure that there is enough + * space in the output buffer. Emits up to 2 bytes to buffer. + */ +static JXL_INLINE void EmitByte(JpegBitWriter* bw, int byte) { + bw->data[bw->pos++] = byte; + if (byte == 0xFF) bw->data[bw->pos++] = 0; +} + +static JXL_INLINE void DischargeBitBuffer(JpegBitWriter* bw) { + // At this point we are ready to emit the bytes of put_buffer to the output. + // The JPEG format requires that after every 0xff byte in the entropy + // coded section, there is a zero byte, therefore we first check if any of + // the bytes of put_buffer is 0xFF. + if (HasZeroByte(~bw->put_buffer)) { + // We have a 0xFF byte somewhere, examine each byte and append a zero + // byte if necessary. + EmitByte(bw, (bw->put_buffer >> 56) & 0xFF); + EmitByte(bw, (bw->put_buffer >> 48) & 0xFF); + EmitByte(bw, (bw->put_buffer >> 40) & 0xFF); + EmitByte(bw, (bw->put_buffer >> 32) & 0xFF); + EmitByte(bw, (bw->put_buffer >> 24) & 0xFF); + EmitByte(bw, (bw->put_buffer >> 16) & 0xFF); + EmitByte(bw, (bw->put_buffer >> 8) & 0xFF); + EmitByte(bw, (bw->put_buffer >> 0) & 0xFF); + } else { + // We don't have any 0xFF bytes, output all 8 bytes without checking. + StoreBE64(bw->put_buffer, bw->data + bw->pos); + bw->pos += 8; + } +} + +static JXL_INLINE void WriteBits(JpegBitWriter* bw, int nbits, uint64_t bits) { + // This is an optimization; if everything goes well, + // then |nbits| is positive; if non-existing Huffman symbol is going to be + // encoded, its length should be zero; later encoder could check the + // "health" of JpegBitWriter. + if (nbits == 0) { + bw->healthy = false; + return; + } + bw->free_bits -= nbits; + if (bw->free_bits < 0) { + bw->put_buffer <<= (bw->free_bits + nbits); + bw->put_buffer |= (bits >> -bw->free_bits); + DischargeBitBuffer(bw); + bw->free_bits += 64; + bw->put_buffer = nbits; + } + bw->put_buffer <<= nbits; + bw->put_buffer |= bits; +} + +} // namespace jpegli +#endif // LIB_JPEGLI_BIT_WRITER_H_ diff --git a/media/libjxl/src/lib/jpegli/bitstream.cc b/media/libjxl/src/lib/jpegli/bitstream.cc new file mode 100644 index 0000000000..4dbeb738bb --- /dev/null +++ b/media/libjxl/src/lib/jpegli/bitstream.cc @@ -0,0 +1,451 @@ +// 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/jpegli/bitstream.h" + +#include + +#include "lib/jpegli/bit_writer.h" +#include "lib/jpegli/error.h" +#include "lib/jpegli/memory_manager.h" + +namespace jpegli { + +void WriteOutput(j_compress_ptr cinfo, const uint8_t* buf, size_t bufsize) { + size_t pos = 0; + while (pos < bufsize) { + if (cinfo->dest->free_in_buffer == 0 && + !(*cinfo->dest->empty_output_buffer)(cinfo)) { + JPEGLI_ERROR("Destination suspension is not supported in markers."); + } + size_t len = std::min(cinfo->dest->free_in_buffer, bufsize - pos); + memcpy(cinfo->dest->next_output_byte, buf + pos, len); + pos += len; + cinfo->dest->free_in_buffer -= len; + cinfo->dest->next_output_byte += len; + } +} + +void WriteOutput(j_compress_ptr cinfo, const std::vector& bytes) { + WriteOutput(cinfo, bytes.data(), bytes.size()); +} + +void WriteOutput(j_compress_ptr cinfo, std::initializer_list bytes) { + WriteOutput(cinfo, bytes.begin(), bytes.size()); +} + +void EncodeAPP0(j_compress_ptr cinfo) { + WriteOutput(cinfo, + {0xff, 0xe0, 0, 16, 'J', 'F', 'I', 'F', '\0', + cinfo->JFIF_major_version, cinfo->JFIF_minor_version, + cinfo->density_unit, static_cast(cinfo->X_density >> 8), + static_cast(cinfo->X_density & 0xff), + static_cast(cinfo->Y_density >> 8), + static_cast(cinfo->Y_density & 0xff), 0, 0}); +} + +void EncodeAPP14(j_compress_ptr cinfo) { + uint8_t color_transform = cinfo->jpeg_color_space == JCS_YCbCr ? 1 + : cinfo->jpeg_color_space == JCS_YCCK ? 2 + : 0; + WriteOutput(cinfo, {0xff, 0xee, 0, 14, 'A', 'd', 'o', 'b', 'e', 0, 100, 0, 0, + 0, 0, color_transform}); +} + +void WriteFileHeader(j_compress_ptr cinfo) { + WriteOutput(cinfo, {0xFF, 0xD8}); // SOI + if (cinfo->write_JFIF_header) { + EncodeAPP0(cinfo); + } + if (cinfo->write_Adobe_marker) { + EncodeAPP14(cinfo); + } +} + +bool EncodeDQT(j_compress_ptr cinfo, bool write_all_tables) { + uint8_t data[4 + NUM_QUANT_TBLS * (1 + 2 * DCTSIZE2)]; // 520 bytes + size_t pos = 0; + data[pos++] = 0xFF; + data[pos++] = 0xDB; + pos += 2; // Length will be filled in later. + + int send_table[NUM_QUANT_TBLS] = {}; + if (write_all_tables) { + for (int i = 0; i < NUM_QUANT_TBLS; ++i) { + if (cinfo->quant_tbl_ptrs[i]) send_table[i] = 1; + } + } else { + for (int c = 0; c < cinfo->num_components; ++c) { + send_table[cinfo->comp_info[c].quant_tbl_no] = 1; + } + } + + bool is_baseline = true; + for (int i = 0; i < NUM_QUANT_TBLS; ++i) { + if (!send_table[i]) continue; + JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[i]; + if (quant_table == nullptr) { + JPEGLI_ERROR("Missing quant table %d", i); + } + int precision = 0; + for (UINT16 q : quant_table->quantval) { + if (q > 255) { + precision = 1; + is_baseline = false; + } + } + if (quant_table->sent_table) { + continue; + } + data[pos++] = (precision << 4) + i; + for (size_t j = 0; j < DCTSIZE2; ++j) { + int val_idx = kJPEGNaturalOrder[j]; + int val = quant_table->quantval[val_idx]; + if (val == 0) { + JPEGLI_ERROR("Invalid quantval 0."); + } + if (precision) { + data[pos++] = val >> 8; + } + data[pos++] = val & 0xFFu; + } + quant_table->sent_table = TRUE; + } + if (pos > 4) { + data[2] = (pos - 2) >> 8u; + data[3] = (pos - 2) & 0xFFu; + WriteOutput(cinfo, data, pos); + } + return is_baseline; +} + +void EncodeSOF(j_compress_ptr cinfo, bool is_baseline) { + if (cinfo->data_precision != kJpegPrecision) { + JPEGLI_ERROR("Unsupported data precision %d", cinfo->data_precision); + } + const uint8_t marker = cinfo->progressive_mode ? 0xc2 + : is_baseline ? 0xc0 + : 0xc1; + const size_t n_comps = cinfo->num_components; + const size_t marker_len = 8 + 3 * n_comps; + std::vector data(marker_len + 2); + size_t pos = 0; + data[pos++] = 0xFF; + data[pos++] = marker; + data[pos++] = marker_len >> 8u; + data[pos++] = marker_len & 0xFFu; + data[pos++] = kJpegPrecision; + data[pos++] = cinfo->image_height >> 8u; + data[pos++] = cinfo->image_height & 0xFFu; + data[pos++] = cinfo->image_width >> 8u; + data[pos++] = cinfo->image_width & 0xFFu; + data[pos++] = n_comps; + for (size_t i = 0; i < n_comps; ++i) { + jpeg_component_info* comp = &cinfo->comp_info[i]; + data[pos++] = comp->component_id; + data[pos++] = ((comp->h_samp_factor << 4u) | (comp->v_samp_factor)); + const uint32_t quant_idx = comp->quant_tbl_no; + if (cinfo->quant_tbl_ptrs[quant_idx] == nullptr) { + JPEGLI_ERROR("Invalid component quant table index %u.", quant_idx); + } + data[pos++] = quant_idx; + } + WriteOutput(cinfo, data); +} + +void WriteFrameHeader(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + bool is_baseline = EncodeDQT(cinfo, /*write_all_tables=*/false); + if (cinfo->progressive_mode || cinfo->arith_code || + cinfo->data_precision != 8) { + is_baseline = false; + } + for (size_t i = 0; i < m->num_huffman_tables; ++i) { + int slot_id = m->slot_id_map[i]; + if (slot_id > 0x11 || (slot_id > 0x01 && slot_id < 0x10)) { + is_baseline = false; + } + } + EncodeSOF(cinfo, is_baseline); +} + +void EncodeDRI(j_compress_ptr cinfo) { + WriteOutput(cinfo, {0xFF, 0xDD, 0, 4, + static_cast(cinfo->restart_interval >> 8), + static_cast(cinfo->restart_interval & 0xFF)}); +} + +void EncodeDHT(j_compress_ptr cinfo, size_t offset, size_t num) { + jpeg_comp_master* m = cinfo->master; + size_t marker_len = 2; + for (size_t i = 0; i < num; ++i) { + const JHUFF_TBL& table = m->huffman_tables[offset + i]; + if (table.sent_table) continue; + marker_len += kJpegHuffmanMaxBitLength + 1; + for (size_t j = 0; j <= kJpegHuffmanMaxBitLength; ++j) { + marker_len += table.bits[j]; + } + } + std::vector data(marker_len + 2); + size_t pos = 0; + data[pos++] = 0xFF; + data[pos++] = 0xC4; + data[pos++] = marker_len >> 8u; + data[pos++] = marker_len & 0xFFu; + for (size_t i = 0; i < num; ++i) { + const JHUFF_TBL& table = m->huffman_tables[offset + i]; + if (table.sent_table) continue; + size_t total_count = 0; + for (size_t i = 0; i <= kJpegHuffmanMaxBitLength; ++i) { + total_count += table.bits[i]; + } + data[pos++] = m->slot_id_map[offset + i]; + for (size_t i = 1; i <= kJpegHuffmanMaxBitLength; ++i) { + data[pos++] = table.bits[i]; + } + for (size_t i = 0; i < total_count; ++i) { + data[pos++] = table.huffval[i]; + } + } + if (marker_len > 2) { + WriteOutput(cinfo, data); + } +} + +void EncodeSOS(j_compress_ptr cinfo, int scan_index) { + jpeg_comp_master* m = cinfo->master; + const jpeg_scan_info* scan_info = &cinfo->scan_info[scan_index]; + const size_t marker_len = 6 + 2 * scan_info->comps_in_scan; + std::vector data(marker_len + 2); + size_t pos = 0; + data[pos++] = 0xFF; + data[pos++] = 0xDA; + data[pos++] = marker_len >> 8u; + data[pos++] = marker_len & 0xFFu; + data[pos++] = scan_info->comps_in_scan; + for (int i = 0; i < scan_info->comps_in_scan; ++i) { + int comp_idx = scan_info->component_index[i]; + data[pos++] = cinfo->comp_info[comp_idx].component_id; + int dc_slot_id = m->slot_id_map[m->context_map[comp_idx]]; + int ac_context = m->ac_ctx_offset[scan_index] + i; + int ac_slot_id = m->slot_id_map[m->context_map[ac_context]]; + data[pos++] = (dc_slot_id << 4u) + (ac_slot_id - 16); + } + data[pos++] = scan_info->Ss; + data[pos++] = scan_info->Se; + data[pos++] = ((scan_info->Ah << 4u) | (scan_info->Al)); + WriteOutput(cinfo, data); +} + +void WriteScanHeader(j_compress_ptr cinfo, int scan_index) { + jpeg_comp_master* m = cinfo->master; + const jpeg_scan_info* scan_info = &cinfo->scan_info[scan_index]; + cinfo->restart_interval = m->scan_token_info[scan_index].restart_interval; + if (cinfo->restart_interval != m->last_restart_interval) { + EncodeDRI(cinfo); + m->last_restart_interval = cinfo->restart_interval; + } + size_t num_dht = 0; + if (scan_index == 0) { + // For the first scan we emit all DC and at most 4 AC Huffman codes. + for (size_t i = 0, num_ac = 0; i < m->num_huffman_tables; ++i) { + if (m->slot_id_map[i] >= 16 && num_ac++ >= 4) break; + ++num_dht; + } + } else if (scan_info->Ss > 0) { + // For multi-scan sequential and progressive DC scans we have already + // emitted all Huffman codes that we need before the first scan. For + // progressive AC scans we only need at most one new Huffman code. + if (m->context_map[m->ac_ctx_offset[scan_index]] == m->next_dht_index) { + num_dht = 1; + } + } + if (num_dht > 0) { + EncodeDHT(cinfo, m->next_dht_index, num_dht); + m->next_dht_index += num_dht; + } + EncodeSOS(cinfo, scan_index); +} + +void WriteBlock(const int32_t* JXL_RESTRICT symbols, + const int32_t* JXL_RESTRICT extra_bits, const int num_nonzeros, + const bool emit_eob, + const HuffmanCodeTable* JXL_RESTRICT dc_code, + const HuffmanCodeTable* JXL_RESTRICT ac_code, + JpegBitWriter* JXL_RESTRICT bw) { + int symbol = symbols[0]; + WriteBits(bw, dc_code->depth[symbol], dc_code->code[symbol] | extra_bits[0]); + for (int i = 1; i < num_nonzeros; ++i) { + symbol = symbols[i]; + if (symbol > 255) { + WriteBits(bw, ac_code->depth[0xf0], ac_code->code[0xf0]); + symbol -= 256; + if (symbol > 255) { + WriteBits(bw, ac_code->depth[0xf0], ac_code->code[0xf0]); + symbol -= 256; + if (symbol > 255) { + WriteBits(bw, ac_code->depth[0xf0], ac_code->code[0xf0]); + symbol -= 256; + } + } + } + WriteBits(bw, ac_code->depth[symbol], + ac_code->code[symbol] | extra_bits[i]); + } + if (emit_eob) { + WriteBits(bw, ac_code->depth[0], ac_code->code[0]); + } +} + +namespace { + +JXL_INLINE void EmitMarker(JpegBitWriter* bw, int marker) { + bw->data[bw->pos++] = 0xFF; + bw->data[bw->pos++] = marker; +} + +void WriteTokens(j_compress_ptr cinfo, int scan_index, JpegBitWriter* bw) { + jpeg_comp_master* m = cinfo->master; + HuffmanCodeTable* coding_tables = &m->coding_tables[0]; + int next_restart_marker = 0; + const ScanTokenInfo& sti = m->scan_token_info[scan_index]; + size_t num_token_arrays = m->cur_token_array + 1; + size_t total_tokens = 0; + size_t restart_idx = 0; + size_t next_restart = sti.restarts[restart_idx]; + uint8_t* context_map = m->context_map; + for (size_t i = 0; i < num_token_arrays; ++i) { + Token* tokens = m->token_arrays[i].tokens; + size_t num_tokens = m->token_arrays[i].num_tokens; + if (sti.token_offset < total_tokens + num_tokens && + total_tokens < sti.token_offset + sti.num_tokens) { + size_t start_ix = + total_tokens < sti.token_offset ? sti.token_offset - total_tokens : 0; + size_t end_ix = std::min(sti.token_offset + sti.num_tokens - total_tokens, + num_tokens); + size_t cycle_len = bw->len / 8; + size_t next_cycle = cycle_len; + for (size_t i = start_ix; i < end_ix; ++i) { + if (total_tokens + i == next_restart) { + JumpToByteBoundary(bw); + EmitMarker(bw, 0xD0 + next_restart_marker); + next_restart_marker += 1; + next_restart_marker &= 0x7; + next_restart = sti.restarts[++restart_idx]; + } + Token t = tokens[i]; + const HuffmanCodeTable* code = &coding_tables[context_map[t.context]]; + WriteBits(bw, code->depth[t.symbol], code->code[t.symbol] | t.bits); + if (--next_cycle == 0) { + if (!EmptyBitWriterBuffer(bw)) { + JPEGLI_ERROR( + "Output suspension is not supported in " + "finish_compress"); + } + next_cycle = cycle_len; + } + } + } + total_tokens += num_tokens; + } +} + +void WriteACRefinementTokens(j_compress_ptr cinfo, int scan_index, + JpegBitWriter* bw) { + jpeg_comp_master* m = cinfo->master; + const ScanTokenInfo& sti = m->scan_token_info[scan_index]; + const uint8_t context = m->ac_ctx_offset[scan_index]; + const HuffmanCodeTable* code = &m->coding_tables[m->context_map[context]]; + size_t cycle_len = bw->len / 64; + size_t next_cycle = cycle_len; + size_t refbit_idx = 0; + size_t eobrun_idx = 0; + size_t restart_idx = 0; + size_t next_restart = sti.restarts[restart_idx]; + int next_restart_marker = 0; + for (size_t i = 0; i < sti.num_tokens; ++i) { + if (i == next_restart) { + JumpToByteBoundary(bw); + EmitMarker(bw, 0xD0 + next_restart_marker); + next_restart_marker += 1; + next_restart_marker &= 0x7; + next_restart = sti.restarts[++restart_idx]; + } + RefToken t = sti.tokens[i]; + int symbol = t.symbol & 253; + uint16_t bits = 0; + if ((symbol & 1) == 0) { + int r = symbol >> 4; + if (r > 0 && r < 15) { + bits = sti.eobruns[eobrun_idx++]; + } + } else { + bits = (t.symbol >> 1) & 1; + } + WriteBits(bw, code->depth[symbol], code->code[symbol] | bits); + for (int j = 0; j < t.refbits; ++j) { + WriteBits(bw, 1, sti.refbits[refbit_idx++]); + } + if (--next_cycle == 0) { + if (!EmptyBitWriterBuffer(bw)) { + JPEGLI_ERROR("Output suspension is not supported in finish_compress"); + } + next_cycle = cycle_len; + } + } +} + +void WriteDCRefinementBits(j_compress_ptr cinfo, int scan_index, + JpegBitWriter* bw) { + jpeg_comp_master* m = cinfo->master; + const ScanTokenInfo& sti = m->scan_token_info[scan_index]; + size_t restart_idx = 0; + size_t next_restart = sti.restarts[restart_idx]; + int next_restart_marker = 0; + size_t cycle_len = bw->len * 4; + size_t next_cycle = cycle_len; + size_t refbit_idx = 0; + for (size_t i = 0; i < sti.num_tokens; ++i) { + if (i == next_restart) { + JumpToByteBoundary(bw); + EmitMarker(bw, 0xD0 + next_restart_marker); + next_restart_marker += 1; + next_restart_marker &= 0x7; + next_restart = sti.restarts[++restart_idx]; + } + WriteBits(bw, 1, sti.refbits[refbit_idx++]); + if (--next_cycle == 0) { + if (!EmptyBitWriterBuffer(bw)) { + JPEGLI_ERROR( + "Output suspension is not supported in " + "finish_compress"); + } + next_cycle = cycle_len; + } + } +} + +} // namespace + +void WriteScanData(j_compress_ptr cinfo, int scan_index) { + const jpeg_scan_info* scan_info = &cinfo->scan_info[scan_index]; + JpegBitWriter* bw = &cinfo->master->bw; + if (scan_info->Ah == 0) { + WriteTokens(cinfo, scan_index, bw); + } else if (scan_info->Ss > 0) { + WriteACRefinementTokens(cinfo, scan_index, bw); + } else { + WriteDCRefinementBits(cinfo, scan_index, bw); + } + if (!bw->healthy) { + JPEGLI_ERROR("Unknown Huffman coded symbol found in scan %d", scan_index); + } + JumpToByteBoundary(bw); + if (!EmptyBitWriterBuffer(bw)) { + JPEGLI_ERROR("Output suspension is not supported in finish_compress"); + } +} + +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/bitstream.h b/media/libjxl/src/lib/jpegli/bitstream.h new file mode 100644 index 0000000000..bed441aefe --- /dev/null +++ b/media/libjxl/src/lib/jpegli/bitstream.h @@ -0,0 +1,43 @@ +// 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_JPEGLI_BITSTREAM_H_ +#define LIB_JPEGLI_BITSTREAM_H_ + +#include +#include + +#include "lib/jpegli/encode_internal.h" + +namespace jpegli { + +void WriteOutput(j_compress_ptr cinfo, const uint8_t* buf, size_t bufsize); +void WriteOutput(j_compress_ptr cinfo, const std::vector& bytes); +void WriteOutput(j_compress_ptr cinfo, std::initializer_list bytes); + +void EncodeAPP0(j_compress_ptr cinfo); +void EncodeAPP14(j_compress_ptr cinfo); +void WriteFileHeader(j_compress_ptr cinfo); + +// Returns true of only baseline 8-bit tables are used. +bool EncodeDQT(j_compress_ptr cinfo, bool write_all_tables); +void EncodeSOF(j_compress_ptr cinfo, bool is_baseline); +void WriteFrameHeader(j_compress_ptr cinfo); + +void EncodeDRI(j_compress_ptr cinfo); +void EncodeDHT(j_compress_ptr cinfo, size_t offset, size_t num); +void EncodeSOS(j_compress_ptr cinfo, int scan_index); +void WriteScanHeader(j_compress_ptr cinfo, int scan_index); + +void WriteBlock(const int32_t* JXL_RESTRICT symbols, + const int32_t* JXL_RESTRICT extra_bits, int num_nonzeros, + bool emit_eob, const HuffmanCodeTable* JXL_RESTRICT dc_code, + const HuffmanCodeTable* JXL_RESTRICT ac_code, + JpegBitWriter* JXL_RESTRICT bw); +void WriteScanData(j_compress_ptr cinfo, int scan_index); + +} // namespace jpegli + +#endif // LIB_JPEGLI_BITSTREAM_H_ diff --git a/media/libjxl/src/lib/jpegli/color_quantize.cc b/media/libjxl/src/lib/jpegli/color_quantize.cc new file mode 100644 index 0000000000..d5b0097f3d --- /dev/null +++ b/media/libjxl/src/lib/jpegli/color_quantize.cc @@ -0,0 +1,536 @@ +// 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/jpegli/color_quantize.h" + +#include +#include +#include + +#include "lib/jpegli/decode_internal.h" +#include "lib/jpegli/error.h" + +namespace jpegli { + +namespace { + +constexpr int kNumColorCellBits[kMaxComponents] = {3, 4, 3, 3}; +constexpr int kCompW[kMaxComponents] = {2, 3, 1, 1}; + +int Pow(int a, int b) { + int r = 1; + for (int i = 0; i < b; ++i) { + r *= a; + } + return r; +} + +int ComponentOrder(j_decompress_ptr cinfo, int i) { + if (cinfo->out_color_components == 3) { + return i < 2 ? 1 - i : i; + } + return i; +} + +int GetColorComponent(int i, int N) { + return (i * 255 + (N - 1) / 2) / (N - 1); +} + +} // namespace + +void ChooseColorMap1Pass(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + int components = cinfo->out_color_components; + int desired = std::min(cinfo->desired_number_of_colors, 256); + int num = 1; + while (Pow(num + 1, components) <= desired) { + ++num; + } + if (num == 1) { + JPEGLI_ERROR("Too few colors (%d) in requested colormap", desired); + } + int actual = Pow(num, components); + for (int i = 0; i < components; ++i) { + m->num_colors_[i] = num; + } + while (actual < desired) { + int total = actual; + for (int i = 0; i < components; ++i) { + int c = ComponentOrder(cinfo, i); + int new_total = (actual / m->num_colors_[c]) * (m->num_colors_[c] + 1); + if (new_total <= desired) { + ++m->num_colors_[c]; + actual = new_total; + } + } + if (actual == total) { + break; + } + } + cinfo->actual_number_of_colors = actual; + cinfo->colormap = (*cinfo->mem->alloc_sarray)( + reinterpret_cast(cinfo), JPOOL_IMAGE, actual, components); + int next_color[kMaxComponents] = {0}; + for (int i = 0; i < actual; ++i) { + for (int c = 0; c < components; ++c) { + cinfo->colormap[c][i] = + GetColorComponent(next_color[c], m->num_colors_[c]); + } + int c = components - 1; + while (c > 0 && next_color[c] + 1 == m->num_colors_[c]) { + next_color[c--] = 0; + } + ++next_color[c]; + } + if (!m->colormap_lut_) { + m->colormap_lut_ = Allocate(cinfo, components * 256, JPOOL_IMAGE); + } + int stride = actual; + for (int c = 0; c < components; ++c) { + int N = m->num_colors_[c]; + stride /= N; + for (int i = 0; i < 256; ++i) { + int index = ((2 * i - 1) * (N - 1) + 254) / 510; + m->colormap_lut_[c * 256 + i] = index * stride; + } + } +} + +namespace { + +// 2^13 priority levels for the PQ seems to be a good compromise between +// accuracy, running time and stack space usage. +const int kMaxPriority = 1 << 13; +const int kMaxLevel = 3; + +// This function is used in the multi-resolution grid to be able to compute +// the keys for the different resolutions by just shifting the first key. +inline int InterlaceBitsRGB(uint8_t r, uint8_t g, uint8_t b) { + int z = 0; + for (int i = 0; i < 7; ++i) { + z += (r >> 5) & 4; + z += (g >> 6) & 2; + z += (b >> 7); + z <<= 3; + r <<= 1; + g <<= 1; + b <<= 1; + } + z += (r >> 5) & 4; + z += (g >> 6) & 2; + z += (b >> 7); + return z; +} + +// This function will compute the actual priorities of the colors based on +// the current distance from the palette, the population count and the signals +// from the multi-resolution grid. +inline int Priority(int d, int n, const int* density, const int* radius) { + int p = d * n; + for (int level = 0; level < kMaxLevel; ++level) { + if (d > radius[level]) { + p += density[level] * (d - radius[level]); + } + } + return std::min(kMaxPriority - 1, p >> 4); +} + +inline int ColorIntQuadDistanceRGB(uint8_t r1, uint8_t g1, uint8_t b1, + uint8_t r2, uint8_t g2, uint8_t b2) { + // weights for the intensity calculation + static constexpr int ired = 2; + static constexpr int igreen = 5; + static constexpr int iblue = 1; + // normalization factor for the intensity calculation (2^ishift) + static constexpr int ishift = 3; + const int rd = r1 - r2; + const int gd = g1 - g2; + const int bd = b1 - b2; + const int id = ired * rd + igreen * gd + iblue * bd; + return rd * rd + gd * gd + bd * bd + ((id * id) >> (2 * ishift)); +} + +inline int ScaleQuadDistanceRGB(int d) { + return static_cast(std::lround(sqrt(d * 0.25))); +} + +// The function updates the minimal distances, the clustering and the +// quantization error after the insertion of the new color into the palette. +void AddToRGBPalette(const uint8_t* red, const uint8_t* green, + const uint8_t* blue, + const int* count, // histogram of colors + const int index, // index of color to be added + const int k, // size of current palette + const int n, // number of colors + int* dist, // array of distances from palette + int* cluster, // mapping of color indices to palette + int* center, // the inverse mapping + int64_t* error) { // measure of the quantization error + center[k] = index; + cluster[index] = k; + *error -= + static_cast(dist[index]) * static_cast(count[index]); + dist[index] = 0; + for (int j = 0; j < n; ++j) { + if (dist[j] > 0) { + const int d = ColorIntQuadDistanceRGB( + red[index], green[index], blue[index], red[j], green[j], blue[j]); + if (d < dist[j]) { + *error += static_cast((d - dist[j])) * + static_cast(count[j]); + dist[j] = d; + cluster[j] = k; + } + } + } +} + +struct RGBPixelHasher { + // A quick but good-enough hash to get 24 bits of RGB into the lower 12 bits. + size_t operator()(uint32_t a) const { return (a ^ (a >> 12)) * 0x9e3779b9; } +}; + +struct WangHasher { + // Thomas Wang's Hash. Nearly perfect and still quite fast. Above (for + // pixels) we use a simpler hash because the number of hash calls is + // proportional to the number of pixels and that hash dominates; we want the + // cost to be minimal and we start with a large table. We can use a better + // hash for the histogram since the number of hash calls is proportional to + // the number of unique colors in the image, which is hopefully much smaller. + // Note that the difference is slight; e.g. replacing RGBPixelHasher with + // WangHasher only slows things down by 5% on an Opteron. + size_t operator()(uint32_t a) const { + a = (a ^ 61) ^ (a >> 16); + a = a + (a << 3); + a = a ^ (a >> 4); + a = a * 0x27d4eb2d; + a = a ^ (a >> 15); + return a; + } +}; + +// Build an index of all the different colors in the input +// image. To do this we map the 24 bit RGB representation of the colors +// to a unique integer index assigned to the different colors in order of +// appearance in the image. Return the number of unique colors found. +// The colors are pre-quantized to 3 * 6 bits precision. +int BuildRGBColorIndex(const uint8_t* const image, int const num_pixels, + int* const count, uint8_t* const red, + uint8_t* const green, uint8_t* const blue) { + // Impossible because rgb are in the low 24 bits, and the upper 8 bits is 0. + const uint32_t impossible_pixel_value = 0x10000000; + std::unordered_map index_map(1 << 12); + std::unordered_map::iterator index_map_lookup; + const uint8_t* imagep = &image[0]; + uint32_t prev_pixel = impossible_pixel_value; + int index = 0; + int n = 0; + for (int i = 0; i < num_pixels; ++i) { + uint8_t r = ((*imagep++) & 0xfc) + 2; + uint8_t g = ((*imagep++) & 0xfc) + 2; + uint8_t b = ((*imagep++) & 0xfc) + 2; + uint32_t pixel = (b << 16) | (g << 8) | r; + if (pixel != prev_pixel) { + prev_pixel = pixel; + index_map_lookup = index_map.find(pixel); + if (index_map_lookup != index_map.end()) { + index = index_map_lookup->second; + } else { + index_map[pixel] = index = n++; + red[index] = r; + green[index] = g; + blue[index] = b; + } + } + ++count[index]; + } + return n; +} + +} // namespace + +void ChooseColorMap2Pass(j_decompress_ptr cinfo) { + if (cinfo->out_color_space != JCS_RGB) { + JPEGLI_ERROR("Two-pass quantizer must use RGB output color space."); + } + jpeg_decomp_master* m = cinfo->master; + const size_t num_pixels = cinfo->output_width * cinfo->output_height; + const int max_color_count = std::max(num_pixels, 1u << 18); + const int max_palette_size = cinfo->desired_number_of_colors; + std::unique_ptr red(new uint8_t[max_color_count]); + std::unique_ptr green(new uint8_t[max_color_count]); + std::unique_ptr blue(new uint8_t[max_color_count]); + std::vector count(max_color_count, 0); + // number of colors + int n = BuildRGBColorIndex(m->pixels_, num_pixels, count.data(), &red[0], + &green[0], &blue[0]); + + std::vector dist(n, std::numeric_limits::max()); + std::vector cluster(n); + std::vector in_palette(n, false); + int center[256]; + int k = 0; // palette size + const int count_threshold = (num_pixels * 4) / max_palette_size; + static constexpr int kAveragePixelErrorThreshold = 1; + const int64_t error_threshold = num_pixels * kAveragePixelErrorThreshold; + int64_t error = 0; // quantization error + + int max_count = 0; + int winner = 0; + for (int i = 0; i < n; ++i) { + if (count[i] > max_count) { + max_count = count[i]; + winner = i; + } + if (!in_palette[i] && count[i] > count_threshold) { + AddToRGBPalette(&red[0], &green[0], &blue[0], count.data(), i, k++, n, + dist.data(), cluster.data(), ¢er[0], &error); + in_palette[i] = true; + } + } + if (k == 0) { + AddToRGBPalette(&red[0], &green[0], &blue[0], count.data(), winner, k++, n, + dist.data(), cluster.data(), ¢er[0], &error); + in_palette[winner] = true; + } + + // Calculation of the multi-resolution density grid. + std::vector density(n * kMaxLevel); + std::vector radius(n * kMaxLevel); + std::unordered_map histogram[kMaxLevel]; + for (int level = 0; level < kMaxLevel; ++level) { + // This value is never used because key = InterlaceBitsRGB(...) >> 6 + } + + for (int i = 0; i < n; ++i) { + if (!in_palette[i]) { + const int key = InterlaceBitsRGB(red[i], green[i], blue[i]) >> 6; + for (int level = 0; level < kMaxLevel; ++level) { + histogram[level][key >> (3 * level)] += count[i]; + } + } + } + for (int i = 0; i < n; ++i) { + if (!in_palette[i]) { + for (int level = 0; level < kMaxLevel; ++level) { + const int mask = (4 << level) - 1; + const int rd = std::max(red[i] & mask, mask - (red[i] & mask)); + const int gd = std::max(green[i] & mask, mask - (green[i] & mask)); + const int bd = std::max(blue[i] & mask, mask - (blue[i] & mask)); + radius[i * kMaxLevel + level] = + ScaleQuadDistanceRGB(ColorIntQuadDistanceRGB(0, 0, 0, rd, gd, bd)); + } + const int key = InterlaceBitsRGB(red[i], green[i], blue[i]) >> 6; + if (kMaxLevel > 0) { + density[i * kMaxLevel] = histogram[0][key] - count[i]; + } + for (int level = 1; level < kMaxLevel; ++level) { + density[i * kMaxLevel + level] = + (histogram[level][key >> (3 * level)] - + histogram[level - 1][key >> (3 * level - 3)]); + } + } + } + + // Calculate the initial error now that the palette has been initialized. + error = 0; + for (int i = 0; i < n; ++i) { + error += static_cast(dist[i]) * static_cast(count[i]); + } + + std::unique_ptr[]> bucket_array( + new std::vector[kMaxPriority]); + int top_priority = -1; + for (int i = 0; i < n; ++i) { + if (!in_palette[i]) { + int priority = Priority(ScaleQuadDistanceRGB(dist[i]), count[i], + &density[i * kMaxLevel], &radius[i * kMaxLevel]); + bucket_array[priority].push_back(i); + top_priority = std::max(priority, top_priority); + } + } + double error_accum = 0; + while (top_priority >= 0 && k < max_palette_size) { + if (error < error_threshold) { + error_accum += std::min(error_threshold, error_threshold - error); + if (error_accum >= 10 * error_threshold) { + break; + } + } + int i = bucket_array[top_priority].back(); + int priority = Priority(ScaleQuadDistanceRGB(dist[i]), count[i], + &density[i * kMaxLevel], &radius[i * kMaxLevel]); + if (priority < top_priority) { + bucket_array[priority].push_back(i); + } else { + AddToRGBPalette(&red[0], &green[0], &blue[0], count.data(), i, k++, n, + dist.data(), cluster.data(), ¢er[0], &error); + } + bucket_array[top_priority].pop_back(); + while (top_priority >= 0 && bucket_array[top_priority].empty()) { + --top_priority; + } + } + + cinfo->actual_number_of_colors = k; + cinfo->colormap = (*cinfo->mem->alloc_sarray)( + reinterpret_cast(cinfo), JPOOL_IMAGE, k, 3); + for (int i = 0; i < k; ++i) { + int index = center[i]; + cinfo->colormap[0][i] = red[index]; + cinfo->colormap[1][i] = green[index]; + cinfo->colormap[2][i] = blue[index]; + } +} + +namespace { + +void FindCandidatesForCell(j_decompress_ptr cinfo, int ncomp, const int cell[], + std::vector* candidates) { + int cell_min[kMaxComponents]; + int cell_max[kMaxComponents]; + int cell_center[kMaxComponents]; + for (int c = 0; c < ncomp; ++c) { + cell_min[c] = cell[c] << (8 - kNumColorCellBits[c]); + cell_max[c] = cell_min[c] + (1 << (8 - kNumColorCellBits[c])) - 1; + cell_center[c] = (cell_min[c] + cell_max[c]) >> 1; + } + int min_maxdist = std::numeric_limits::max(); + int mindist[256]; + for (int i = 0; i < cinfo->actual_number_of_colors; ++i) { + int dmin = 0; + int dmax = 0; + for (int c = 0; c < ncomp; ++c) { + int palette_c = cinfo->colormap[c][i]; + int dminc = 0; + int dmaxc; + if (palette_c < cell_min[c]) { + dminc = cell_min[c] - palette_c; + dmaxc = cell_max[c] - palette_c; + } else if (palette_c > cell_max[c]) { + dminc = palette_c - cell_max[c]; + dmaxc = palette_c - cell_min[c]; + } else if (palette_c > cell_center[c]) { + dmaxc = palette_c - cell_min[c]; + } else { + dmaxc = cell_max[c] - palette_c; + } + dminc *= kCompW[c]; + dmaxc *= kCompW[c]; + dmin += dminc * dminc; + dmax += dmaxc * dmaxc; + } + mindist[i] = dmin; + min_maxdist = std::min(dmax, min_maxdist); + } + for (int i = 0; i < cinfo->actual_number_of_colors; ++i) { + if (mindist[i] < min_maxdist) { + candidates->push_back(i); + } + } +} + +} // namespace + +void CreateInverseColorMap(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + int ncomp = cinfo->out_color_components; + JPEGLI_CHECK(ncomp > 0); + JPEGLI_CHECK(ncomp <= kMaxComponents); + int num_cells = 1; + for (int c = 0; c < ncomp; ++c) { + num_cells *= (1 << kNumColorCellBits[c]); + } + m->candidate_lists_.resize(num_cells); + + int next_cell[kMaxComponents] = {0}; + for (int i = 0; i < num_cells; ++i) { + m->candidate_lists_[i].clear(); + FindCandidatesForCell(cinfo, ncomp, next_cell, &m->candidate_lists_[i]); + int c = ncomp - 1; + while (c > 0 && next_cell[c] + 1 == (1 << kNumColorCellBits[c])) { + next_cell[c--] = 0; + } + ++next_cell[c]; + } + m->regenerate_inverse_colormap_ = false; +} + +int LookupColorIndex(j_decompress_ptr cinfo, const JSAMPLE* pixel) { + jpeg_decomp_master* m = cinfo->master; + int num_channels = cinfo->out_color_components; + int index = 0; + if (m->quant_mode_ == 1) { + for (int c = 0; c < num_channels; ++c) { + index += m->colormap_lut_[c * 256 + pixel[c]]; + } + } else { + size_t cell_idx = 0; + size_t stride = 1; + for (int c = num_channels - 1; c >= 0; --c) { + cell_idx += (pixel[c] >> (8 - kNumColorCellBits[c])) * stride; + stride <<= kNumColorCellBits[c]; + } + JPEGLI_CHECK(cell_idx < m->candidate_lists_.size()); + int mindist = std::numeric_limits::max(); + const auto& candidates = m->candidate_lists_[cell_idx]; + for (uint8_t i : candidates) { + int dist = 0; + for (int c = 0; c < num_channels; ++c) { + int d = (cinfo->colormap[c][i] - pixel[c]) * kCompW[c]; + dist += d * d; + } + if (dist < mindist) { + mindist = dist; + index = i; + } + } + } + JPEGLI_CHECK(index < cinfo->actual_number_of_colors); + return index; +} + +void CreateOrderedDitherTables(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + static constexpr size_t kDitherSize = 4; + static constexpr size_t kDitherMask = kDitherSize - 1; + static constexpr float kBaseDitherMatrix[] = { + 0, 8, 2, 10, // + 12, 4, 14, 6, // + 3, 11, 1, 9, // + 15, 7, 13, 5, // + }; + m->dither_size_ = kDitherSize; + m->dither_mask_ = kDitherMask; + size_t ncells = m->dither_size_ * m->dither_size_; + for (int c = 0; c < cinfo->out_color_components; ++c) { + float spread = 1.0f / (m->num_colors_[c] - 1); + float mul = spread / ncells; + float offset = 0.5f * spread; + if (m->dither_[c] == nullptr) { + m->dither_[c] = Allocate(cinfo, ncells, JPOOL_IMAGE_ALIGNED); + } + for (size_t idx = 0; idx < ncells; ++idx) { + m->dither_[c][idx] = kBaseDitherMatrix[idx] * mul - offset; + } + } +} + +void InitFSDitherState(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + for (int c = 0; c < cinfo->out_color_components; ++c) { + if (m->error_row_[c] == nullptr) { + m->error_row_[c] = + Allocate(cinfo, cinfo->output_width, JPOOL_IMAGE_ALIGNED); + m->error_row_[c + kMaxComponents] = + Allocate(cinfo, cinfo->output_width, JPOOL_IMAGE_ALIGNED); + } + memset(m->error_row_[c], 0.0, cinfo->output_width * sizeof(float)); + memset(m->error_row_[c + kMaxComponents], 0.0, + cinfo->output_width * sizeof(float)); + } +} + +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/color_quantize.h b/media/libjxl/src/lib/jpegli/color_quantize.h new file mode 100644 index 0000000000..92b922f756 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/color_quantize.h @@ -0,0 +1,27 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_JPEGLI_COLOR_QUANTIZE_H_ +#define LIB_JPEGLI_COLOR_QUANTIZE_H_ + +#include "lib/jpegli/common.h" + +namespace jpegli { + +void ChooseColorMap1Pass(j_decompress_ptr cinfo); + +void ChooseColorMap2Pass(j_decompress_ptr cinfo); + +void CreateInverseColorMap(j_decompress_ptr cinfo); + +void CreateOrderedDitherTables(j_decompress_ptr cinfo); + +void InitFSDitherState(j_decompress_ptr cinfo); + +int LookupColorIndex(j_decompress_ptr cinfo, const JSAMPLE* pixel); + +} // namespace jpegli + +#endif // LIB_JPEGLI_COLOR_QUANTIZE_H_ diff --git a/media/libjxl/src/lib/jpegli/color_transform.cc b/media/libjxl/src/lib/jpegli/color_transform.cc new file mode 100644 index 0000000000..ec906bedce --- /dev/null +++ b/media/libjxl/src/lib/jpegli/color_transform.cc @@ -0,0 +1,547 @@ +// 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/jpegli/color_transform.h" + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jpegli/color_transform.cc" +#include +#include + +#include "lib/jpegli/decode_internal.h" +#include "lib/jpegli/encode_internal.h" +#include "lib/jpegli/error.h" +#include "lib/jxl/base/compiler_specific.h" + +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { + +// These templates are not found via ADL. +using hwy::HWY_NAMESPACE::Add; +using hwy::HWY_NAMESPACE::Div; +using hwy::HWY_NAMESPACE::Mul; +using hwy::HWY_NAMESPACE::MulAdd; +using hwy::HWY_NAMESPACE::Sub; + +template +void YCbCrToExtRGB(float* row[kMaxComponents], size_t xsize) { + const HWY_CAPPED(float, 8) df; + const float* row_y = row[0]; + const float* row_cb = row[1]; + const float* row_cr = row[2]; + float* row_r = row[kRed]; + float* row_g = row[kGreen]; + float* row_b = row[kBlue]; + float* row_a = row[kAlpha]; + + // Full-range BT.601 as defined by JFIF Clause 7: + // https://www.itu.int/rec/T-REC-T.871-201105-I/en + const auto crcr = Set(df, 1.402f); + const auto cgcb = Set(df, -0.114f * 1.772f / 0.587f); + const auto cgcr = Set(df, -0.299f * 1.402f / 0.587f); + const auto cbcb = Set(df, 1.772f); + const auto alpha_opaque = Set(df, 127.0f / 255.0f); + + for (size_t x = 0; x < xsize; x += Lanes(df)) { + const auto y_vec = Load(df, row_y + x); + const auto cb_vec = Load(df, row_cb + x); + const auto cr_vec = Load(df, row_cr + x); + const auto r_vec = MulAdd(crcr, cr_vec, y_vec); + const auto g_vec = MulAdd(cgcr, cr_vec, MulAdd(cgcb, cb_vec, y_vec)); + const auto b_vec = MulAdd(cbcb, cb_vec, y_vec); + Store(r_vec, df, row_r + x); + Store(g_vec, df, row_g + x); + Store(b_vec, df, row_b + x); + if (kAlpha >= 0) { + Store(alpha_opaque, df, row_a + x); + } + } +} + +void YCbCrToRGB(float* row[kMaxComponents], size_t xsize) { + YCbCrToExtRGB<0, 1, 2, -1>(row, xsize); +} + +void YCbCrToBGR(float* row[kMaxComponents], size_t xsize) { + YCbCrToExtRGB<2, 1, 0, -1>(row, xsize); +} + +void YCbCrToRGBA(float* row[kMaxComponents], size_t xsize) { + YCbCrToExtRGB<0, 1, 2, 3>(row, xsize); +} + +void YCbCrToBGRA(float* row[kMaxComponents], size_t xsize) { + YCbCrToExtRGB<2, 1, 0, 3>(row, xsize); +} + +void YCbCrToARGB(float* row[kMaxComponents], size_t xsize) { + YCbCrToExtRGB<1, 2, 3, 0>(row, xsize); +} + +void YCbCrToABGR(float* row[kMaxComponents], size_t xsize) { + YCbCrToExtRGB<3, 2, 1, 0>(row, xsize); +} + +void YCCKToCMYK(float* row[kMaxComponents], size_t xsize) { + const HWY_CAPPED(float, 8) df; + float* JXL_RESTRICT row0 = row[0]; + float* JXL_RESTRICT row1 = row[1]; + float* JXL_RESTRICT row2 = row[2]; + YCbCrToRGB(row, xsize); + const auto offset = Set(df, -1.0f / 255.0f); + for (size_t x = 0; x < xsize; x += Lanes(df)) { + Store(Sub(offset, Load(df, row0 + x)), df, row0 + x); + Store(Sub(offset, Load(df, row1 + x)), df, row1 + x); + Store(Sub(offset, Load(df, row2 + x)), df, row2 + x); + } +} + +template +void ExtRGBToYCbCr(float* row[kMaxComponents], size_t xsize) { + const HWY_CAPPED(float, 8) df; + const float* row_r = row[kRed]; + const float* row_g = row[kGreen]; + const float* row_b = row[kBlue]; + float* row_y = row[0]; + float* row_cb = row[1]; + float* row_cr = row[2]; + // Full-range BT.601 as defined by JFIF Clause 7: + // https://www.itu.int/rec/T-REC-T.871-201105-I/en + const auto c128 = Set(df, 128.0f); + const auto kR = Set(df, 0.299f); // NTSC luma + const auto kG = Set(df, 0.587f); + const auto kB = Set(df, 0.114f); + const auto kAmpR = Set(df, 0.701f); + const auto kAmpB = Set(df, 0.886f); + const auto kDiffR = Add(kAmpR, kR); + const auto kDiffB = Add(kAmpB, kB); + const auto kNormR = Div(Set(df, 1.0f), (Add(kAmpR, Add(kG, kB)))); + const auto kNormB = Div(Set(df, 1.0f), (Add(kR, Add(kG, kAmpB)))); + + for (size_t x = 0; x < xsize; x += Lanes(df)) { + const auto r = Load(df, row_r + x); + const auto g = Load(df, row_g + x); + const auto b = Load(df, row_b + x); + const auto r_base = Mul(r, kR); + const auto r_diff = Mul(r, kDiffR); + const auto g_base = Mul(g, kG); + const auto b_base = Mul(b, kB); + const auto b_diff = Mul(b, kDiffB); + const auto y_base = Add(r_base, Add(g_base, b_base)); + const auto cb_vec = MulAdd(Sub(b_diff, y_base), kNormB, c128); + const auto cr_vec = MulAdd(Sub(r_diff, y_base), kNormR, c128); + Store(y_base, df, row_y + x); + Store(cb_vec, df, row_cb + x); + Store(cr_vec, df, row_cr + x); + } +} + +void RGBToYCbCr(float* row[kMaxComponents], size_t xsize) { + ExtRGBToYCbCr<0, 1, 2>(row, xsize); +} + +void BGRToYCbCr(float* row[kMaxComponents], size_t xsize) { + ExtRGBToYCbCr<2, 1, 0>(row, xsize); +} + +void ARGBToYCbCr(float* row[kMaxComponents], size_t xsize) { + ExtRGBToYCbCr<1, 2, 3>(row, xsize); +} + +void ABGRToYCbCr(float* row[kMaxComponents], size_t xsize) { + ExtRGBToYCbCr<3, 2, 1>(row, xsize); +} + +void CMYKToYCCK(float* row[kMaxComponents], size_t xsize) { + const HWY_CAPPED(float, 8) df; + float* JXL_RESTRICT row0 = row[0]; + float* JXL_RESTRICT row1 = row[1]; + float* JXL_RESTRICT row2 = row[2]; + const auto unity = Set(df, 255.0f); + for (size_t x = 0; x < xsize; x += Lanes(df)) { + Store(Sub(unity, Load(df, row0 + x)), df, row0 + x); + Store(Sub(unity, Load(df, row1 + x)), df, row1 + x); + Store(Sub(unity, Load(df, row2 + x)), df, row2 + x); + } + RGBToYCbCr(row, xsize); +} + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace jpegli { + +HWY_EXPORT(CMYKToYCCK); +HWY_EXPORT(YCCKToCMYK); +HWY_EXPORT(YCbCrToRGB); +HWY_EXPORT(YCbCrToBGR); +HWY_EXPORT(YCbCrToRGBA); +HWY_EXPORT(YCbCrToBGRA); +HWY_EXPORT(YCbCrToARGB); +HWY_EXPORT(YCbCrToABGR); +HWY_EXPORT(RGBToYCbCr); +HWY_EXPORT(BGRToYCbCr); +HWY_EXPORT(ARGBToYCbCr); +HWY_EXPORT(ABGRToYCbCr); + +bool CheckColorSpaceComponents(int num_components, J_COLOR_SPACE colorspace) { + switch (colorspace) { + case JCS_GRAYSCALE: + return num_components == 1; + case JCS_RGB: + case JCS_YCbCr: +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGB: + case JCS_EXT_BGR: +#endif + return num_components == 3; + case JCS_CMYK: + case JCS_YCCK: +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGBX: + case JCS_EXT_BGRX: + case JCS_EXT_XBGR: + case JCS_EXT_XRGB: +#endif +#ifdef JCS_ALPHA_EXTENSIONS + case JCS_EXT_RGBA: + case JCS_EXT_BGRA: + case JCS_EXT_ABGR: + case JCS_EXT_ARGB: +#endif + return num_components == 4; + default: + // Unrecognized colorspaces can have any number of channels, since no + // color transform will be performed on them. + return true; + } +} + +void NullTransform(float* row[kMaxComponents], size_t len) {} + +void FillAlpha(float* row, size_t len) { + static const float kAlpha = 127.0f / 255.0f; + for (size_t i = 0; i < len; ++i) { + row[i] = kAlpha; + } +} + +// Works for BGR as well. +void GrayscaleToRGB(float* row[kMaxComponents], size_t len) { + memcpy(row[1], row[0], len * sizeof(row[1][0])); + memcpy(row[2], row[0], len * sizeof(row[2][0])); +} + +// Works for BGRA as well. +void GrayscaleToRGBA(float* row[kMaxComponents], size_t len) { + memcpy(row[1], row[0], len * sizeof(row[1][0])); + memcpy(row[2], row[0], len * sizeof(row[2][0])); + FillAlpha(row[3], len); +} + +// Works for ABGR as well. +void GrayscaleToARGB(float* row[kMaxComponents], size_t len) { + memcpy(row[1], row[0], len * sizeof(row[1][0])); + memcpy(row[2], row[0], len * sizeof(row[2][0])); + memcpy(row[3], row[0], len * sizeof(row[1][0])); + FillAlpha(row[0], len); +} + +void GrayscaleToYCbCr(float* row[kMaxComponents], size_t len) { + memset(row[1], 0, len * sizeof(row[1][0])); + memset(row[2], 0, len * sizeof(row[2][0])); +} + +void RGBToBGR(float* row[kMaxComponents], size_t len) { + for (size_t i = 0; i < len; ++i) { + std::swap(row[0][i], row[2][i]); + } +} + +void RGBToRGBA(float* row[kMaxComponents], size_t len) { + FillAlpha(row[3], len); +} + +void RGBToBGRA(float* row[kMaxComponents], size_t len) { + static const float kAlpha = 127.0f / 255.0f; + for (size_t i = 0; i < len; ++i) { + std::swap(row[0][i], row[2][i]); + row[3][i] = kAlpha; + } +} + +void RGBToARGB(float* row[kMaxComponents], size_t len) { + memcpy(row[3], row[2], len * sizeof(row[1][0])); + memcpy(row[2], row[1], len * sizeof(row[2][0])); + memcpy(row[1], row[0], len * sizeof(row[1][0])); + FillAlpha(row[0], len); +} + +void RGBToABGR(float* row[kMaxComponents], size_t len) { + static const float kAlpha = 127.0f / 255.0f; + for (size_t i = 0; i < len; ++i) { + std::swap(row[1][i], row[2][i]); + row[3][i] = row[0][i]; + row[0][i] = kAlpha; + } +} + +void ChooseColorTransform(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + if (!CheckColorSpaceComponents(cinfo->input_components, + cinfo->in_color_space)) { + JPEGLI_ERROR("Invalid number of input components %d for colorspace %d", + cinfo->input_components, cinfo->in_color_space); + } + if (!CheckColorSpaceComponents(cinfo->num_components, + cinfo->jpeg_color_space)) { + JPEGLI_ERROR("Invalid number of components %d for colorspace %d", + cinfo->num_components, cinfo->jpeg_color_space); + } + if (cinfo->jpeg_color_space == cinfo->in_color_space) { + if (cinfo->num_components != cinfo->input_components) { + JPEGLI_ERROR("Input/output components mismatch: %d vs %d", + cinfo->input_components, cinfo->num_components); + } + // No color transform requested. + m->color_transform = NullTransform; + return; + } + + if (cinfo->in_color_space == JCS_RGB && m->xyb_mode) { + JPEGLI_ERROR("Color transform on XYB colorspace is not supported."); + } + + m->color_transform = nullptr; + if (cinfo->jpeg_color_space == JCS_GRAYSCALE) { + if (cinfo->in_color_space == JCS_RGB) { + m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr); + } else if (cinfo->in_color_space == JCS_YCbCr || + cinfo->in_color_space == JCS_YCCK) { + // Since the first luminance channel is the grayscale version of the + // image, nothing to do here + m->color_transform = NullTransform; + } + } else if (cinfo->jpeg_color_space == JCS_RGB) { + if (cinfo->in_color_space == JCS_GRAYSCALE) { + m->color_transform = GrayscaleToRGB; + } + } else if (cinfo->jpeg_color_space == JCS_YCbCr) { + if (cinfo->in_color_space == JCS_RGB) { + m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr); + } else if (cinfo->in_color_space == JCS_GRAYSCALE) { + m->color_transform = GrayscaleToYCbCr; + } + } else if (cinfo->jpeg_color_space == JCS_YCCK) { + if (cinfo->in_color_space == JCS_CMYK) { + m->color_transform = HWY_DYNAMIC_DISPATCH(CMYKToYCCK); + } + } + + if (cinfo->jpeg_color_space == JCS_GRAYSCALE || + cinfo->jpeg_color_space == JCS_YCbCr) { + switch (cinfo->in_color_space) { +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGB: + case JCS_EXT_RGBX: + m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr); + break; + case JCS_EXT_BGR: + case JCS_EXT_BGRX: + m->color_transform = HWY_DYNAMIC_DISPATCH(BGRToYCbCr); + break; + case JCS_EXT_XRGB: + m->color_transform = HWY_DYNAMIC_DISPATCH(ARGBToYCbCr); + break; + case JCS_EXT_XBGR: + m->color_transform = HWY_DYNAMIC_DISPATCH(ABGRToYCbCr); + break; +#endif +#ifdef JCS_ALPHA_EXTENSIONS + case JCS_EXT_RGBA: + m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr); + break; + case JCS_EXT_BGRA: + m->color_transform = HWY_DYNAMIC_DISPATCH(BGRToYCbCr); + break; + case JCS_EXT_ARGB: + m->color_transform = HWY_DYNAMIC_DISPATCH(ARGBToYCbCr); + break; + case JCS_EXT_ABGR: + m->color_transform = HWY_DYNAMIC_DISPATCH(ABGRToYCbCr); + break; +#endif + default:; // Nothing to do. + } + } + + if (m->color_transform == nullptr) { + // TODO(szabadka) Support more color transforms. + JPEGLI_ERROR("Unsupported color transform %d -> %d", cinfo->in_color_space, + cinfo->jpeg_color_space); + } +} + +void ChooseColorTransform(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + if (!CheckColorSpaceComponents(cinfo->out_color_components, + cinfo->out_color_space)) { + JPEGLI_ERROR("Invalid number of output components %d for colorspace %d", + cinfo->out_color_components, cinfo->out_color_space); + } + if (!CheckColorSpaceComponents(cinfo->num_components, + cinfo->jpeg_color_space)) { + JPEGLI_ERROR("Invalid number of components %d for colorspace %d", + cinfo->num_components, cinfo->jpeg_color_space); + } + if (cinfo->jpeg_color_space == cinfo->out_color_space) { + if (cinfo->num_components != cinfo->out_color_components) { + JPEGLI_ERROR("Input/output components mismatch: %d vs %d", + cinfo->num_components, cinfo->out_color_components); + } + // No color transform requested. + m->color_transform = NullTransform; + return; + } + + m->color_transform = nullptr; + if (cinfo->jpeg_color_space == JCS_GRAYSCALE) { + switch (cinfo->out_color_space) { + case JCS_RGB: + m->color_transform = GrayscaleToRGB; + break; +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGB: + case JCS_EXT_BGR: + m->color_transform = GrayscaleToRGB; + break; + case JCS_EXT_RGBX: + case JCS_EXT_BGRX: + m->color_transform = GrayscaleToRGBA; + break; + case JCS_EXT_XRGB: + case JCS_EXT_XBGR: + m->color_transform = GrayscaleToARGB; + break; +#endif +#ifdef JCS_ALPHA_EXTENSIONS + case JCS_EXT_RGBA: + case JCS_EXT_BGRA: + m->color_transform = GrayscaleToRGBA; + break; + case JCS_EXT_ARGB: + case JCS_EXT_ABGR: + m->color_transform = GrayscaleToARGB; + break; +#endif + default: + m->color_transform = nullptr; + } + } else if (cinfo->jpeg_color_space == JCS_RGB) { + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr); + break; +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGB: + m->color_transform = NullTransform; + break; + case JCS_EXT_BGR: + m->color_transform = RGBToBGR; + break; + case JCS_EXT_RGBX: + m->color_transform = RGBToRGBA; + break; + case JCS_EXT_BGRX: + m->color_transform = RGBToBGRA; + break; + case JCS_EXT_XRGB: + m->color_transform = RGBToARGB; + break; + case JCS_EXT_XBGR: + m->color_transform = RGBToABGR; + break; +#endif +#ifdef JCS_ALPHA_EXTENSIONS + case JCS_EXT_RGBA: + m->color_transform = RGBToRGBA; + break; + case JCS_EXT_BGRA: + m->color_transform = RGBToBGRA; + break; + case JCS_EXT_ARGB: + m->color_transform = RGBToARGB; + break; + case JCS_EXT_ABGR: + m->color_transform = RGBToABGR; + break; +#endif + default: + m->color_transform = nullptr; + } + } else if (cinfo->jpeg_color_space == JCS_YCbCr) { + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + m->color_transform = NullTransform; + break; + case JCS_RGB: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGB); + break; +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGB: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGB); + break; + case JCS_EXT_BGR: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToBGR); + break; + case JCS_EXT_RGBX: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGBA); + break; + case JCS_EXT_BGRX: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToBGRA); + break; + case JCS_EXT_XRGB: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToARGB); + break; + case JCS_EXT_XBGR: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToABGR); + break; +#endif +#ifdef JCS_ALPHA_EXTENSIONS + case JCS_EXT_RGBA: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGBA); + break; + case JCS_EXT_BGRA: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToBGRA); + break; + case JCS_EXT_ARGB: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToARGB); + break; + case JCS_EXT_ABGR: + m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToABGR); + break; +#endif + default: + m->color_transform = nullptr; + } + } else if (cinfo->jpeg_color_space == JCS_YCCK) { + if (cinfo->out_color_space == JCS_CMYK) { + m->color_transform = HWY_DYNAMIC_DISPATCH(YCCKToCMYK); + } + } + + if (m->color_transform == nullptr) { + // TODO(szabadka) Support more color transforms. + JPEGLI_ERROR("Unsupported color transform %d -> %d", + cinfo->jpeg_color_space, cinfo->out_color_space); + } +} + +} // namespace jpegli +#endif // HWY_ONCE diff --git a/media/libjxl/src/lib/jpegli/color_transform.h b/media/libjxl/src/lib/jpegli/color_transform.h new file mode 100644 index 0000000000..8d58f8849a --- /dev/null +++ b/media/libjxl/src/lib/jpegli/color_transform.h @@ -0,0 +1,20 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_JPEGLI_COLOR_TRANSFORM_H_ +#define LIB_JPEGLI_COLOR_TRANSFORM_H_ + +#include "lib/jpegli/common.h" +#include "lib/jxl/base/compiler_specific.h" + +namespace jpegli { + +void ChooseColorTransform(j_compress_ptr cinfo); + +void ChooseColorTransform(j_decompress_ptr cinfo); + +} // namespace jpegli + +#endif // LIB_JPEGLI_COLOR_TRANSFORM_H_ diff --git a/media/libjxl/src/lib/jpegli/common.cc b/media/libjxl/src/lib/jpegli/common.cc new file mode 100644 index 0000000000..5f34372f3e --- /dev/null +++ b/media/libjxl/src/lib/jpegli/common.cc @@ -0,0 +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 "lib/jpegli/common.h" + +#include "lib/jpegli/decode_internal.h" +#include "lib/jpegli/encode_internal.h" +#include "lib/jpegli/memory_manager.h" + +void jpegli_abort(j_common_ptr cinfo) { + if (cinfo->mem == nullptr) return; + for (int pool_id = 0; pool_id < JPOOL_NUMPOOLS; ++pool_id) { + if (pool_id == JPOOL_PERMANENT) continue; + (*cinfo->mem->free_pool)(cinfo, pool_id); + } + if (cinfo->is_decompressor) { + cinfo->global_state = jpegli::kDecStart; + } else { + cinfo->global_state = jpegli::kEncStart; + } +} + +void jpegli_destroy(j_common_ptr cinfo) { + if (cinfo->mem == nullptr) return; + (*cinfo->mem->self_destruct)(cinfo); + if (cinfo->is_decompressor) { + cinfo->global_state = jpegli::kDecNull; + delete reinterpret_cast(cinfo)->master; + } else { + cinfo->global_state = jpegli::kEncNull; + } +} + +JQUANT_TBL* jpegli_alloc_quant_table(j_common_ptr cinfo) { + JQUANT_TBL* table = jpegli::Allocate(cinfo, 1); + table->sent_table = FALSE; + return table; +} + +JHUFF_TBL* jpegli_alloc_huff_table(j_common_ptr cinfo) { + JHUFF_TBL* table = jpegli::Allocate(cinfo, 1); + table->sent_table = FALSE; + return table; +} + +int jpegli_bytes_per_sample(JpegliDataType data_type) { + switch (data_type) { + case JPEGLI_TYPE_UINT8: + return 1; + case JPEGLI_TYPE_UINT16: + return 2; + case JPEGLI_TYPE_FLOAT: + return 4; + default: + return 0; + } +} diff --git a/media/libjxl/src/lib/jpegli/common.h b/media/libjxl/src/lib/jpegli/common.h new file mode 100644 index 0000000000..731a2e9d3f --- /dev/null +++ b/media/libjxl/src/lib/jpegli/common.h @@ -0,0 +1,43 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// This file contains the C API of the common encoder/decoder part of libjpegli +// library, which is based on the C API of libjpeg, with the function names +// changed from jpeg_* to jpegli_*, while compressor and decompressor object +// definitions are included directly from jpeglib.h +// +// Applications can use the libjpegli library in one of the following ways: +// +// (1) Include jpegli/encode.h and/or jpegli/decode.h, update the function +// names of the API and link against libjpegli. +// +// (2) Leave the application code unchanged, but replace the libjpeg.so library +// with the one built by this project that is API- and ABI-compatible with +// libjpeg-turbo's version of libjpeg.so. + +#ifndef LIB_JPEGLI_COMMON_H_ +#define LIB_JPEGLI_COMMON_H_ + +#include "lib/jxl/base/include_jpeglib.h" // NOLINT + +#ifdef __cplusplus +extern "C" { +#endif + +struct jpeg_error_mgr* jpegli_std_error(struct jpeg_error_mgr* err); + +void jpegli_abort(j_common_ptr cinfo); + +void jpegli_destroy(j_common_ptr cinfo); + +JQUANT_TBL* jpegli_alloc_quant_table(j_common_ptr cinfo); + +JHUFF_TBL* jpegli_alloc_huff_table(j_common_ptr cinfo); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // LIB_JPEGLI_COMMON_H_ diff --git a/media/libjxl/src/lib/jpegli/common_internal.h b/media/libjxl/src/lib/jpegli/common_internal.h new file mode 100644 index 0000000000..bd0a86d3b0 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/common_internal.h @@ -0,0 +1,166 @@ +// 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_JPEGLI_COMMON_INTERNAL_H_ +#define LIB_JPEGLI_COMMON_INTERNAL_H_ + +#include +#include +#include + +// Suppress any -Wdeprecated-declarations warning that might be emitted by +// GCC or Clang by std::stable_sort in C++17 or later mode +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#elif defined(__GNUC__) +#pragma GCC push_options +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +#include + +#ifdef __clang__ +#pragma clang diagnostic pop +#elif defined(__GNUC__) +#pragma GCC pop_options +#endif + +#include + +#include "lib/jpegli/memory_manager.h" +#include "lib/jpegli/simd.h" +#include "lib/jxl/base/compiler_specific.h" // for ssize_t + +namespace jpegli { + +enum State { + kDecNull, + kDecStart, + kDecInHeader, + kDecHeaderDone, + kDecProcessMarkers, + kDecProcessScan, + kEncNull, + kEncStart, + kEncHeader, + kEncReadImage, + kEncWriteCoeffs, +}; + +template +constexpr inline T1 DivCeil(T1 a, T2 b) { + return (a + b - 1) / b; +} + +template +constexpr inline T1 RoundUpTo(T1 a, T2 b) { + return DivCeil(a, b) * b; +} + +constexpr size_t kDCTBlockSize = 64; +// This is set to the same value as MAX_COMPS_IN_SCAN, because that is the +// maximum number of channels the libjpeg-turbo decoder can decode. +constexpr int kMaxComponents = 4; +constexpr int kMaxQuantTables = 4; +constexpr int kJpegPrecision = 8; +constexpr int kMaxHuffmanTables = 4; +constexpr size_t kJpegHuffmanMaxBitLength = 16; +constexpr int kJpegHuffmanAlphabetSize = 256; +constexpr int kJpegDCAlphabetSize = 12; +constexpr int kMaxDHTMarkers = 512; +constexpr int kMaxDimPixels = 65535; +constexpr uint8_t kApp1 = 0xE1; +constexpr uint8_t kApp2 = 0xE2; +const uint8_t kIccProfileTag[12] = "ICC_PROFILE"; +const uint8_t kExifTag[6] = "Exif\0"; +const uint8_t kXMPTag[29] = "http://ns.adobe.com/xap/1.0/"; + +/* clang-format off */ +constexpr uint32_t kJPEGNaturalOrder[80] = { + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // extra entries for safety in decoder + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63 +}; + +constexpr uint32_t kJPEGZigZagOrder[64] = { + 0, 1, 5, 6, 14, 15, 27, 28, + 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, + 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, + 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, + 35, 36, 48, 49, 57, 58, 62, 63 +}; +/* clang-format on */ + +template +class RowBuffer { + public: + template + void Allocate(CInfoType cinfo, size_t num_rows, size_t rowsize) { + static_assert(sizeof(T) == 4); + size_t vec_size = std::max(VectorSize(), sizeof(T)); + size_t alignment = std::max(HWY_ALIGNMENT, vec_size); + size_t min_memstride = alignment + rowsize * sizeof(T) + vec_size; + size_t memstride = RoundUpTo(min_memstride, alignment); + xsize_ = rowsize; + ysize_ = num_rows; + stride_ = memstride / sizeof(T); + offset_ = alignment / sizeof(T); + data_ = ::jpegli::Allocate(cinfo, ysize_ * stride_, JPOOL_IMAGE_ALIGNED); + } + + T* Row(ssize_t y) const { + return &data_[((ysize_ + y) % ysize_) * stride_ + offset_]; + } + + size_t xsize() const { return xsize_; }; + size_t ysize() const { return ysize_; }; + size_t stride() const { return stride_; } + + void PadRow(size_t y, size_t from, int border) { + float* row = Row(y); + for (int offset = -border; offset < 0; ++offset) { + row[offset] = row[0]; + } + float last_val = row[from - 1]; + for (size_t x = from; x < xsize_ + border; ++x) { + row[x] = last_val; + } + } + + void CopyRow(ssize_t dst_row, ssize_t src_row, int border) { + memcpy(Row(dst_row) - border, Row(src_row) - border, + (xsize_ + 2 * border) * sizeof(T)); + } + + void FillRow(ssize_t y, T val, size_t len) { + T* row = Row(y); + for (size_t x = 0; x < len; ++x) { + row[x] = val; + } + } + + private: + size_t xsize_; + size_t ysize_; + size_t stride_; + size_t offset_; + T* data_; +}; + +} // namespace jpegli + +#endif // LIB_JPEGLI_COMMON_INTERNAL_H_ diff --git a/media/libjxl/src/lib/jpegli/dct-inl.h b/media/libjxl/src/lib/jpegli/dct-inl.h new file mode 100644 index 0000000000..fb54a152d5 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/dct-inl.h @@ -0,0 +1,260 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#if defined(LIB_JPEGLI_DCT_INL_H_) == defined(HWY_TARGET_TOGGLE) +#ifdef LIB_JPEGLI_DCT_INL_H_ +#undef LIB_JPEGLI_DCT_INL_H_ +#else +#define LIB_JPEGLI_DCT_INL_H_ +#endif + +#include "lib/jpegli/transpose-inl.h" +#include "lib/jxl/base/compiler_specific.h" + +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { +namespace { + +// These templates are not found via ADL. +using hwy::HWY_NAMESPACE::Abs; +using hwy::HWY_NAMESPACE::Add; +using hwy::HWY_NAMESPACE::DemoteTo; +using hwy::HWY_NAMESPACE::Ge; +using hwy::HWY_NAMESPACE::IfThenElseZero; +using hwy::HWY_NAMESPACE::Mul; +using hwy::HWY_NAMESPACE::MulAdd; +using hwy::HWY_NAMESPACE::Rebind; +using hwy::HWY_NAMESPACE::Round; +using hwy::HWY_NAMESPACE::Sub; +using hwy::HWY_NAMESPACE::Vec; + +using D = HWY_FULL(float); +using DI = HWY_FULL(int32_t); + +template +void AddReverse(const float* JXL_RESTRICT a_in1, + const float* JXL_RESTRICT a_in2, float* JXL_RESTRICT a_out) { + HWY_CAPPED(float, 8) d8; + for (size_t i = 0; i < N; i++) { + auto in1 = Load(d8, a_in1 + i * 8); + auto in2 = Load(d8, a_in2 + (N - i - 1) * 8); + Store(Add(in1, in2), d8, a_out + i * 8); + } +} + +template +void SubReverse(const float* JXL_RESTRICT a_in1, + const float* JXL_RESTRICT a_in2, float* JXL_RESTRICT a_out) { + HWY_CAPPED(float, 8) d8; + for (size_t i = 0; i < N; i++) { + auto in1 = Load(d8, a_in1 + i * 8); + auto in2 = Load(d8, a_in2 + (N - i - 1) * 8); + Store(Sub(in1, in2), d8, a_out + i * 8); + } +} + +template +void B(float* JXL_RESTRICT coeff) { + HWY_CAPPED(float, 8) d8; + constexpr float kSqrt2 = 1.41421356237f; + auto sqrt2 = Set(d8, kSqrt2); + auto in1 = Load(d8, coeff); + auto in2 = Load(d8, coeff + 8); + Store(MulAdd(in1, sqrt2, in2), d8, coeff); + for (size_t i = 1; i + 1 < N; i++) { + auto in1 = Load(d8, coeff + i * 8); + auto in2 = Load(d8, coeff + (i + 1) * 8); + Store(Add(in1, in2), d8, coeff + i * 8); + } +} + +// Ideally optimized away by compiler (except the multiply). +template +void InverseEvenOdd(const float* JXL_RESTRICT a_in, float* JXL_RESTRICT a_out) { + HWY_CAPPED(float, 8) d8; + for (size_t i = 0; i < N / 2; i++) { + auto in1 = Load(d8, a_in + i * 8); + Store(in1, d8, a_out + 2 * i * 8); + } + for (size_t i = N / 2; i < N; i++) { + auto in1 = Load(d8, a_in + i * 8); + Store(in1, d8, a_out + (2 * (i - N / 2) + 1) * 8); + } +} + +// Constants for DCT implementation. Generated by the following snippet: +// for i in range(N // 2): +// print(1.0 / (2 * math.cos((i + 0.5) * math.pi / N)), end=", ") +template +struct WcMultipliers; + +template <> +struct WcMultipliers<4> { + static constexpr float kMultipliers[] = { + 0.541196100146197, + 1.3065629648763764, + }; +}; + +template <> +struct WcMultipliers<8> { + static constexpr float kMultipliers[] = { + 0.5097955791041592, + 0.6013448869350453, + 0.8999762231364156, + 2.5629154477415055, + }; +}; + +#if JXL_CXX_LANG < JXL_CXX_17 +constexpr float WcMultipliers<4>::kMultipliers[]; +constexpr float WcMultipliers<8>::kMultipliers[]; +#endif + +// Invoked on full vector. +template +void Multiply(float* JXL_RESTRICT coeff) { + HWY_CAPPED(float, 8) d8; + for (size_t i = 0; i < N / 2; i++) { + auto in1 = Load(d8, coeff + (N / 2 + i) * 8); + auto mul = Set(d8, WcMultipliers::kMultipliers[i]); + Store(Mul(in1, mul), d8, coeff + (N / 2 + i) * 8); + } +} + +void LoadFromBlock(const float* JXL_RESTRICT pixels, size_t pixels_stride, + size_t off, float* JXL_RESTRICT coeff) { + HWY_CAPPED(float, 8) d8; + for (size_t i = 0; i < 8; i++) { + Store(LoadU(d8, pixels + i * pixels_stride + off), d8, coeff + i * 8); + } +} + +void StoreToBlockAndScale(const float* JXL_RESTRICT coeff, float* output, + size_t off) { + HWY_CAPPED(float, 8) d8; + auto mul = Set(d8, 1.0f / 8); + for (size_t i = 0; i < 8; i++) { + StoreU(Mul(mul, Load(d8, coeff + i * 8)), d8, output + i * 8 + off); + } +} + +template +struct DCT1DImpl; + +template <> +struct DCT1DImpl<1> { + JXL_INLINE void operator()(float* JXL_RESTRICT mem) {} +}; + +template <> +struct DCT1DImpl<2> { + JXL_INLINE void operator()(float* JXL_RESTRICT mem) { + HWY_CAPPED(float, 8) d8; + auto in1 = Load(d8, mem); + auto in2 = Load(d8, mem + 8); + Store(Add(in1, in2), d8, mem); + Store(Sub(in1, in2), d8, mem + 8); + } +}; + +template +struct DCT1DImpl { + void operator()(float* JXL_RESTRICT mem) { + HWY_ALIGN float tmp[N * 8]; + AddReverse(mem, mem + N * 4, tmp); + DCT1DImpl()(tmp); + SubReverse(mem, mem + N * 4, tmp + N * 4); + Multiply(tmp); + DCT1DImpl()(tmp + N * 4); + B(tmp + N * 4); + InverseEvenOdd(tmp, mem); + } +}; + +void DCT1D(const float* JXL_RESTRICT pixels, size_t pixels_stride, + float* JXL_RESTRICT output) { + HWY_CAPPED(float, 8) d8; + HWY_ALIGN float tmp[64]; + for (size_t i = 0; i < 8; i += Lanes(d8)) { + // TODO(veluca): consider removing the temporary memory here (as is done in + // IDCT), if it turns out that some compilers don't optimize away the loads + // and this is performance-critical. + LoadFromBlock(pixels, pixels_stride, i, tmp); + DCT1DImpl<8>()(tmp); + StoreToBlockAndScale(tmp, output, i); + } +} + +JXL_INLINE JXL_MAYBE_UNUSED void TransformFromPixels( + const float* JXL_RESTRICT pixels, size_t pixels_stride, + float* JXL_RESTRICT coefficients, float* JXL_RESTRICT scratch_space) { + DCT1D(pixels, pixels_stride, scratch_space); + Transpose8x8Block(scratch_space, coefficients); + DCT1D(coefficients, 8, scratch_space); + Transpose8x8Block(scratch_space, coefficients); +} + +JXL_INLINE JXL_MAYBE_UNUSED void StoreQuantizedValue(const Vec& ival, + int16_t* out) { + Rebind di16; + Store(DemoteTo(di16, ival), di16, out); +} + +JXL_INLINE JXL_MAYBE_UNUSED void StoreQuantizedValue(const Vec& ival, + int32_t* out) { + DI di; + Store(ival, di, out); +} + +template +void QuantizeBlock(const float* dct, const float* qmc, float aq_strength, + const float* zero_bias_offset, const float* zero_bias_mul, + T* block) { + D d; + DI di; + const auto aq_mul = Set(d, aq_strength); + for (size_t k = 0; k < DCTSIZE2; k += Lanes(d)) { + const auto val = Load(d, dct + k); + const auto q = Load(d, qmc + k); + const auto qval = Mul(val, q); + const auto zb_offset = Load(d, zero_bias_offset + k); + const auto zb_mul = Load(d, zero_bias_mul + k); + const auto threshold = Add(zb_offset, Mul(zb_mul, aq_mul)); + const auto nzero_mask = Ge(Abs(qval), threshold); + const auto ival = ConvertTo(di, IfThenElseZero(nzero_mask, Round(qval))); + StoreQuantizedValue(ival, block + k); + } +} + +template +void ComputeCoefficientBlock(const float* JXL_RESTRICT pixels, size_t stride, + const float* JXL_RESTRICT qmc, + int16_t last_dc_coeff, float aq_strength, + const float* zero_bias_offset, + const float* zero_bias_mul, + float* JXL_RESTRICT tmp, T* block) { + float* JXL_RESTRICT dct = tmp; + float* JXL_RESTRICT scratch_space = tmp + DCTSIZE2; + TransformFromPixels(pixels, stride, dct, scratch_space); + QuantizeBlock(dct, qmc, aq_strength, zero_bias_offset, zero_bias_mul, block); + // Center DC values around zero. + static constexpr float kDCBias = 128.0f; + const float dc = (dct[0] - kDCBias) * qmc[0]; + float dc_threshold = zero_bias_offset[0] + aq_strength * zero_bias_mul[0]; + if (std::abs(dc - last_dc_coeff) < dc_threshold) { + block[0] = last_dc_coeff; + } else { + block[0] = std::round(dc); + } +} + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); +#endif // LIB_JPEGLI_DCT_INL_H_ diff --git a/media/libjxl/src/lib/jpegli/decode.cc b/media/libjxl/src/lib/jpegli/decode.cc new file mode 100644 index 0000000000..832f0df876 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/decode.cc @@ -0,0 +1,1060 @@ +// 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/jpegli/decode.h" + +#include + +#include + +#include "lib/jpegli/color_quantize.h" +#include "lib/jpegli/decode_internal.h" +#include "lib/jpegli/decode_marker.h" +#include "lib/jpegli/decode_scan.h" +#include "lib/jpegli/error.h" +#include "lib/jpegli/memory_manager.h" +#include "lib/jpegli/render.h" +#include "lib/jxl/base/byte_order.h" +#include "lib/jxl/base/status.h" + +namespace jpegli { + +void InitializeImage(j_decompress_ptr cinfo) { + cinfo->restart_interval = 0; + cinfo->saw_JFIF_marker = FALSE; + cinfo->JFIF_major_version = 1; + cinfo->JFIF_minor_version = 1; + cinfo->density_unit = 0; + cinfo->X_density = 1; + cinfo->Y_density = 1; + cinfo->saw_Adobe_marker = FALSE; + cinfo->Adobe_transform = 0; + cinfo->CCIR601_sampling = FALSE; // not used + cinfo->marker_list = nullptr; + cinfo->comp_info = nullptr; + cinfo->input_scan_number = 0; + cinfo->input_iMCU_row = 0; + cinfo->output_scan_number = 0; + cinfo->output_iMCU_row = 0; + cinfo->output_scanline = 0; + cinfo->unread_marker = 0; + cinfo->coef_bits = nullptr; + // We set all these to zero since we don't yet support arithmetic coding. + memset(cinfo->arith_dc_L, 0, sizeof(cinfo->arith_dc_L)); + memset(cinfo->arith_dc_U, 0, sizeof(cinfo->arith_dc_U)); + memset(cinfo->arith_ac_K, 0, sizeof(cinfo->arith_ac_K)); + // Initialize the private fields. + jpeg_decomp_master* m = cinfo->master; + m->input_buffer_.clear(); + m->input_buffer_pos_ = 0; + m->codestream_bits_ahead_ = 0; + m->is_multiscan_ = false; + m->found_soi_ = false; + m->found_dri_ = false; + m->found_sof_ = false; + m->found_sos_ = false; + m->found_eoi_ = false; + m->icc_index_ = 0; + m->icc_total_ = 0; + m->icc_profile_.clear(); + memset(m->dc_huff_lut_, 0, sizeof(m->dc_huff_lut_)); + memset(m->ac_huff_lut_, 0, sizeof(m->ac_huff_lut_)); + // Initialize the values to an invalid symbol so that we can recognize it + // when reading the bit stream using a Huffman code with space > 0. + for (size_t i = 0; i < kAllHuffLutSize; ++i) { + m->dc_huff_lut_[i].bits = 0; + m->dc_huff_lut_[i].value = 0xffff; + m->ac_huff_lut_[i].bits = 0; + m->ac_huff_lut_[i].value = 0xffff; + } + m->colormap_lut_ = nullptr; + m->pixels_ = nullptr; + m->scanlines_ = nullptr; + m->regenerate_inverse_colormap_ = true; + for (int i = 0; i < kMaxComponents; ++i) { + m->dither_[i] = nullptr; + m->error_row_[i] = nullptr; + } + m->output_passes_done_ = 0; + m->xoffset_ = 0; + m->dequant_ = nullptr; +} + +void InitializeDecompressParams(j_decompress_ptr cinfo) { + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->out_color_space = JCS_UNKNOWN; + cinfo->scale_num = 1; + cinfo->scale_denom = 1; + cinfo->output_gamma = 0.0f; + cinfo->buffered_image = FALSE; + cinfo->raw_data_out = FALSE; + cinfo->dct_method = JDCT_DEFAULT; + cinfo->do_fancy_upsampling = TRUE; + cinfo->do_block_smoothing = TRUE; + cinfo->quantize_colors = FALSE; + cinfo->dither_mode = JDITHER_FS; + cinfo->two_pass_quantize = TRUE; + cinfo->desired_number_of_colors = 256; + cinfo->enable_1pass_quant = FALSE; + cinfo->enable_external_quant = FALSE; + cinfo->enable_2pass_quant = FALSE; + cinfo->actual_number_of_colors = 0; + cinfo->colormap = nullptr; +} + +void InitProgressMonitor(j_decompress_ptr cinfo, bool coef_only) { + if (!cinfo->progress) return; + jpeg_decomp_master* m = cinfo->master; + int nc = cinfo->num_components; + int estimated_num_scans = + cinfo->progressive_mode ? 2 + 3 * nc : (m->is_multiscan_ ? nc : 1); + cinfo->progress->pass_limit = cinfo->total_iMCU_rows * estimated_num_scans; + cinfo->progress->pass_counter = 0; + if (coef_only) { + cinfo->progress->total_passes = 1; + } else { + int input_passes = !cinfo->buffered_image && m->is_multiscan_ ? 1 : 0; + bool two_pass_quant = FROM_JXL_BOOL(cinfo->quantize_colors) && + (cinfo->colormap != nullptr) && + FROM_JXL_BOOL(cinfo->two_pass_quantize) && + FROM_JXL_BOOL(cinfo->enable_2pass_quant); + cinfo->progress->total_passes = input_passes + (two_pass_quant ? 2 : 1); + } + cinfo->progress->completed_passes = 0; +} + +void InitProgressMonitorForOutput(j_decompress_ptr cinfo) { + if (!cinfo->progress) return; + jpeg_decomp_master* m = cinfo->master; + int passes_per_output = cinfo->enable_2pass_quant ? 2 : 1; + int output_passes_left = cinfo->buffered_image && !m->found_eoi_ ? 2 : 1; + cinfo->progress->total_passes = + m->output_passes_done_ + passes_per_output * output_passes_left; + cinfo->progress->completed_passes = m->output_passes_done_; +} + +void ProgressMonitorInputPass(j_decompress_ptr cinfo) { + if (!cinfo->progress) return; + cinfo->progress->pass_counter = + ((cinfo->input_scan_number - 1) * cinfo->total_iMCU_rows + + cinfo->input_iMCU_row); + if (cinfo->progress->pass_counter > cinfo->progress->pass_limit) { + cinfo->progress->pass_limit = + cinfo->input_scan_number * cinfo->total_iMCU_rows; + } + (*cinfo->progress->progress_monitor)(reinterpret_cast(cinfo)); +} + +void ProgressMonitorOutputPass(j_decompress_ptr cinfo) { + if (!cinfo->progress) return; + jpeg_decomp_master* m = cinfo->master; + int input_passes = !cinfo->buffered_image && m->is_multiscan_ ? 1 : 0; + cinfo->progress->pass_counter = cinfo->output_scanline; + cinfo->progress->pass_limit = cinfo->output_height; + cinfo->progress->completed_passes = input_passes + m->output_passes_done_; + (*cinfo->progress->progress_monitor)(reinterpret_cast(cinfo)); +} + +void BuildHuffmanLookupTable(j_decompress_ptr cinfo, JHUFF_TBL* table, + HuffmanTableEntry* huff_lut) { + uint32_t counts[kJpegHuffmanMaxBitLength + 1] = {}; + counts[0] = 0; + int total_count = 0; + int space = 1 << kJpegHuffmanMaxBitLength; + int max_depth = 1; + for (size_t i = 1; i <= kJpegHuffmanMaxBitLength; ++i) { + int count = table->bits[i]; + if (count != 0) { + max_depth = i; + } + counts[i] = count; + total_count += count; + space -= count * (1 << (kJpegHuffmanMaxBitLength - i)); + } + uint32_t values[kJpegHuffmanAlphabetSize + 1] = {}; + uint8_t values_seen[256] = {0}; + for (int i = 0; i < total_count; ++i) { + int value = table->huffval[i]; + if (values_seen[value]) { + JPEGLI_ERROR("Duplicate Huffman code value %d", value); + } + values_seen[value] = 1; + values[i] = value; + } + // Add an invalid symbol that will have the all 1 code. + ++counts[max_depth]; + values[total_count] = kJpegHuffmanAlphabetSize; + space -= (1 << (kJpegHuffmanMaxBitLength - max_depth)); + if (space < 0) { + JPEGLI_ERROR("Invalid Huffman code lengths."); + } else if (space > 0 && huff_lut[0].value != 0xffff) { + // Re-initialize the values to an invalid symbol so that we can recognize + // it when reading the bit stream using a Huffman code with space > 0. + for (int i = 0; i < kJpegHuffmanLutSize; ++i) { + huff_lut[i].bits = 0; + huff_lut[i].value = 0xffff; + } + } + BuildJpegHuffmanTable(&counts[0], &values[0], huff_lut); +} + +void PrepareForScan(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + for (int i = 0; i < cinfo->comps_in_scan; ++i) { + int comp_idx = cinfo->cur_comp_info[i]->component_index; + int* prev_coef_bits = cinfo->coef_bits[comp_idx + cinfo->num_components]; + for (int k = std::min(cinfo->Ss, 1); k <= std::max(cinfo->Se, 9); k++) { + prev_coef_bits[k] = + (cinfo->input_scan_number > 0) ? cinfo->coef_bits[comp_idx][k] : 0; + } + for (int k = cinfo->Ss; k <= cinfo->Se; ++k) { + cinfo->coef_bits[comp_idx][k] = cinfo->Al; + } + } + AddStandardHuffmanTables(reinterpret_cast(cinfo), + /*is_dc=*/false); + AddStandardHuffmanTables(reinterpret_cast(cinfo), + /*is_dc=*/true); + // Check that all the Huffman tables needed for this scan are defined and + // build derived lookup tables. + for (int i = 0; i < cinfo->comps_in_scan; ++i) { + if (cinfo->Ss == 0) { + int dc_tbl_idx = cinfo->cur_comp_info[i]->dc_tbl_no; + JHUFF_TBL* table = cinfo->dc_huff_tbl_ptrs[dc_tbl_idx]; + HuffmanTableEntry* huff_lut = + &m->dc_huff_lut_[dc_tbl_idx * kJpegHuffmanLutSize]; + if (!table) { + JPEGLI_ERROR("DC Huffman table %d not found", dc_tbl_idx); + } + BuildHuffmanLookupTable(cinfo, table, huff_lut); + } + if (cinfo->Se > 0) { + int ac_tbl_idx = cinfo->cur_comp_info[i]->ac_tbl_no; + JHUFF_TBL* table = cinfo->ac_huff_tbl_ptrs[ac_tbl_idx]; + HuffmanTableEntry* huff_lut = + &m->ac_huff_lut_[ac_tbl_idx * kJpegHuffmanLutSize]; + if (!table) { + JPEGLI_ERROR("AC Huffman table %d not found", ac_tbl_idx); + } + BuildHuffmanLookupTable(cinfo, table, huff_lut); + } + } + // Copy quantization tables into comp_info. + for (int i = 0; i < cinfo->comps_in_scan; ++i) { + jpeg_component_info* comp = cinfo->cur_comp_info[i]; + int quant_tbl_idx = comp->quant_tbl_no; + JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[quant_tbl_idx]; + if (!quant_table) { + JPEGLI_ERROR("Quantization table with index %d not found", quant_tbl_idx); + } + if (comp->quant_table == nullptr) { + comp->quant_table = Allocate(cinfo, 1, JPOOL_IMAGE); + memcpy(comp->quant_table, quant_table, sizeof(JQUANT_TBL)); + } + } + if (cinfo->comps_in_scan == 1) { + const auto& comp = *cinfo->cur_comp_info[0]; + cinfo->MCUs_per_row = DivCeil(cinfo->image_width * comp.h_samp_factor, + cinfo->max_h_samp_factor * DCTSIZE); + cinfo->MCU_rows_in_scan = DivCeil(cinfo->image_height * comp.v_samp_factor, + cinfo->max_v_samp_factor * DCTSIZE); + m->mcu_rows_per_iMCU_row_ = cinfo->cur_comp_info[0]->v_samp_factor; + } else { + cinfo->MCU_rows_in_scan = cinfo->total_iMCU_rows; + cinfo->MCUs_per_row = m->iMCU_cols_; + m->mcu_rows_per_iMCU_row_ = 1; + size_t mcu_size = 0; + for (int i = 0; i < cinfo->comps_in_scan; ++i) { + jpeg_component_info* comp = cinfo->cur_comp_info[i]; + mcu_size += comp->h_samp_factor * comp->v_samp_factor; + } + if (mcu_size > D_MAX_BLOCKS_IN_MCU) { + JPEGLI_ERROR("MCU size too big"); + } + } + memset(m->last_dc_coeff_, 0, sizeof(m->last_dc_coeff_)); + m->restarts_to_go_ = cinfo->restart_interval; + m->next_restart_marker_ = 0; + m->eobrun_ = -1; + m->scan_mcu_row_ = 0; + m->scan_mcu_col_ = 0; + m->codestream_bits_ahead_ = 0; + ++cinfo->input_scan_number; + cinfo->input_iMCU_row = 0; + PrepareForiMCURow(cinfo); + cinfo->global_state = kDecProcessScan; +} + +int ConsumeInput(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + if (cinfo->global_state == kDecProcessScan && m->streaming_mode_ && + cinfo->input_iMCU_row > cinfo->output_iMCU_row) { + // Prevent input from getting ahead of output in streaming mode. + return JPEG_SUSPENDED; + } + jpeg_source_mgr* src = cinfo->src; + int status; + for (;;) { + const uint8_t* data; + size_t len; + if (m->input_buffer_.empty()) { + data = cinfo->src->next_input_byte; + len = cinfo->src->bytes_in_buffer; + } else { + data = &m->input_buffer_[m->input_buffer_pos_]; + len = m->input_buffer_.size() - m->input_buffer_pos_; + } + size_t pos = 0; + if (cinfo->global_state == kDecProcessScan) { + status = ProcessScan(cinfo, data, len, &pos, &m->codestream_bits_ahead_); + } else { + status = ProcessMarkers(cinfo, data, len, &pos); + } + if (m->input_buffer_.empty()) { + cinfo->src->next_input_byte += pos; + cinfo->src->bytes_in_buffer -= pos; + } else { + m->input_buffer_pos_ += pos; + size_t bytes_left = m->input_buffer_.size() - m->input_buffer_pos_; + if (bytes_left <= src->bytes_in_buffer) { + src->next_input_byte += (src->bytes_in_buffer - bytes_left); + src->bytes_in_buffer = bytes_left; + m->input_buffer_.clear(); + m->input_buffer_pos_ = 0; + } + } + if (status == kHandleRestart) { + JXL_DASSERT(m->input_buffer_.size() <= + m->input_buffer_pos_ + src->bytes_in_buffer); + m->input_buffer_.clear(); + m->input_buffer_pos_ = 0; + if (cinfo->unread_marker == 0xd0 + m->next_restart_marker_) { + cinfo->unread_marker = 0; + } else { + if (!(*cinfo->src->resync_to_restart)(cinfo, m->next_restart_marker_)) { + return JPEG_SUSPENDED; + } + } + m->next_restart_marker_ += 1; + m->next_restart_marker_ &= 0x7; + m->restarts_to_go_ = cinfo->restart_interval; + if (cinfo->unread_marker != 0) { + JPEGLI_WARN("Failed to resync to next restart marker, skipping scan."); + return JPEG_SCAN_COMPLETED; + } + continue; + } + if (status == kHandleMarkerProcessor) { + JXL_DASSERT(m->input_buffer_.size() <= + m->input_buffer_pos_ + src->bytes_in_buffer); + m->input_buffer_.clear(); + m->input_buffer_pos_ = 0; + if (!(*GetMarkerProcessor(cinfo))(cinfo)) { + return JPEG_SUSPENDED; + } + cinfo->unread_marker = 0; + continue; + } + if (status != kNeedMoreInput) { + break; + } + if (m->input_buffer_.empty()) { + JXL_DASSERT(m->input_buffer_pos_ == 0); + m->input_buffer_.assign(src->next_input_byte, + src->next_input_byte + src->bytes_in_buffer); + } + if (!(*cinfo->src->fill_input_buffer)(cinfo)) { + m->input_buffer_.clear(); + m->input_buffer_pos_ = 0; + return JPEG_SUSPENDED; + } + if (src->bytes_in_buffer == 0) { + JPEGLI_ERROR("Empty input."); + } + m->input_buffer_.insert(m->input_buffer_.end(), src->next_input_byte, + src->next_input_byte + src->bytes_in_buffer); + } + if (status == JPEG_SCAN_COMPLETED) { + cinfo->global_state = kDecProcessMarkers; + } else if (status == JPEG_REACHED_SOS) { + if (cinfo->global_state == kDecInHeader) { + cinfo->global_state = kDecHeaderDone; + } else { + PrepareForScan(cinfo); + } + } + return status; +} + +bool IsInputReady(j_decompress_ptr cinfo) { + if (cinfo->master->found_eoi_) { + return true; + } + if (cinfo->input_scan_number > cinfo->output_scan_number) { + return true; + } + if (cinfo->input_scan_number < cinfo->output_scan_number) { + return false; + } + if (cinfo->input_iMCU_row == cinfo->total_iMCU_rows) { + return true; + } + return cinfo->input_iMCU_row > + cinfo->output_iMCU_row + (cinfo->master->streaming_mode_ ? 0 : 2); +} + +bool ReadOutputPass(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + if (!m->pixels_) { + size_t stride = cinfo->out_color_components * cinfo->output_width; + size_t num_samples = cinfo->output_height * stride; + m->pixels_ = Allocate(cinfo, num_samples, JPOOL_IMAGE); + m->scanlines_ = + Allocate(cinfo, cinfo->output_height, JPOOL_IMAGE); + for (size_t i = 0; i < cinfo->output_height; ++i) { + m->scanlines_[i] = &m->pixels_[i * stride]; + } + } + size_t num_output_rows = 0; + while (num_output_rows < cinfo->output_height) { + if (IsInputReady(cinfo)) { + ProgressMonitorOutputPass(cinfo); + ProcessOutput(cinfo, &num_output_rows, m->scanlines_, + cinfo->output_height); + } else if (ConsumeInput(cinfo) == JPEG_SUSPENDED) { + return false; + } + } + cinfo->output_scanline = 0; + cinfo->output_iMCU_row = 0; + return true; +} + +boolean PrepareQuantizedOutput(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + if (cinfo->raw_data_out) { + JPEGLI_ERROR("Color quantization is not supported in raw data mode."); + } + if (m->output_data_type_ != JPEGLI_TYPE_UINT8) { + JPEGLI_ERROR("Color quantization must use 8-bit mode."); + } + if (cinfo->colormap) { + m->quant_mode_ = 3; + } else if (cinfo->two_pass_quantize && cinfo->enable_2pass_quant) { + m->quant_mode_ = 2; + } else if (cinfo->enable_1pass_quant) { + m->quant_mode_ = 1; + } else { + JPEGLI_ERROR("Invalid quantization mode change"); + } + if (m->quant_mode_ > 1 && cinfo->dither_mode == JDITHER_ORDERED) { + cinfo->dither_mode = JDITHER_FS; + } + if (m->quant_mode_ == 1) { + ChooseColorMap1Pass(cinfo); + } else if (m->quant_mode_ == 2) { + m->quant_pass_ = 0; + if (!ReadOutputPass(cinfo)) { + return FALSE; + } + ChooseColorMap2Pass(cinfo); + } + if (m->quant_mode_ == 2 || + (m->quant_mode_ == 3 && m->regenerate_inverse_colormap_)) { + CreateInverseColorMap(cinfo); + } + if (cinfo->dither_mode == JDITHER_ORDERED) { + CreateOrderedDitherTables(cinfo); + } else if (cinfo->dither_mode == JDITHER_FS) { + InitFSDitherState(cinfo); + } + m->quant_pass_ = 1; + return TRUE; +} + +void AllocateCoefficientBuffer(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + j_common_ptr comptr = reinterpret_cast(cinfo); + jvirt_barray_ptr* coef_arrays = jpegli::Allocate( + cinfo, cinfo->num_components, JPOOL_IMAGE); + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + size_t height_in_blocks = + m->streaming_mode_ ? comp->v_samp_factor : comp->height_in_blocks; + coef_arrays[c] = (*cinfo->mem->request_virt_barray)( + comptr, JPOOL_IMAGE, TRUE, comp->width_in_blocks, height_in_blocks, + comp->v_samp_factor); + } + cinfo->master->coef_arrays = coef_arrays; + (*cinfo->mem->realize_virt_arrays)(comptr); +} + +void AllocateOutputBuffers(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + size_t iMCU_width = cinfo->max_h_samp_factor * m->min_scaled_dct_size; + size_t output_stride = m->iMCU_cols_ * iMCU_width; + m->need_context_rows_ = false; + for (int c = 0; c < cinfo->num_components; ++c) { + if (cinfo->do_fancy_upsampling && m->v_factor[c] == 2) { + m->need_context_rows_ = true; + } + } + for (int c = 0; c < cinfo->num_components; ++c) { + const auto& comp = cinfo->comp_info[c]; + size_t cheight = comp.v_samp_factor * m->scaled_dct_size[c]; + int downsampled_width = output_stride / m->h_factor[c]; + m->raw_height_[c] = comp.height_in_blocks * m->scaled_dct_size[c]; + if (m->need_context_rows_) { + cheight *= 3; + } + m->raw_output_[c].Allocate(cinfo, cheight, downsampled_width); + } + int num_all_components = + std::max(cinfo->out_color_components, cinfo->num_components); + for (int c = 0; c < num_all_components; ++c) { + m->render_output_[c].Allocate(cinfo, cinfo->max_v_samp_factor, + output_stride); + } + m->idct_scratch_ = Allocate(cinfo, 5 * DCTSIZE2, JPOOL_IMAGE_ALIGNED); + // Padding for horizontal chroma upsampling. + constexpr size_t kPaddingLeft = 64; + constexpr size_t kPaddingRight = 64; + m->upsample_scratch_ = Allocate( + cinfo, output_stride + kPaddingLeft + kPaddingRight, JPOOL_IMAGE_ALIGNED); + size_t bytes_per_sample = jpegli_bytes_per_sample(m->output_data_type_); + size_t bytes_per_pixel = cinfo->out_color_components * bytes_per_sample; + size_t scratch_stride = RoundUpTo(output_stride, HWY_ALIGNMENT); + m->output_scratch_ = Allocate( + cinfo, bytes_per_pixel * scratch_stride, JPOOL_IMAGE_ALIGNED); + m->smoothing_scratch_ = + Allocate(cinfo, DCTSIZE2, JPOOL_IMAGE_ALIGNED); + size_t coeffs_per_block = cinfo->num_components * DCTSIZE2; + m->nonzeros_ = Allocate(cinfo, coeffs_per_block, JPOOL_IMAGE_ALIGNED); + m->sumabs_ = Allocate(cinfo, coeffs_per_block, JPOOL_IMAGE_ALIGNED); + m->biases_ = Allocate(cinfo, coeffs_per_block, JPOOL_IMAGE_ALIGNED); + m->dequant_ = Allocate(cinfo, coeffs_per_block, JPOOL_IMAGE_ALIGNED); + memset(m->dequant_, 0, coeffs_per_block * sizeof(float)); +} + +} // namespace jpegli + +void jpegli_CreateDecompress(j_decompress_ptr cinfo, int version, + size_t structsize) { + cinfo->mem = nullptr; + if (structsize != sizeof(*cinfo)) { + JPEGLI_ERROR("jpeg_decompress_struct has wrong size."); + } + jpegli::InitMemoryManager(reinterpret_cast(cinfo)); + cinfo->is_decompressor = TRUE; + cinfo->progress = nullptr; + cinfo->src = nullptr; + for (auto& quant_tbl_ptr : cinfo->quant_tbl_ptrs) { + quant_tbl_ptr = nullptr; + } + for (int i = 0; i < NUM_HUFF_TBLS; i++) { + cinfo->dc_huff_tbl_ptrs[i] = nullptr; + cinfo->ac_huff_tbl_ptrs[i] = nullptr; + } + cinfo->global_state = jpegli::kDecStart; + cinfo->sample_range_limit = nullptr; // not used + cinfo->rec_outbuf_height = 1; // output works with any buffer height + cinfo->master = new jpeg_decomp_master; + jpeg_decomp_master* m = cinfo->master; + for (auto& app_marker_parser : m->app_marker_parsers) { + app_marker_parser = nullptr; + } + m->com_marker_parser = nullptr; + memset(m->markers_to_save_, 0, sizeof(m->markers_to_save_)); + jpegli::InitializeDecompressParams(cinfo); + jpegli::InitializeImage(cinfo); +} + +void jpegli_destroy_decompress(j_decompress_ptr cinfo) { + jpegli_destroy(reinterpret_cast(cinfo)); +} + +void jpegli_abort_decompress(j_decompress_ptr cinfo) { + jpegli_abort(reinterpret_cast(cinfo)); +} + +void jpegli_save_markers(j_decompress_ptr cinfo, int marker_code, + unsigned int length_limit) { + // TODO(szabadka) Limit our memory usage by taking into account length_limit. + jpeg_decomp_master* m = cinfo->master; + if (marker_code < 0xe0) { + JPEGLI_ERROR("jpegli_save_markers: invalid marker code %d", marker_code); + } + m->markers_to_save_[marker_code - 0xe0] = 1; +} + +void jpegli_set_marker_processor(j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine) { + jpeg_decomp_master* m = cinfo->master; + if (marker_code == 0xfe) { + m->com_marker_parser = routine; + } else if (marker_code >= 0xe0 && marker_code <= 0xef) { + m->app_marker_parsers[marker_code - 0xe0] = routine; + } else { + JPEGLI_ERROR("jpegli_set_marker_processor: invalid marker code %d", + marker_code); + } +} + +int jpegli_consume_input(j_decompress_ptr cinfo) { + if (cinfo->global_state == jpegli::kDecStart) { + (*cinfo->err->reset_error_mgr)(reinterpret_cast(cinfo)); + (*cinfo->src->init_source)(cinfo); + jpegli::InitializeDecompressParams(cinfo); + jpegli::InitializeImage(cinfo); + cinfo->global_state = jpegli::kDecInHeader; + } + if (cinfo->global_state == jpegli::kDecHeaderDone) { + return JPEG_REACHED_SOS; + } + if (cinfo->master->found_eoi_) { + return JPEG_REACHED_EOI; + } + if (cinfo->global_state == jpegli::kDecInHeader || + cinfo->global_state == jpegli::kDecProcessMarkers || + cinfo->global_state == jpegli::kDecProcessScan) { + return jpegli::ConsumeInput(cinfo); + } + JPEGLI_ERROR("Unexpected state %d", cinfo->global_state); + return JPEG_REACHED_EOI; // return value does not matter +} + +int jpegli_read_header(j_decompress_ptr cinfo, boolean require_image) { + if (cinfo->global_state != jpegli::kDecStart && + cinfo->global_state != jpegli::kDecInHeader) { + JPEGLI_ERROR("jpegli_read_header: unexpected state %d", + cinfo->global_state); + } + if (cinfo->src == nullptr) { + JPEGLI_ERROR("Missing source."); + } + for (;;) { + int retcode = jpegli_consume_input(cinfo); + if (retcode == JPEG_SUSPENDED) { + return retcode; + } else if (retcode == JPEG_REACHED_SOS) { + break; + } else if (retcode == JPEG_REACHED_EOI) { + if (require_image) { + JPEGLI_ERROR("jpegli_read_header: unexpected EOI marker."); + } + jpegli_abort_decompress(cinfo); + return JPEG_HEADER_TABLES_ONLY; + } + }; + return JPEG_HEADER_OK; +} + +boolean jpegli_read_icc_profile(j_decompress_ptr cinfo, JOCTET** icc_data_ptr, + unsigned int* icc_data_len) { + if (cinfo->global_state == jpegli::kDecStart || + cinfo->global_state == jpegli::kDecInHeader) { + JPEGLI_ERROR("jpegli_read_icc_profile: unexpected state %d", + cinfo->global_state); + } + if (icc_data_ptr == nullptr || icc_data_len == nullptr) { + JPEGLI_ERROR("jpegli_read_icc_profile: invalid output buffer"); + } + jpeg_decomp_master* m = cinfo->master; + if (m->icc_profile_.empty()) { + *icc_data_ptr = nullptr; + *icc_data_len = 0; + return FALSE; + } + *icc_data_len = m->icc_profile_.size(); + *icc_data_ptr = static_cast(malloc(*icc_data_len)); + if (*icc_data_ptr == nullptr) { + JPEGLI_ERROR("jpegli_read_icc_profile: Out of memory"); + } + memcpy(*icc_data_ptr, m->icc_profile_.data(), *icc_data_len); + return TRUE; +} + +void jpegli_core_output_dimensions(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + if (!m->found_sof_) { + JPEGLI_ERROR("No SOF marker found."); + } + if (cinfo->raw_data_out) { + if (cinfo->scale_num != 1 || cinfo->scale_denom != 1) { + JPEGLI_ERROR("Output scaling is not supported in raw output mode"); + } + } + if (cinfo->scale_num != 1 || cinfo->scale_denom != 1) { + int dctsize = 16; + while (cinfo->scale_num * DCTSIZE <= cinfo->scale_denom * (dctsize - 1)) { + --dctsize; + } + m->min_scaled_dct_size = dctsize; + cinfo->output_width = + jpegli::DivCeil(cinfo->image_width * dctsize, DCTSIZE); + cinfo->output_height = + jpegli::DivCeil(cinfo->image_height * dctsize, DCTSIZE); + for (int c = 0; c < cinfo->num_components; ++c) { + m->scaled_dct_size[c] = m->min_scaled_dct_size; + } + } else { + cinfo->output_width = cinfo->image_width; + cinfo->output_height = cinfo->image_height; + m->min_scaled_dct_size = DCTSIZE; + for (int c = 0; c < cinfo->num_components; ++c) { + m->scaled_dct_size[c] = DCTSIZE; + } + } +} + +void jpegli_calc_output_dimensions(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + jpegli_core_output_dimensions(cinfo); + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + m->h_factor[c] = cinfo->max_h_samp_factor / comp->h_samp_factor; + m->v_factor[c] = cinfo->max_v_samp_factor / comp->v_samp_factor; + } + if (cinfo->scale_num != 1 || cinfo->scale_denom != 1) { + for (int c = 0; c < cinfo->num_components; ++c) { + // Prefer IDCT scaling over 2x upsampling. + while (m->scaled_dct_size[c] < DCTSIZE && (m->v_factor[c] % 2) == 0 && + (m->h_factor[c] % 2) == 0) { + m->scaled_dct_size[c] *= 2; + m->v_factor[c] /= 2; + m->h_factor[c] /= 2; + } + } + } + switch (cinfo->out_color_space) { + case JCS_GRAYSCALE: + cinfo->out_color_components = 1; + break; + case JCS_RGB: + case JCS_YCbCr: +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGB: + case JCS_EXT_BGR: +#endif + cinfo->out_color_components = 3; + break; + case JCS_CMYK: + case JCS_YCCK: +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGBX: + case JCS_EXT_BGRX: + case JCS_EXT_XBGR: + case JCS_EXT_XRGB: +#endif +#ifdef JCS_ALPHA_EXTENSIONS + case JCS_EXT_RGBA: + case JCS_EXT_BGRA: + case JCS_EXT_ABGR: + case JCS_EXT_ARGB: +#endif + cinfo->out_color_components = 4; + break; + default: + cinfo->out_color_components = cinfo->num_components; + } + cinfo->output_components = + cinfo->quantize_colors ? 1 : cinfo->out_color_components; + cinfo->rec_outbuf_height = 1; +} + +boolean jpegli_has_multiple_scans(j_decompress_ptr cinfo) { + if (cinfo->global_state != jpegli::kDecHeaderDone && + cinfo->global_state != jpegli::kDecProcessScan && + cinfo->global_state != jpegli::kDecProcessMarkers) { + JPEGLI_ERROR("jpegli_has_multiple_scans: unexpected state %d", + cinfo->global_state); + } + return TO_JXL_BOOL(cinfo->master->is_multiscan_); +} + +boolean jpegli_input_complete(j_decompress_ptr cinfo) { + return TO_JXL_BOOL(cinfo->master->found_eoi_); +} + +boolean jpegli_start_decompress(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + if (cinfo->global_state == jpegli::kDecHeaderDone) { + m->streaming_mode_ = !m->is_multiscan_ && + !FROM_JXL_BOOL(cinfo->buffered_image) && + (!FROM_JXL_BOOL(cinfo->quantize_colors) || + !FROM_JXL_BOOL(cinfo->two_pass_quantize)); + jpegli::AllocateCoefficientBuffer(cinfo); + jpegli_calc_output_dimensions(cinfo); + jpegli::PrepareForScan(cinfo); + if (cinfo->quantize_colors) { + if (cinfo->colormap != nullptr) { + cinfo->enable_external_quant = TRUE; + } else if (cinfo->two_pass_quantize && + cinfo->out_color_space == JCS_RGB) { + cinfo->enable_2pass_quant = TRUE; + } else { + cinfo->enable_1pass_quant = TRUE; + } + } + jpegli::InitProgressMonitor(cinfo, /*coef_only=*/false); + jpegli::AllocateOutputBuffers(cinfo); + if (cinfo->buffered_image == TRUE) { + cinfo->output_scan_number = 0; + return TRUE; + } + } else if (!m->is_multiscan_) { + JPEGLI_ERROR("jpegli_start_decompress: unexpected state %d", + cinfo->global_state); + } + if (m->is_multiscan_) { + if (cinfo->global_state != jpegli::kDecProcessScan && + cinfo->global_state != jpegli::kDecProcessMarkers) { + JPEGLI_ERROR("jpegli_start_decompress: unexpected state %d", + cinfo->global_state); + } + while (!m->found_eoi_) { + jpegli::ProgressMonitorInputPass(cinfo); + if (jpegli::ConsumeInput(cinfo) == JPEG_SUSPENDED) { + return FALSE; + } + } + } + cinfo->output_scan_number = cinfo->input_scan_number; + jpegli::PrepareForOutput(cinfo); + if (cinfo->quantize_colors) { + return jpegli::PrepareQuantizedOutput(cinfo); + } else { + return TRUE; + } +} + +boolean jpegli_start_output(j_decompress_ptr cinfo, int scan_number) { + jpeg_decomp_master* m = cinfo->master; + if (!cinfo->buffered_image) { + JPEGLI_ERROR("jpegli_start_output: buffered image mode was not set"); + } + if (cinfo->global_state != jpegli::kDecProcessScan && + cinfo->global_state != jpegli::kDecProcessMarkers) { + JPEGLI_ERROR("jpegli_start_output: unexpected state %d", + cinfo->global_state); + } + cinfo->output_scan_number = std::max(1, scan_number); + if (m->found_eoi_) { + cinfo->output_scan_number = + std::min(cinfo->output_scan_number, cinfo->input_scan_number); + } + jpegli::InitProgressMonitorForOutput(cinfo); + jpegli::PrepareForOutput(cinfo); + if (cinfo->quantize_colors) { + return jpegli::PrepareQuantizedOutput(cinfo); + } else { + return TRUE; + } +} + +boolean jpegli_finish_output(j_decompress_ptr cinfo) { + if (!cinfo->buffered_image) { + JPEGLI_ERROR("jpegli_finish_output: buffered image mode was not set"); + } + if (cinfo->global_state != jpegli::kDecProcessScan && + cinfo->global_state != jpegli::kDecProcessMarkers) { + JPEGLI_ERROR("jpegli_finish_output: unexpected state %d", + cinfo->global_state); + } + // Advance input to the start of the next scan, or to the end of input. + while (cinfo->input_scan_number <= cinfo->output_scan_number && + !cinfo->master->found_eoi_) { + if (jpegli::ConsumeInput(cinfo) == JPEG_SUSPENDED) { + return FALSE; + } + } + return TRUE; +} + +JDIMENSION jpegli_read_scanlines(j_decompress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION max_lines) { + jpeg_decomp_master* m = cinfo->master; + if (cinfo->global_state != jpegli::kDecProcessScan && + cinfo->global_state != jpegli::kDecProcessMarkers) { + JPEGLI_ERROR("jpegli_read_scanlines: unexpected state %d", + cinfo->global_state); + } + if (cinfo->buffered_image) { + if (cinfo->output_scan_number == 0) { + JPEGLI_ERROR( + "jpegli_read_scanlines: " + "jpegli_start_output() was not called"); + } + } else if (m->is_multiscan_ && !m->found_eoi_) { + JPEGLI_ERROR( + "jpegli_read_scanlines: " + "jpegli_start_decompress() did not finish"); + } + if (cinfo->output_scanline + max_lines > cinfo->output_height) { + max_lines = cinfo->output_height - cinfo->output_scanline; + } + jpegli::ProgressMonitorOutputPass(cinfo); + size_t num_output_rows = 0; + while (num_output_rows < max_lines) { + if (jpegli::IsInputReady(cinfo)) { + jpegli::ProcessOutput(cinfo, &num_output_rows, scanlines, max_lines); + } else if (jpegli::ConsumeInput(cinfo) == JPEG_SUSPENDED) { + break; + } + } + return num_output_rows; +} + +JDIMENSION jpegli_skip_scanlines(j_decompress_ptr cinfo, JDIMENSION num_lines) { + // TODO(szabadka) Skip the IDCT for skipped over blocks. + return jpegli_read_scanlines(cinfo, nullptr, num_lines); +} + +void jpegli_crop_scanline(j_decompress_ptr cinfo, JDIMENSION* xoffset, + JDIMENSION* width) { + jpeg_decomp_master* m = cinfo->master; + if ((cinfo->global_state != jpegli::kDecProcessScan && + cinfo->global_state != jpegli::kDecProcessMarkers) || + cinfo->output_scanline != 0) { + JPEGLI_ERROR("jpegli_crop_decompress: unexpected state %d", + cinfo->global_state); + } + if (cinfo->raw_data_out) { + JPEGLI_ERROR("Output cropping is not supported in raw data mode"); + } + if (xoffset == nullptr || width == nullptr || *width == 0 || + *xoffset + *width > cinfo->output_width) { + JPEGLI_ERROR("jpegli_crop_scanline: Invalid arguments"); + } + // TODO(szabadka) Skip the IDCT for skipped over blocks. + size_t xend = *xoffset + *width; + size_t iMCU_width = m->min_scaled_dct_size * cinfo->max_h_samp_factor; + *xoffset = (*xoffset / iMCU_width) * iMCU_width; + *width = xend - *xoffset; + cinfo->master->xoffset_ = *xoffset; + cinfo->output_width = *width; +} + +JDIMENSION jpegli_read_raw_data(j_decompress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION max_lines) { + if ((cinfo->global_state != jpegli::kDecProcessScan && + cinfo->global_state != jpegli::kDecProcessMarkers) || + !cinfo->raw_data_out) { + JPEGLI_ERROR("jpegli_read_raw_data: unexpected state %d", + cinfo->global_state); + } + size_t iMCU_height = cinfo->max_v_samp_factor * DCTSIZE; + if (max_lines < iMCU_height) { + JPEGLI_ERROR("jpegli_read_raw_data: output buffer too small"); + } + jpegli::ProgressMonitorOutputPass(cinfo); + while (!jpegli::IsInputReady(cinfo)) { + if (jpegli::ConsumeInput(cinfo) == JPEG_SUSPENDED) { + return 0; + } + } + if (cinfo->output_iMCU_row < cinfo->total_iMCU_rows) { + jpegli::ProcessRawOutput(cinfo, data); + return iMCU_height; + } + return 0; +} + +jvirt_barray_ptr* jpegli_read_coefficients(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + m->streaming_mode_ = false; + if (!cinfo->buffered_image && cinfo->global_state == jpegli::kDecHeaderDone) { + jpegli::AllocateCoefficientBuffer(cinfo); + jpegli_calc_output_dimensions(cinfo); + jpegli::InitProgressMonitor(cinfo, /*coef_only=*/true); + jpegli::PrepareForScan(cinfo); + } + if (cinfo->global_state != jpegli::kDecProcessScan && + cinfo->global_state != jpegli::kDecProcessMarkers) { + JPEGLI_ERROR("jpegli_read_coefficients: unexpected state %d", + cinfo->global_state); + } + if (!cinfo->buffered_image) { + while (!m->found_eoi_) { + jpegli::ProgressMonitorInputPass(cinfo); + if (jpegli::ConsumeInput(cinfo) == JPEG_SUSPENDED) { + return nullptr; + } + } + cinfo->output_scanline = cinfo->output_height; + } + return m->coef_arrays; +} + +boolean jpegli_finish_decompress(j_decompress_ptr cinfo) { + if (cinfo->global_state != jpegli::kDecProcessScan && + cinfo->global_state != jpegli::kDecProcessMarkers) { + JPEGLI_ERROR("jpegli_finish_decompress: unexpected state %d", + cinfo->global_state); + } + if (!cinfo->buffered_image && cinfo->output_scanline < cinfo->output_height) { + JPEGLI_ERROR("Incomplete output"); + } + while (!cinfo->master->found_eoi_) { + if (jpegli::ConsumeInput(cinfo) == JPEG_SUSPENDED) { + return FALSE; + } + } + (*cinfo->src->term_source)(cinfo); + jpegli_abort_decompress(cinfo); + return TRUE; +} + +boolean jpegli_resync_to_restart(j_decompress_ptr cinfo, int desired) { + JPEGLI_WARN("Invalid restart marker found: 0x%02x vs 0x%02x.", + cinfo->unread_marker, 0xd0 + desired); + // This is a trivial implementation, we just let the decoder skip the entire + // scan and attempt to render the partial input. + return TRUE; +} + +void jpegli_new_colormap(j_decompress_ptr cinfo) { + if (cinfo->global_state != jpegli::kDecProcessScan && + cinfo->global_state != jpegli::kDecProcessMarkers) { + JPEGLI_ERROR("jpegli_new_colormap: unexpected state %d", + cinfo->global_state); + } + if (!cinfo->buffered_image) { + JPEGLI_ERROR("jpegli_new_colormap: not in buffered image mode"); + } + if (!cinfo->enable_external_quant) { + JPEGLI_ERROR("external colormap quantizer was not enabled"); + } + if (!cinfo->quantize_colors || cinfo->colormap == nullptr) { + JPEGLI_ERROR("jpegli_new_colormap: not in external colormap mode"); + } + cinfo->master->regenerate_inverse_colormap_ = true; +} + +void jpegli_set_output_format(j_decompress_ptr cinfo, JpegliDataType data_type, + JpegliEndianness endianness) { + switch (data_type) { + case JPEGLI_TYPE_UINT8: + case JPEGLI_TYPE_UINT16: + case JPEGLI_TYPE_FLOAT: + cinfo->master->output_data_type_ = data_type; + break; + default: + JPEGLI_ERROR("Unsupported data type %d", data_type); + } + switch (endianness) { + case JPEGLI_NATIVE_ENDIAN: + cinfo->master->swap_endianness_ = false; + break; + case JPEGLI_LITTLE_ENDIAN: + cinfo->master->swap_endianness_ = !IsLittleEndian(); + break; + case JPEGLI_BIG_ENDIAN: + cinfo->master->swap_endianness_ = IsLittleEndian(); + break; + default: + JPEGLI_ERROR("Unsupported endianness %d", endianness); + } +} diff --git a/media/libjxl/src/lib/jpegli/decode.h b/media/libjxl/src/lib/jpegli/decode.h new file mode 100644 index 0000000000..78c39b409c --- /dev/null +++ b/media/libjxl/src/lib/jpegli/decode.h @@ -0,0 +1,107 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// This file contains the C API of the decoder part of the libjpegli library, +// which is based on the C API of libjpeg, with the function names changed from +// jpeg_* to jpegli_*, while decompressor object definitions are included +// directly from jpeglib.h +// +// Applications can use the libjpegli library in one of the following ways: +// +// (1) Include jpegli/encode.h and/or jpegli/decode.h, update the function +// names of the API and link against libjpegli. +// +// (2) Leave the application code unchanged, but replace the libjpeg.so library +// with the one built by this project that is API- and ABI-compatible with +// libjpeg-turbo's version of libjpeg.so. + +#ifndef LIB_JPEGLI_DECODE_H_ +#define LIB_JPEGLI_DECODE_H_ + +#include "lib/jpegli/common.h" +#include "lib/jpegli/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define jpegli_create_decompress(cinfo) \ + jpegli_CreateDecompress((cinfo), JPEG_LIB_VERSION, \ + (size_t)sizeof(struct jpeg_decompress_struct)) + +void jpegli_CreateDecompress(j_decompress_ptr cinfo, int version, + size_t structsize); + +void jpegli_stdio_src(j_decompress_ptr cinfo, FILE *infile); + +void jpegli_mem_src(j_decompress_ptr cinfo, const unsigned char *inbuffer, + unsigned long insize /* NOLINT */); + +int jpegli_read_header(j_decompress_ptr cinfo, boolean require_image); + +boolean jpegli_start_decompress(j_decompress_ptr cinfo); + +JDIMENSION jpegli_read_scanlines(j_decompress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION max_lines); + +JDIMENSION jpegli_skip_scanlines(j_decompress_ptr cinfo, JDIMENSION num_lines); + +void jpegli_crop_scanline(j_decompress_ptr cinfo, JDIMENSION *xoffset, + JDIMENSION *width); + +boolean jpegli_finish_decompress(j_decompress_ptr cinfo); + +JDIMENSION jpegli_read_raw_data(j_decompress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION max_lines); + +jvirt_barray_ptr *jpegli_read_coefficients(j_decompress_ptr cinfo); + +boolean jpegli_has_multiple_scans(j_decompress_ptr cinfo); + +boolean jpegli_start_output(j_decompress_ptr cinfo, int scan_number); + +boolean jpegli_finish_output(j_decompress_ptr cinfo); + +boolean jpegli_input_complete(j_decompress_ptr cinfo); + +int jpegli_consume_input(j_decompress_ptr cinfo); + +#if JPEG_LIB_VERSION >= 80 +void jpegli_core_output_dimensions(j_decompress_ptr cinfo); +#endif +void jpegli_calc_output_dimensions(j_decompress_ptr cinfo); + +void jpegli_save_markers(j_decompress_ptr cinfo, int marker_code, + unsigned int length_limit); + +void jpegli_set_marker_processor(j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine); + +boolean jpegli_resync_to_restart(j_decompress_ptr cinfo, int desired); + +boolean jpegli_read_icc_profile(j_decompress_ptr cinfo, JOCTET **icc_data_ptr, + unsigned int *icc_data_len); + +void jpegli_abort_decompress(j_decompress_ptr cinfo); + +void jpegli_destroy_decompress(j_decompress_ptr cinfo); + +void jpegli_new_colormap(j_decompress_ptr cinfo); + +// +// New API functions that are not available in libjpeg +// +// NOTE: This part of the API is still experimental and will probably change in +// the future. +// + +void jpegli_set_output_format(j_decompress_ptr cinfo, JpegliDataType data_type, + JpegliEndianness endianness); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // LIB_JPEGLI_DECODE_H_ diff --git a/media/libjxl/src/lib/jpegli/decode_api_test.cc b/media/libjxl/src/lib/jpegli/decode_api_test.cc new file mode 100644 index 0000000000..b7ce6bb4d7 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/decode_api_test.cc @@ -0,0 +1,1340 @@ +// 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 "lib/jpegli/decode.h" +#include "lib/jpegli/encode.h" +#include "lib/jpegli/libjpeg_test_util.h" +#include "lib/jpegli/test_params.h" +#include "lib/jpegli/test_utils.h" +#include "lib/jpegli/testing.h" +#include "lib/jpegli/types.h" +#include "lib/jxl/base/status.h" + +namespace jpegli { +namespace { + +constexpr uint8_t kFakeEoiMarker[2] = {0xff, 0xd9}; +constexpr size_t kNumSourceBuffers = 4; + +// Custom source manager that refills the input buffer in chunks, simulating +// a file reader with a fixed buffer size. +class SourceManager { + public: + SourceManager(const uint8_t* data, size_t len, size_t max_chunk_size) + : data_(data), len_(len), max_chunk_size_(max_chunk_size) { + pub_.skip_input_data = skip_input_data; + pub_.resync_to_restart = jpegli_resync_to_restart; + pub_.term_source = term_source; + pub_.init_source = init_source; + pub_.fill_input_buffer = fill_input_buffer; + if (max_chunk_size_ == 0) max_chunk_size_ = len; + buffers_.resize(kNumSourceBuffers, std::vector(max_chunk_size_)); + Reset(); + } + + void Reset() { + pub_.next_input_byte = nullptr; + pub_.bytes_in_buffer = 0; + pos_ = 0; + chunk_idx_ = 0; + } + + ~SourceManager() { + EXPECT_EQ(0, pub_.bytes_in_buffer); + EXPECT_EQ(len_, pos_); + } + + private: + jpeg_source_mgr pub_; + const uint8_t* data_; + size_t len_; + size_t chunk_idx_; + size_t pos_; + size_t max_chunk_size_; + std::vector> buffers_; + + static void init_source(j_decompress_ptr cinfo) {} + + static boolean fill_input_buffer(j_decompress_ptr cinfo) { + auto* src = reinterpret_cast(cinfo->src); + if (src->pos_ < src->len_) { + size_t chunk_size = std::min(src->len_ - src->pos_, src->max_chunk_size_); + size_t next_idx = ++src->chunk_idx_ % kNumSourceBuffers; + uint8_t* next_buffer = src->buffers_[next_idx].data(); + memcpy(next_buffer, src->data_ + src->pos_, chunk_size); + src->pub_.next_input_byte = next_buffer; + src->pub_.bytes_in_buffer = chunk_size; + } else { + src->pub_.next_input_byte = kFakeEoiMarker; + src->pub_.bytes_in_buffer = 2; + src->len_ += 2; + } + src->pos_ += src->pub_.bytes_in_buffer; + return TRUE; + } + + static void skip_input_data(j_decompress_ptr cinfo, + long num_bytes /* NOLINT */) { + auto* src = reinterpret_cast(cinfo->src); + if (num_bytes <= 0) { + return; + } + if (src->pub_.bytes_in_buffer >= static_cast(num_bytes)) { + src->pub_.bytes_in_buffer -= num_bytes; + src->pub_.next_input_byte += num_bytes; + } else { + src->pos_ += num_bytes - src->pub_.bytes_in_buffer; + src->pub_.bytes_in_buffer = 0; + } + } + + static void term_source(j_decompress_ptr cinfo) {} +}; + +uint8_t markers_seen[kMarkerSequenceLen]; +size_t num_markers_seen = 0; + +uint8_t get_next_byte(j_decompress_ptr cinfo) { + if (cinfo->src->bytes_in_buffer == 0) { + (*cinfo->src->fill_input_buffer)(cinfo); + } + cinfo->src->bytes_in_buffer--; + return *cinfo->src->next_input_byte++; +} + +boolean test_marker_processor(j_decompress_ptr cinfo) { + markers_seen[num_markers_seen] = cinfo->unread_marker; + size_t marker_len = (get_next_byte(cinfo) << 8) + get_next_byte(cinfo); + EXPECT_EQ(2 + ((num_markers_seen + 2) % sizeof(kMarkerData)), marker_len); + if (marker_len > 2) { + (*cinfo->src->skip_input_data)(cinfo, marker_len - 2); + } + ++num_markers_seen; + return TRUE; +} + +void ReadOutputImage(const DecompressParams& dparams, j_decompress_ptr cinfo, + TestImage* output) { + JDIMENSION xoffset = 0; + JDIMENSION yoffset = 0; + JDIMENSION xsize_cropped = cinfo->output_width; + JDIMENSION ysize_cropped = cinfo->output_height; + if (dparams.crop_output) { + xoffset = xsize_cropped = cinfo->output_width / 3; + yoffset = ysize_cropped = cinfo->output_height / 3; + jpegli_crop_scanline(cinfo, &xoffset, &xsize_cropped); + } + output->ysize = ysize_cropped; + output->xsize = cinfo->output_width; + output->components = cinfo->out_color_components; + output->data_type = dparams.data_type; + output->endianness = dparams.endianness; + size_t bytes_per_sample = jpegli_bytes_per_sample(dparams.data_type); + if (cinfo->raw_data_out) { + output->color_space = cinfo->jpeg_color_space; + for (int c = 0; c < cinfo->num_components; ++c) { + size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE; + size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE; + std::vector plane(ysize * xsize * bytes_per_sample); + output->raw_data.emplace_back(std::move(plane)); + } + } else { + output->color_space = cinfo->out_color_space; + output->AllocatePixels(); + } + size_t total_output_lines = 0; + while (cinfo->output_scanline < cinfo->output_height) { + size_t max_lines; + size_t num_output_lines; + if (cinfo->raw_data_out) { + size_t iMCU_height = cinfo->max_v_samp_factor * DCTSIZE; + EXPECT_EQ(cinfo->output_scanline, cinfo->output_iMCU_row * iMCU_height); + max_lines = iMCU_height; + std::vector> rowdata(cinfo->num_components); + std::vector data(cinfo->num_components); + for (int c = 0; c < cinfo->num_components; ++c) { + size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE; + size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE; + size_t num_lines = cinfo->comp_info[c].v_samp_factor * DCTSIZE; + rowdata[c].resize(num_lines); + size_t y0 = cinfo->output_iMCU_row * num_lines; + for (size_t i = 0; i < num_lines; ++i) { + rowdata[c][i] = + y0 + i < ysize ? &output->raw_data[c][(y0 + i) * xsize] : nullptr; + } + data[c] = rowdata[c].data(); + } + num_output_lines = jpegli_read_raw_data(cinfo, data.data(), max_lines); + } else { + size_t max_output_lines = dparams.max_output_lines; + if (max_output_lines == 0) max_output_lines = cinfo->output_height; + if (cinfo->output_scanline < yoffset) { + max_lines = yoffset - cinfo->output_scanline; + num_output_lines = jpegli_skip_scanlines(cinfo, max_lines); + } else if (cinfo->output_scanline >= yoffset + ysize_cropped) { + max_lines = cinfo->output_height - cinfo->output_scanline; + num_output_lines = jpegli_skip_scanlines(cinfo, max_lines); + } else { + size_t lines_left = yoffset + ysize_cropped - cinfo->output_scanline; + max_lines = std::min(max_output_lines, lines_left); + size_t stride = cinfo->output_width * cinfo->out_color_components * + bytes_per_sample; + std::vector scanlines(max_lines); + for (size_t i = 0; i < max_lines; ++i) { + size_t yidx = cinfo->output_scanline - yoffset + i; + scanlines[i] = &output->pixels[yidx * stride]; + } + num_output_lines = + jpegli_read_scanlines(cinfo, scanlines.data(), max_lines); + if (cinfo->quantize_colors) { + for (size_t i = 0; i < num_output_lines; ++i) { + UnmapColors(scanlines[i], cinfo->output_width, + cinfo->out_color_components, cinfo->colormap, + cinfo->actual_number_of_colors); + } + } + } + } + total_output_lines += num_output_lines; + EXPECT_EQ(total_output_lines, cinfo->output_scanline); + EXPECT_EQ(num_output_lines, max_lines); + } + EXPECT_EQ(cinfo->total_iMCU_rows, + DivCeil(cinfo->image_height, cinfo->max_v_samp_factor * DCTSIZE)); +} + +struct TestConfig { + std::string fn; + std::string fn_desc; + TestImage input; + CompressParams jparams; + DecompressParams dparams; + bool compare_to_orig = false; + float max_tolerance_factor = 1.01f; + float max_rms_dist = 1.0f; + float max_diff = 35.0f; +}; + +jxl::StatusOr> GetTestJpegData(TestConfig& config) { + std::vector compressed; + if (!config.fn.empty()) { + JXL_ASSIGN_OR_RETURN(compressed, ReadTestData(config.fn)); + } else { + GeneratePixels(&config.input); + JXL_RETURN_IF_ERROR( + EncodeWithJpegli(config.input, config.jparams, &compressed)); + } + if (config.dparams.size_factor < 1.0f) { + compressed.resize(compressed.size() * config.dparams.size_factor); + } + return compressed; +} + +void TestAPINonBuffered(const CompressParams& jparams, + const DecompressParams& dparams, + const TestImage& expected_output, + j_decompress_ptr cinfo, TestImage* output) { + if (jparams.add_marker) { + jpegli_save_markers(cinfo, kSpecialMarker0, 0xffff); + jpegli_save_markers(cinfo, kSpecialMarker1, 0xffff); + num_markers_seen = 0; + jpegli_set_marker_processor(cinfo, 0xe6, test_marker_processor); + jpegli_set_marker_processor(cinfo, 0xe7, test_marker_processor); + jpegli_set_marker_processor(cinfo, 0xe8, test_marker_processor); + } + if (!jparams.icc.empty()) { + jpegli_save_markers(cinfo, JPEG_APP0 + 2, 0xffff); + } + jpegli_read_header(cinfo, /*require_image=*/TRUE); + if (jparams.add_marker) { + EXPECT_EQ(num_markers_seen, kMarkerSequenceLen); + EXPECT_EQ(0, memcmp(markers_seen, kMarkerSequence, num_markers_seen)); + } + if (!jparams.icc.empty()) { + uint8_t* icc_data = nullptr; + unsigned int icc_len; + ASSERT_TRUE(jpegli_read_icc_profile(cinfo, &icc_data, &icc_len)); + ASSERT_TRUE(icc_data); + EXPECT_EQ(0, memcmp(jparams.icc.data(), icc_data, icc_len)); + free(icc_data); + } + // Check that jpegli_calc_output_dimensions can be called multiple times + // even with different parameters. + if (!cinfo->raw_data_out) { + cinfo->scale_num = 1; + cinfo->scale_denom = 2; + } + jpegli_calc_output_dimensions(cinfo); + SetDecompressParams(dparams, cinfo); + jpegli_set_output_format(cinfo, dparams.data_type, dparams.endianness); + VerifyHeader(jparams, cinfo); + jpegli_calc_output_dimensions(cinfo); + EXPECT_LE(expected_output.xsize, cinfo->output_width); + if (!dparams.crop_output) { + EXPECT_EQ(expected_output.xsize, cinfo->output_width); + } + if (dparams.output_mode == COEFFICIENTS) { + jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(cinfo); + ASSERT_TRUE(coef_arrays != nullptr); + CopyCoefficients(cinfo, coef_arrays, output); + } else { + jpegli_start_decompress(cinfo); + VerifyScanHeader(jparams, cinfo); + ReadOutputImage(dparams, cinfo, output); + } + jpegli_finish_decompress(cinfo); +} + +void TestAPIBuffered(const CompressParams& jparams, + const DecompressParams& dparams, j_decompress_ptr cinfo, + std::vector* output_progression) { + EXPECT_EQ(JPEG_REACHED_SOS, + jpegli_read_header(cinfo, /*require_image=*/TRUE)); + cinfo->buffered_image = TRUE; + SetDecompressParams(dparams, cinfo); + jpegli_set_output_format(cinfo, dparams.data_type, dparams.endianness); + VerifyHeader(jparams, cinfo); + bool has_multiple_scans = FROM_JXL_BOOL(jpegli_has_multiple_scans(cinfo)); + EXPECT_TRUE(jpegli_start_decompress(cinfo)); + // start decompress should not read the whole input in buffered image mode + EXPECT_FALSE(jpegli_input_complete(cinfo)); + EXPECT_EQ(0, cinfo->output_scan_number); + int sos_marker_cnt = 1; // read_header reads the first SOS marker + while (!jpegli_input_complete(cinfo)) { + EXPECT_EQ(cinfo->input_scan_number, sos_marker_cnt); + if (dparams.skip_scans && (cinfo->input_scan_number % 2) != 1) { + int result = JPEG_SUSPENDED; + while (result != JPEG_REACHED_SOS && result != JPEG_REACHED_EOI) { + result = jpegli_consume_input(cinfo); + } + if (result == JPEG_REACHED_SOS) ++sos_marker_cnt; + continue; + } + SetScanDecompressParams(dparams, cinfo, cinfo->input_scan_number); + EXPECT_TRUE(jpegli_start_output(cinfo, cinfo->input_scan_number)); + // start output sets output_scan_number, but does not change + // input_scan_number + EXPECT_EQ(cinfo->output_scan_number, cinfo->input_scan_number); + EXPECT_EQ(cinfo->input_scan_number, sos_marker_cnt); + VerifyScanHeader(jparams, cinfo); + TestImage output; + ReadOutputImage(dparams, cinfo, &output); + output_progression->emplace_back(std::move(output)); + // read scanlines/read raw data does not change input/output scan number + EXPECT_EQ(cinfo->input_scan_number, sos_marker_cnt); + EXPECT_EQ(cinfo->output_scan_number, cinfo->input_scan_number); + EXPECT_TRUE(jpegli_finish_output(cinfo)); + ++sos_marker_cnt; // finish output reads the next SOS marker or EOI + if (dparams.output_mode == COEFFICIENTS) { + jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(cinfo); + ASSERT_TRUE(coef_arrays != nullptr); + CopyCoefficients(cinfo, coef_arrays, &output_progression->back()); + } + } + jpegli_finish_decompress(cinfo); + if (dparams.size_factor == 1.0f) { + EXPECT_EQ(has_multiple_scans, cinfo->input_scan_number > 1); + } +} + +TEST(DecodeAPITest, ReuseCinfo) { + TestImage input; + TestImage output; + TestImage expected; + std::vector output_progression; + std::vector expected_output_progression; + CompressParams jparams; + DecompressParams dparams; + std::vector compressed; + jpeg_decompress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + input.xsize = 129; + input.ysize = 73; + GeneratePixels(&input); + for (int h_samp : {2, 1}) { + for (int v_samp : {2, 1}) { + for (int progr : {0, 2}) { + jparams.h_sampling = {h_samp, 1, 1}; + jparams.v_sampling = {v_samp, 1, 1}; + jparams.progressive_mode = progr; + printf( + "Generating input with %dx%d chroma subsampling " + "progressive level %d\n", + h_samp, v_samp, progr); + JPEGLI_TEST_ENSURE_TRUE( + EncodeWithJpegli(input, jparams, &compressed)); + for (JpegIOMode output_mode : {PIXELS, RAW_DATA, COEFFICIENTS}) { + for (bool crop : {true, false}) { + if (crop && output_mode != PIXELS) continue; + for (int scale_num : {1, 2, 3, 4, 7, 8, 13, 16}) { + if (scale_num != 8 && output_mode != PIXELS) continue; + int scale_denom = 8; + while (scale_num % 2 == 0 && scale_denom % 2 == 0) { + scale_num /= 2; + scale_denom /= 2; + } + printf("Decoding with output mode %d output scaling %d/%d %s\n", + output_mode, scale_num, scale_denom, + crop ? "with cropped output" : ""); + dparams.output_mode = output_mode; + dparams.scale_num = scale_num; + dparams.scale_denom = scale_denom; + expected.Clear(); + DecodeWithLibjpeg(jparams, dparams, compressed, &expected); + output.Clear(); + cinfo.buffered_image = JXL_FALSE; + cinfo.raw_data_out = JXL_FALSE; + cinfo.scale_num = cinfo.scale_denom = 1; + SourceManager src(compressed.data(), compressed.size(), + 1u << 12); + cinfo.src = reinterpret_cast(&src); + jpegli_read_header(&cinfo, /*require_image=*/TRUE); + jpegli_abort_decompress(&cinfo); + src.Reset(); + TestAPINonBuffered(jparams, dparams, expected, &cinfo, &output); + float max_rms = output_mode == COEFFICIENTS ? 0.0f : 1.0f; + if (scale_num == 1 && scale_denom == 8 && h_samp != v_samp) { + max_rms = 5.0f; // libjpeg does not do fancy upsampling + } + VerifyOutputImage(expected, output, max_rms); + printf("Decoding in buffered image mode\n"); + expected_output_progression.clear(); + DecodeAllScansWithLibjpeg(jparams, dparams, compressed, + &expected_output_progression); + output_progression.clear(); + src.Reset(); + TestAPIBuffered(jparams, dparams, &cinfo, &output_progression); + JPEGLI_TEST_ENSURE_TRUE(output_progression.size() == + expected_output_progression.size()); + for (size_t i = 0; i < output_progression.size(); ++i) { + const TestImage& output = output_progression[i]; + const TestImage& expected = expected_output_progression[i]; + VerifyOutputImage(expected, output, max_rms); + } + } + } + } + } + } + } + return true; + }; + ASSERT_TRUE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); +} + +std::vector GenerateBasicConfigs() { + std::vector all_configs; + for (int samp : {1, 2}) { + for (int progr : {0, 2}) { + TestConfig config; + config.input.xsize = 257 + samp * 37; + config.input.ysize = 265 + (progr / 2) * 17; + config.jparams.h_sampling = {samp, 1, 1}; + config.jparams.v_sampling = {samp, 1, 1}; + config.jparams.progressive_mode = progr; + GeneratePixels(&config.input); + all_configs.push_back(config); + } + } + return all_configs; +} + +TEST(DecodeAPITest, ReuseCinfoSameMemSource) { + std::vector all_configs = GenerateBasicConfigs(); + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + { + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + for (const TestConfig& config : all_configs) { + EncodeWithJpegli(config.input, config.jparams, &cinfo); + } + return true; + }; + EXPECT_TRUE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + } + std::vector all_outputs(all_configs.size()); + { + jpeg_decompress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + jpegli_mem_src(&cinfo, buffer, buffer_size); + for (size_t i = 0; i < all_configs.size(); ++i) { + TestAPINonBuffered(all_configs[i].jparams, DecompressParams(), + all_configs[i].input, &cinfo, &all_outputs[i]); + } + return true; + }; + EXPECT_TRUE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); + } + for (size_t i = 0; i < all_configs.size(); ++i) { + VerifyOutputImage(all_configs[i].input, all_outputs[i], 2.35f); + } + if (buffer) free(buffer); +} + +TEST(DecodeAPITest, ReuseCinfoSameStdSource) { + std::vector all_configs = GenerateBasicConfigs(); + FILE* tmpf = tmpfile(); + ASSERT_TRUE(tmpf); + { + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_stdio_dest(&cinfo, tmpf); + for (const TestConfig& config : all_configs) { + EncodeWithJpegli(config.input, config.jparams, &cinfo); + } + return true; + }; + EXPECT_TRUE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + } + fseek(tmpf, 0, SEEK_SET); + std::vector all_outputs(all_configs.size()); + { + jpeg_decompress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + jpegli_stdio_src(&cinfo, tmpf); + for (size_t i = 0; i < all_configs.size(); ++i) { + TestAPINonBuffered(all_configs[i].jparams, DecompressParams(), + all_configs[i].input, &cinfo, &all_outputs[i]); + } + return true; + }; + EXPECT_TRUE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); + } + for (size_t i = 0; i < all_configs.size(); ++i) { + VerifyOutputImage(all_configs[i].input, all_outputs[i], 2.35f); + } + fclose(tmpf); +} + +TEST(DecodeAPITest, AbbreviatedStreams) { + uint8_t* table_stream = nullptr; + unsigned long table_stream_size = 0; // NOLINT + uint8_t* data_stream = nullptr; + unsigned long data_stream_size = 0; // NOLINT + { + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &table_stream, &table_stream_size); + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + jpegli_set_defaults(&cinfo); + jpegli_write_tables(&cinfo); + jpegli_mem_dest(&cinfo, &data_stream, &data_stream_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.optimize_coding = FALSE; + jpegli_set_progressive_level(&cinfo, 0); + jpegli_start_compress(&cinfo, FALSE); + JSAMPLE image[3] = {0}; + JSAMPROW row[] = {image}; + jpegli_write_scanlines(&cinfo, row, 1); + jpegli_finish_compress(&cinfo); + return true; + }; + EXPECT_TRUE(try_catch_block()); + EXPECT_LT(data_stream_size, 50); + jpegli_destroy_compress(&cinfo); + } + { + jpeg_decompress_struct cinfo = {}; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + jpegli_mem_src(&cinfo, table_stream, table_stream_size); + jpegli_read_header(&cinfo, FALSE); + jpegli_mem_src(&cinfo, data_stream, data_stream_size); + jpegli_read_header(&cinfo, TRUE); + EXPECT_EQ(1, cinfo.image_width); + EXPECT_EQ(1, cinfo.image_height); + EXPECT_EQ(3, cinfo.num_components); + jpegli_start_decompress(&cinfo); + JSAMPLE image[3] = {0}; + JSAMPROW row[] = {image}; + jpegli_read_scanlines(&cinfo, row, 1); + EXPECT_EQ(0, image[0]); + EXPECT_EQ(0, image[1]); + EXPECT_EQ(0, image[2]); + jpegli_finish_decompress(&cinfo); + return true; + }; + EXPECT_TRUE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); + } + if (table_stream) free(table_stream); + if (data_stream) free(data_stream); +} + +class DecodeAPITestParam : public ::testing::TestWithParam {}; + +TEST_P(DecodeAPITestParam, TestAPI) { + TestConfig config = GetParam(); + const DecompressParams& dparams = config.dparams; + if (dparams.skip_scans) return; + JXL_ASSIGN_OR_QUIT(std::vector compressed, GetTestJpegData(config), + "Failed to create test data"); + SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size); + + TestImage output1; + DecodeWithLibjpeg(config.jparams, dparams, compressed, &output1); + + TestImage output0; + jpeg_decompress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + cinfo.src = reinterpret_cast(&src); + TestAPINonBuffered(config.jparams, dparams, output1, &cinfo, &output0); + return true; + }; + ASSERT_TRUE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); + + if (config.compare_to_orig) { + double rms0 = DistanceRms(config.input, output0); + double rms1 = DistanceRms(config.input, output1); + printf("rms: %f vs %f\n", rms0, rms1); + EXPECT_LE(rms0, rms1 * config.max_tolerance_factor); + } else { + VerifyOutputImage(output0, output1, config.max_rms_dist, config.max_diff); + } +} + +class DecodeAPITestParamBuffered : public ::testing::TestWithParam { +}; + +TEST_P(DecodeAPITestParamBuffered, TestAPI) { + TestConfig config = GetParam(); + const DecompressParams& dparams = config.dparams; + JXL_ASSIGN_OR_QUIT(std::vector compressed, GetTestJpegData(config), + "Failed to create test data."); + SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size); + + std::vector output_progression1; + DecodeAllScansWithLibjpeg(config.jparams, dparams, compressed, + &output_progression1); + + std::vector output_progression0; + jpeg_decompress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + cinfo.src = reinterpret_cast(&src); + TestAPIBuffered(config.jparams, dparams, &cinfo, &output_progression0); + return true; + }; + ASSERT_TRUE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); + + ASSERT_EQ(output_progression0.size(), output_progression1.size()); + for (size_t i = 0; i < output_progression0.size(); ++i) { + const TestImage& output = output_progression0[i]; + const TestImage& expected = output_progression1[i]; + if (config.compare_to_orig) { + double rms0 = DistanceRms(config.input, output); + double rms1 = DistanceRms(config.input, expected); + printf("rms: %f vs %f\n", rms0, rms1); + EXPECT_LE(rms0, rms1 * config.max_tolerance_factor); + } else { + VerifyOutputImage(expected, output, config.max_rms_dist, config.max_diff); + } + } +} + +std::vector GenerateTests(bool buffered) { + std::vector all_tests; + { + std::vector> testfiles({ + {"jxl/flower/flower.png.im_q85_420_progr.jpg", "Q85YUV420PROGR"}, + {"jxl/flower/flower.png.im_q85_420_R13B.jpg", "Q85YUV420R13B"}, + {"jxl/flower/flower.png.im_q85_444.jpg", "Q85YUV444"}, + }); + for (size_t i = 0; i < (buffered ? 1u : testfiles.size()); ++i) { + TestConfig config; + config.fn = testfiles[i].first; + config.fn_desc = testfiles[i].second; + for (size_t chunk_size : {0, 1, 64, 65536}) { + config.dparams.chunk_size = chunk_size; + for (size_t max_output_lines : {0, 1, 8, 16}) { + config.dparams.max_output_lines = max_output_lines; + config.dparams.output_mode = PIXELS; + all_tests.push_back(config); + } + { + config.dparams.max_output_lines = 16; + config.dparams.output_mode = RAW_DATA; + all_tests.push_back(config); + } + } + } + } + + { + std::vector> testfiles({ + {"jxl/flower/flower_small.q85_444_non_interleaved.jpg", + "Q85YUV444NonInterleaved"}, + {"jxl/flower/flower_small.q85_420_non_interleaved.jpg", + "Q85YUV420NonInterleaved"}, + {"jxl/flower/flower_small.q85_444_partially_interleaved.jpg", + "Q85YUV444PartiallyInterleaved"}, + {"jxl/flower/flower_small.q85_420_partially_interleaved.jpg", + "Q85YUV420PartiallyInterleaved"}, + {"jxl/flower/flower.png.im_q85_422.jpg", "Q85YUV422"}, + {"jxl/flower/flower.png.im_q85_440.jpg", "Q85YUV440"}, + {"jxl/flower/flower.png.im_q85_444_1x2.jpg", "Q85YUV444_1x2"}, + {"jxl/flower/flower.png.im_q85_asymmetric.jpg", "Q85Asymmetric"}, + {"jxl/flower/flower.png.im_q85_gray.jpg", "Q85Gray"}, + {"jxl/flower/flower.png.im_q85_luma_subsample.jpg", "Q85LumaSubsample"}, + {"jxl/flower/flower.png.im_q85_rgb.jpg", "Q85RGB"}, + {"jxl/flower/flower.png.im_q85_rgb_subsample_blue.jpg", + "Q85RGBSubsampleBlue"}, + {"jxl/flower/flower_small.cmyk.jpg", "CMYK"}, + }); + for (size_t i = 0; i < (buffered ? 4u : testfiles.size()); ++i) { + for (JpegIOMode output_mode : {PIXELS, RAW_DATA}) { + TestConfig config; + config.fn = testfiles[i].first; + config.fn_desc = testfiles[i].second; + config.dparams.output_mode = output_mode; + all_tests.push_back(config); + } + } + } + + // Tests for common chroma subsampling and output modes. + for (JpegIOMode output_mode : {PIXELS, RAW_DATA, COEFFICIENTS}) { + for (int h_samp : {1, 2}) { + for (int v_samp : {1, 2}) { + for (bool fancy : {true, false}) { + if (!fancy && (output_mode != PIXELS || h_samp * v_samp == 1)) { + continue; + } + TestConfig config; + config.dparams.output_mode = output_mode; + config.dparams.do_fancy_upsampling = fancy; + config.jparams.progressive_mode = 2; + config.jparams.h_sampling = {h_samp, 1, 1}; + config.jparams.v_sampling = {v_samp, 1, 1}; + if (output_mode == COEFFICIENTS) { + config.max_rms_dist = 0.0f; + } + all_tests.push_back(config); + } + } + } + } + + // Tests for partial input. + for (float size_factor : {0.1f, 0.33f, 0.5f, 0.75f}) { + for (int progr : {0, 1, 3}) { + for (int samp : {1, 2}) { + for (bool skip_scans : {false, true}) { + if (skip_scans && (progr != 1 || size_factor < 0.5f)) continue; + for (JpegIOMode output_mode : {PIXELS, RAW_DATA}) { + TestConfig config; + config.input.xsize = 517; + config.input.ysize = 523; + config.jparams.h_sampling = {samp, 1, 1}; + config.jparams.v_sampling = {samp, 1, 1}; + config.jparams.progressive_mode = progr; + config.dparams.size_factor = size_factor; + config.dparams.output_mode = output_mode; + config.dparams.skip_scans = skip_scans; + // The last partially available block can behave differently. + // TODO(szabadka) Figure out if we can make the behaviour more + // similar. + config.max_rms_dist = samp == 1 ? 1.75f : 3.0f; + config.max_diff = 255.0f; + all_tests.push_back(config); + } + } + } + } + } + + // Tests for block smoothing. + for (float size_factor : {0.1f, 0.33f, 0.5f, 0.75f, 1.0f}) { + for (int samp : {1, 2}) { + for (bool skip_scans : {false, true}) { + if (skip_scans && size_factor < 0.3f) continue; + TestConfig config; + config.input.xsize = 517; + config.input.ysize = 523; + config.jparams.h_sampling = {samp, 1, 1}; + config.jparams.v_sampling = {samp, 1, 1}; + config.jparams.progressive_mode = 2; + config.dparams.size_factor = size_factor; + config.dparams.do_block_smoothing = true; + config.dparams.skip_scans = skip_scans; + // libjpeg does smoothing for incomplete scans differently at + // the border between current and previous scans. + config.max_rms_dist = 8.0f; + config.max_diff = 255.0f; + all_tests.push_back(config); + } + } + } + + // Test for switching output color quantization modes between scans. + if (buffered) { + TestConfig config; + config.jparams.progressive_mode = 2; + config.dparams.quantize_colors = true; + config.dparams.scan_params = { + {3, JDITHER_NONE, CQUANT_1PASS}, {4, JDITHER_ORDERED, CQUANT_1PASS}, + {5, JDITHER_FS, CQUANT_1PASS}, {6, JDITHER_NONE, CQUANT_EXTERNAL}, + {8, JDITHER_NONE, CQUANT_REUSE}, {9, JDITHER_NONE, CQUANT_EXTERNAL}, + {10, JDITHER_NONE, CQUANT_2PASS}, {11, JDITHER_NONE, CQUANT_REUSE}, + {12, JDITHER_NONE, CQUANT_2PASS}, {13, JDITHER_FS, CQUANT_2PASS}, + }; + config.compare_to_orig = true; + config.max_tolerance_factor = 1.04f; + all_tests.push_back(config); + } + + if (buffered) { + return all_tests; + } + + // Tests for output color quantization. + for (int num_colors : {8, 64, 256}) { + for (ColorQuantMode mode : {CQUANT_1PASS, CQUANT_EXTERNAL, CQUANT_2PASS}) { + if (mode == CQUANT_EXTERNAL && num_colors != 256) continue; + for (J_DITHER_MODE dither : {JDITHER_NONE, JDITHER_ORDERED, JDITHER_FS}) { + if (mode == CQUANT_EXTERNAL && dither != JDITHER_NONE) continue; + if (mode != CQUANT_1PASS && dither == JDITHER_ORDERED) continue; + for (bool crop : {false, true}) { + for (bool scale : {false, true}) { + for (bool samp : {false, true}) { + if ((num_colors != 256) && (crop || scale || samp)) { + continue; + } + if (mode == CQUANT_2PASS && crop) continue; + TestConfig config; + config.input.xsize = 1024; + config.input.ysize = 768; + config.dparams.quantize_colors = true; + config.dparams.desired_number_of_colors = num_colors; + config.dparams.scan_params = {{kLastScan, dither, mode}}; + config.dparams.crop_output = crop; + if (scale) { + config.dparams.scale_num = 7; + config.dparams.scale_denom = 8; + } + if (samp) { + config.jparams.h_sampling = {2, 1, 1}; + config.jparams.v_sampling = {2, 1, 1}; + } + if (!scale && !crop) { + config.compare_to_orig = true; + if (dither != JDITHER_NONE) { + config.max_tolerance_factor = 1.05f; + } + if (mode == CQUANT_2PASS && + (num_colors == 8 || dither == JDITHER_FS)) { + // TODO(szabadka) Lower this bound. + config.max_tolerance_factor = 1.5f; + } + } else { + // We only test for buffer overflows, etc. + config.max_rms_dist = 100.0f; + config.max_diff = 255.0f; + } + all_tests.push_back(config); + } + } + } + } + } + } + + // Tests for output formats. + for (JpegliDataType type : + {JPEGLI_TYPE_UINT8, JPEGLI_TYPE_UINT16, JPEGLI_TYPE_FLOAT}) { + for (JpegliEndianness endianness : + {JPEGLI_NATIVE_ENDIAN, JPEGLI_LITTLE_ENDIAN, JPEGLI_BIG_ENDIAN}) { + if (type == JPEGLI_TYPE_UINT8 && endianness != JPEGLI_NATIVE_ENDIAN) { + continue; + } + for (int channels = 1; channels <= 4; ++channels) { + TestConfig config; + config.dparams.data_type = type; + config.dparams.endianness = endianness; + config.input.color_space = JCS_UNKNOWN; + config.input.components = channels; + config.dparams.set_out_color_space = true; + config.dparams.out_color_space = JCS_UNKNOWN; + all_tests.push_back(config); + } + } + } + // Test for output cropping. + { + TestConfig config; + config.dparams.crop_output = true; + all_tests.push_back(config); + } + // Tests for color transforms. + for (J_COLOR_SPACE out_color_space : + {JCS_RGB, JCS_GRAYSCALE, JCS_EXT_RGB, JCS_EXT_BGR, JCS_EXT_RGBA, + JCS_EXT_BGRA, JCS_EXT_ARGB, JCS_EXT_ABGR}) { + TestConfig config; + config.input.xsize = config.input.ysize = 256; + config.input.color_space = JCS_GRAYSCALE; + config.dparams.set_out_color_space = true; + config.dparams.out_color_space = out_color_space; + all_tests.push_back(config); + } + for (J_COLOR_SPACE jpeg_color_space : {JCS_RGB, JCS_YCbCr}) { + for (J_COLOR_SPACE out_color_space : + {JCS_RGB, JCS_YCbCr, JCS_GRAYSCALE, JCS_EXT_RGB, JCS_EXT_BGR, + JCS_EXT_RGBA, JCS_EXT_BGRA, JCS_EXT_ARGB, JCS_EXT_ABGR}) { + if (jpeg_color_space == JCS_RGB && out_color_space == JCS_YCbCr) continue; + TestConfig config; + config.input.xsize = config.input.ysize = 256; + config.jparams.set_jpeg_colorspace = true; + config.jparams.jpeg_color_space = jpeg_color_space; + config.dparams.set_out_color_space = true; + config.dparams.out_color_space = out_color_space; + all_tests.push_back(config); + } + } + for (J_COLOR_SPACE jpeg_color_space : {JCS_CMYK, JCS_YCCK}) { + for (J_COLOR_SPACE out_color_space : {JCS_CMYK, JCS_YCCK}) { + if (jpeg_color_space == JCS_CMYK && out_color_space == JCS_YCCK) continue; + TestConfig config; + config.input.xsize = config.input.ysize = 256; + config.input.color_space = JCS_CMYK; + config.jparams.set_jpeg_colorspace = true; + config.jparams.jpeg_color_space = jpeg_color_space; + config.dparams.set_out_color_space = true; + config.dparams.out_color_space = out_color_space; + all_tests.push_back(config); + } + } + // Tests for progressive levels. + for (int p = 0; p < 3 + NumTestScanScripts(); ++p) { + TestConfig config; + config.jparams.progressive_mode = p; + all_tests.push_back(config); + } + // Tests for RST markers. + for (size_t r : {1, 17, 1024}) { + for (size_t chunk_size : {1, 65536}) { + for (int progr : {0, 2}) { + TestConfig config; + config.dparams.chunk_size = chunk_size; + config.jparams.progressive_mode = progr; + config.jparams.restart_interval = r; + all_tests.push_back(config); + } + } + } + for (size_t rr : {1, 3, 8, 100}) { + TestConfig config; + config.jparams.restart_in_rows = rr; + all_tests.push_back(config); + } + // Tests for custom quantization tables. + for (int type : {0, 1, 10, 100, 10000}) { + for (int scale : {1, 50, 100, 200, 500}) { + for (bool add_raw : {false, true}) { + for (bool baseline : {true, false}) { + if (!baseline && (add_raw || type * scale < 25500)) continue; + TestConfig config; + config.input.xsize = 64; + config.input.ysize = 64; + CustomQuantTable table; + table.table_type = type; + table.scale_factor = scale; + table.force_baseline = baseline; + table.add_raw = add_raw; + table.Generate(); + config.jparams.quant_tables.push_back(table); + config.jparams.quant_indexes = {0, 0, 0}; + config.compare_to_orig = true; + config.max_tolerance_factor = 1.02; + all_tests.push_back(config); + } + } + } + } + for (int qidx = 0; qidx < 8; ++qidx) { + if (qidx == 3) continue; + TestConfig config; + config.input.xsize = 256; + config.input.ysize = 256; + config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1, + (qidx >> 0) & 1}; + all_tests.push_back(config); + } + for (int qidx = 0; qidx < 8; ++qidx) { + for (int slot_idx = 0; slot_idx < 2; ++slot_idx) { + if (qidx == 0 && slot_idx == 0) continue; + TestConfig config; + config.input.xsize = 256; + config.input.ysize = 256; + config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1, + (qidx >> 0) & 1}; + CustomQuantTable table; + table.slot_idx = slot_idx; + table.Generate(); + config.jparams.quant_tables.push_back(table); + all_tests.push_back(config); + } + } + for (int qidx = 0; qidx < 8; ++qidx) { + for (bool xyb : {false, true}) { + TestConfig config; + config.input.xsize = 256; + config.input.ysize = 256; + config.jparams.xyb_mode = xyb; + config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1, + (qidx >> 0) & 1}; + { + CustomQuantTable table; + table.slot_idx = 0; + table.Generate(); + config.jparams.quant_tables.push_back(table); + } + { + CustomQuantTable table; + table.slot_idx = 1; + table.table_type = 20; + table.Generate(); + config.jparams.quant_tables.push_back(table); + } + config.compare_to_orig = true; + all_tests.push_back(config); + } + } + for (bool xyb : {false, true}) { + TestConfig config; + config.input.xsize = 256; + config.input.ysize = 256; + config.jparams.xyb_mode = xyb; + config.jparams.quant_indexes = {0, 1, 2}; + { + CustomQuantTable table; + table.slot_idx = 0; + table.Generate(); + config.jparams.quant_tables.push_back(table); + } + { + CustomQuantTable table; + table.slot_idx = 1; + table.table_type = 20; + table.Generate(); + config.jparams.quant_tables.push_back(table); + } + { + CustomQuantTable table; + table.slot_idx = 2; + table.table_type = 30; + table.Generate(); + config.jparams.quant_tables.push_back(table); + } + config.compare_to_orig = true; + all_tests.push_back(config); + } + // Tests for fixed (and custom) prefix codes. + for (J_COLOR_SPACE jpeg_color_space : {JCS_RGB, JCS_YCbCr}) { + for (bool flat_dc_luma : {false, true}) { + TestConfig config; + config.jparams.set_jpeg_colorspace = true; + config.jparams.jpeg_color_space = jpeg_color_space; + config.jparams.progressive_mode = 0; + config.jparams.optimize_coding = 0; + config.jparams.use_flat_dc_luma_code = flat_dc_luma; + all_tests.push_back(config); + } + } + for (J_COLOR_SPACE jpeg_color_space : {JCS_CMYK, JCS_YCCK}) { + for (bool flat_dc_luma : {false, true}) { + TestConfig config; + config.input.color_space = JCS_CMYK; + config.jparams.set_jpeg_colorspace = true; + config.jparams.jpeg_color_space = jpeg_color_space; + config.jparams.progressive_mode = 0; + config.jparams.optimize_coding = 0; + config.jparams.use_flat_dc_luma_code = flat_dc_luma; + all_tests.push_back(config); + } + } + // Test for jpeg without DHT marker. + { + TestConfig config; + config.jparams.progressive_mode = 0; + config.jparams.optimize_coding = 0; + config.jparams.omit_standard_tables = true; + all_tests.push_back(config); + } + // Test for custom component ids. + { + TestConfig config; + config.input.xsize = config.input.ysize = 128; + config.jparams.comp_ids = {7, 17, 177}; + all_tests.push_back(config); + } + // Tests for JFIF/Adobe markers. + for (int override_JFIF : {-1, 0, 1}) { + for (int override_Adobe : {-1, 0, 1}) { + if (override_JFIF == -1 && override_Adobe == -1) continue; + TestConfig config; + config.input.xsize = config.input.ysize = 128; + config.jparams.override_JFIF = override_JFIF; + config.jparams.override_Adobe = override_Adobe; + all_tests.push_back(config); + } + } + // Tests for small images. + for (int xsize : {1, 7, 8, 9, 15, 16, 17}) { + for (int ysize : {1, 7, 8, 9, 15, 16, 17}) { + TestConfig config; + config.input.xsize = xsize; + config.input.ysize = ysize; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; + all_tests.push_back(config); + } + } + // Tests for custom marker processor. + for (size_t chunk_size : {0, 1, 64, 65536}) { + TestConfig config; + config.input.xsize = config.input.ysize = 256; + config.dparams.chunk_size = chunk_size; + config.jparams.add_marker = true; + all_tests.push_back(config); + } + // Tests for icc profile decoding. + for (size_t icc_size : {728, 70000, 1000000}) { + TestConfig config; + config.input.xsize = config.input.ysize = 256; + config.jparams.icc.resize(icc_size); + for (size_t i = 0; i < icc_size; ++i) { + config.jparams.icc[i] = (i * 17) & 0xff; + } + all_tests.push_back(config); + } + // Tests for unusual sampling factors. + for (int h0_samp : {1, 2, 3, 4}) { + for (int v0_samp : {1, 2, 3, 4}) { + for (int dxb = 0; dxb < h0_samp; ++dxb) { + for (int dyb = 0; dyb < v0_samp; ++dyb) { + for (int dx = 0; dx < 2; ++dx) { + for (int dy = 0; dy < 2; ++dy) { + TestConfig config; + config.input.xsize = 128 + dyb * 8 + dy; + config.input.ysize = 256 + dxb * 8 + dx; + config.jparams.progressive_mode = 2; + config.jparams.h_sampling = {h0_samp, 1, 1}; + config.jparams.v_sampling = {v0_samp, 1, 1}; + config.compare_to_orig = true; + all_tests.push_back(config); + } + } + } + } + } + } + for (int h0_samp : {1, 2, 4}) { + for (int v0_samp : {1, 2, 4}) { + for (int h2_samp : {1, 2, 4}) { + for (int v2_samp : {1, 2, 4}) { + TestConfig config; + config.input.xsize = 137; + config.input.ysize = 75; + config.jparams.progressive_mode = 2; + config.jparams.h_sampling = {h0_samp, 1, h2_samp}; + config.jparams.v_sampling = {v0_samp, 1, v2_samp}; + config.compare_to_orig = true; + all_tests.push_back(config); + } + } + } + } + { + TestConfig config; + config.input.xsize = 137; + config.input.ysize = 80; + config.jparams.progressive_mode = 0; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {4, 2, 1}; + config.compare_to_orig = true; + all_tests.push_back(config); + } + for (int h0_samp : {1, 3}) { + for (int v0_samp : {1, 3}) { + for (int h2_samp : {1, 3}) { + for (int v2_samp : {1, 3}) { + TestConfig config; + config.input.xsize = 205; + config.input.ysize = 99; + config.jparams.progressive_mode = 2; + config.jparams.h_sampling = {h0_samp, 1, h2_samp}; + config.jparams.v_sampling = {v0_samp, 1, v2_samp}; + all_tests.push_back(config); + } + } + } + } + // Tests for output scaling. + for (int scale_num = 1; scale_num <= 16; ++scale_num) { + if (scale_num == 8) continue; + for (bool crop : {false, true}) { + for (int samp : {1, 2}) { + for (int progr : {0, 2}) { + TestConfig config; + config.jparams.h_sampling = {samp, 1, 1}; + config.jparams.v_sampling = {samp, 1, 1}; + config.jparams.progressive_mode = progr; + config.dparams.scale_num = scale_num; + config.dparams.scale_denom = 8; + config.dparams.crop_output = crop; + all_tests.push_back(config); + } + } + } + } + return all_tests; +} + +std::string QuantMode(ColorQuantMode mode) { + switch (mode) { + case CQUANT_1PASS: + return "1pass"; + case CQUANT_EXTERNAL: + return "External"; + case CQUANT_2PASS: + return "2pass"; + case CQUANT_REUSE: + return "Reuse"; + } + return ""; +} + +std::string DitherMode(J_DITHER_MODE mode) { + switch (mode) { + case JDITHER_NONE: + return "No"; + case JDITHER_ORDERED: + return "Ordered"; + case JDITHER_FS: + return "FS"; + } + return ""; +} + +std::ostream& operator<<(std::ostream& os, const DecompressParams& dparams) { + if (dparams.chunk_size == 0) { + os << "CompleteInput"; + } else { + os << "InputChunks" << dparams.chunk_size; + } + if (dparams.size_factor < 1.0f) { + os << "Partial" << static_cast(dparams.size_factor * 100) << "p"; + } + if (dparams.max_output_lines == 0) { + os << "CompleteOutput"; + } else { + os << "OutputLines" << dparams.max_output_lines; + } + if (dparams.output_mode == RAW_DATA) { + os << "RawDataOut"; + } else if (dparams.output_mode == COEFFICIENTS) { + os << "CoeffsOut"; + } + os << IOMethodName(dparams.data_type, dparams.endianness); + if (dparams.set_out_color_space) { + os << "OutColor" + << ColorSpaceName(static_cast(dparams.out_color_space)); + } + if (dparams.crop_output) { + os << "Crop"; + } + if (dparams.do_block_smoothing) { + os << "BlockSmoothing"; + } + if (!dparams.do_fancy_upsampling) { + os << "NoFancyUpsampling"; + } + if (dparams.scale_num != 1 || dparams.scale_denom != 1) { + os << "Scale" << dparams.scale_num << "_" << dparams.scale_denom; + } + if (dparams.quantize_colors) { + os << "Quant" << dparams.desired_number_of_colors << "colors"; + for (size_t i = 0; i < dparams.scan_params.size(); ++i) { + if (i > 0) os << "_"; + const auto& sparam = dparams.scan_params[i]; + os << QuantMode(sparam.color_quant_mode); + os << DitherMode(static_cast(sparam.dither_mode)) + << "Dither"; + } + } + if (dparams.skip_scans) { + os << "SkipScans"; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, const TestConfig& c) { + if (!c.fn.empty()) { + os << c.fn_desc; + } else { + os << c.input; + } + os << c.jparams; + os << c.dparams; + return os; +} + +std::string TestDescription(const testing::TestParamInfo& info) { + std::stringstream name; + name << info.param; + return name.str(); +} + +JPEGLI_INSTANTIATE_TEST_SUITE_P(DecodeAPITest, DecodeAPITestParam, + testing::ValuesIn(GenerateTests(false)), + TestDescription); + +JPEGLI_INSTANTIATE_TEST_SUITE_P(DecodeAPITestBuffered, + DecodeAPITestParamBuffered, + testing::ValuesIn(GenerateTests(true)), + TestDescription); + +} // namespace +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/decode_internal.h b/media/libjxl/src/lib/jpegli/decode_internal.h new file mode 100644 index 0000000000..c36f6774b9 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/decode_internal.h @@ -0,0 +1,156 @@ +// 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_JPEGLI_DECODE_INTERNAL_H_ +#define LIB_JPEGLI_DECODE_INTERNAL_H_ + +#include + +#include +#include + +#include "lib/jpegli/common.h" +#include "lib/jpegli/common_internal.h" +#include "lib/jpegli/huffman.h" +#include "lib/jpegli/types.h" + +namespace jpegli { + +static constexpr int kNeedMoreInput = 100; +static constexpr int kHandleRestart = 101; +static constexpr int kHandleMarkerProcessor = 102; +static constexpr int kProcessNextMarker = 103; +static constexpr size_t kAllHuffLutSize = NUM_HUFF_TBLS * kJpegHuffmanLutSize; + +typedef int16_t coeff_t; + +// State of the decoder that has to be saved before decoding one MCU in case +// we run out of the bitstream. +struct MCUCodingState { + coeff_t last_dc_coeff[kMaxComponents]; + int eobrun; + coeff_t coeffs[D_MAX_BLOCKS_IN_MCU * DCTSIZE2]; +}; + +} // namespace jpegli + +// Use this forward-declared libjpeg struct to hold all our private variables. +// TODO(szabadka) Remove variables that have a corresponding version in cinfo. +struct jpeg_decomp_master { + // + // Input handling state. + // + std::vector input_buffer_; + size_t input_buffer_pos_; + // Number of bits after codestream_pos_ that were already processed. + size_t codestream_bits_ahead_; + + // Coefficient buffers + jvirt_barray_ptr* coef_arrays; + JBLOCKARRAY coeff_rows[jpegli::kMaxComponents]; + + bool streaming_mode_; + + // + // Marker data processing state. + // + bool found_soi_; + bool found_dri_; + bool found_sof_; + bool found_sos_; + bool found_eoi_; + + // Whether this jpeg has multiple scans (progressive or non-interleaved + // sequential). + bool is_multiscan_; + + size_t icc_index_; + size_t icc_total_; + std::vector icc_profile_; + jpegli::HuffmanTableEntry dc_huff_lut_[jpegli::kAllHuffLutSize]; + jpegli::HuffmanTableEntry ac_huff_lut_[jpegli::kAllHuffLutSize]; + uint8_t markers_to_save_[32]; + jpeg_marker_parser_method app_marker_parsers[16]; + jpeg_marker_parser_method com_marker_parser; + + // Fields defined by SOF marker. + size_t iMCU_cols_; + int h_factor[jpegli::kMaxComponents]; + int v_factor[jpegli::kMaxComponents]; + + // Initialized at start of frame. + uint16_t scan_progression_[jpegli::kMaxComponents][DCTSIZE2]; + + // + // Per scan state. + // + size_t scan_mcu_row_; + size_t scan_mcu_col_; + size_t mcu_rows_per_iMCU_row_; + jpegli::coeff_t last_dc_coeff_[jpegli::kMaxComponents]; + int eobrun_; + int restarts_to_go_; + int next_restart_marker_; + + jpegli::MCUCodingState mcu_; + + // + // Rendering state. + // + int output_passes_done_; + JpegliDataType output_data_type_ = JPEGLI_TYPE_UINT8; + size_t xoffset_; + bool swap_endianness_ = false; + bool need_context_rows_; + bool regenerate_inverse_colormap_; + bool apply_smoothing; + + int min_scaled_dct_size; + int scaled_dct_size[jpegli::kMaxComponents]; + + size_t raw_height_[jpegli::kMaxComponents]; + jpegli::RowBuffer raw_output_[jpegli::kMaxComponents]; + jpegli::RowBuffer render_output_[jpegli::kMaxComponents]; + + void (*inverse_transform[jpegli::kMaxComponents])( + const int16_t* JXL_RESTRICT qblock, const float* JXL_RESTRICT dequant, + const float* JXL_RESTRICT biases, float* JXL_RESTRICT scratch_space, + float* JXL_RESTRICT output, size_t output_stride, size_t dctsize); + + void (*color_transform)(float* row[jpegli::kMaxComponents], size_t len); + + float* idct_scratch_; + float* upsample_scratch_; + uint8_t* output_scratch_; + int16_t* smoothing_scratch_; + float* dequant_; + // 1 = 1pass, 2 = 2pass, 3 = external + int quant_mode_; + int quant_pass_; + int num_colors_[jpegli::kMaxComponents]; + uint8_t* colormap_lut_; + uint8_t* pixels_; + JSAMPARRAY scanlines_; + std::vector> candidate_lists_; + float* dither_[jpegli::kMaxComponents]; + float* error_row_[2 * jpegli::kMaxComponents]; + size_t dither_size_; + size_t dither_mask_; + + // Per channel and per frequency statistics about the number of nonzeros and + // the sum of coefficient absolute values, used in dequantization bias + // computation. + int* nonzeros_; + int* sumabs_; + size_t num_processed_blocks_[jpegli::kMaxComponents]; + float* biases_; +#define SAVED_COEFS 10 + // This holds the coef_bits of the scan before the current scan, + // i.e. the bottom half when rendering incomplete scans. + int (*coef_bits_latch)[SAVED_COEFS]; + int (*prev_coef_bits_latch)[SAVED_COEFS]; +}; + +#endif // LIB_JPEGLI_DECODE_INTERNAL_H_ diff --git a/media/libjxl/src/lib/jpegli/decode_marker.cc b/media/libjxl/src/lib/jpegli/decode_marker.cc new file mode 100644 index 0000000000..2621ed0867 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/decode_marker.cc @@ -0,0 +1,585 @@ +// 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/jpegli/decode_marker.h" + +#include +#include + +#include "lib/jpegli/common.h" +#include "lib/jpegli/decode_internal.h" +#include "lib/jpegli/error.h" +#include "lib/jpegli/huffman.h" +#include "lib/jpegli/memory_manager.h" +#include "lib/jxl/base/printf_macros.h" + +namespace jpegli { +namespace { + +constexpr int kMaxDimPixels = 65535; +constexpr uint8_t kIccProfileTag[12] = "ICC_PROFILE"; + +// Macros for commonly used error conditions. + +#define JPEG_VERIFY_LEN(n) \ + if (pos + (n) > len) { \ + JPEGLI_ERROR("Unexpected end of marker: pos=%" PRIuS \ + " need=%d len=%" PRIuS, \ + pos, static_cast(n), len); \ + } + +#define JPEG_VERIFY_INPUT(var, low, high) \ + if ((var) < (low) || (var) > (high)) { \ + JPEGLI_ERROR("Invalid " #var ": %d", static_cast(var)); \ + } + +#define JPEG_VERIFY_MARKER_END() \ + if (pos != len) { \ + JPEGLI_ERROR("Invalid marker length: declared=%" PRIuS " actual=%" PRIuS, \ + len, pos); \ + } + +inline int ReadUint8(const uint8_t* data, size_t* pos) { + return data[(*pos)++]; +} + +inline int ReadUint16(const uint8_t* data, size_t* pos) { + int v = (data[*pos] << 8) + data[*pos + 1]; + *pos += 2; + return v; +} + +void ProcessSOF(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { + jpeg_decomp_master* m = cinfo->master; + if (!m->found_soi_) { + JPEGLI_ERROR("Unexpected SOF marker."); + } + if (m->found_sof_) { + JPEGLI_ERROR("Duplicate SOF marker."); + } + m->found_sof_ = true; + cinfo->progressive_mode = TO_JXL_BOOL(cinfo->unread_marker == 0xc2); + cinfo->arith_code = 0; + size_t pos = 2; + JPEG_VERIFY_LEN(6); + cinfo->data_precision = ReadUint8(data, &pos); + cinfo->image_height = ReadUint16(data, &pos); + cinfo->image_width = ReadUint16(data, &pos); + cinfo->num_components = ReadUint8(data, &pos); + JPEG_VERIFY_INPUT(cinfo->data_precision, kJpegPrecision, kJpegPrecision); + JPEG_VERIFY_INPUT(cinfo->image_height, 1, kMaxDimPixels); + JPEG_VERIFY_INPUT(cinfo->image_width, 1, kMaxDimPixels); + JPEG_VERIFY_INPUT(cinfo->num_components, 1, kMaxComponents); + JPEG_VERIFY_LEN(3 * cinfo->num_components); + cinfo->comp_info = jpegli::Allocate( + cinfo, cinfo->num_components, JPOOL_IMAGE); + + // Read sampling factors and quant table index for each component. + uint8_t ids_seen[256] = {0}; + cinfo->max_h_samp_factor = 1; + cinfo->max_v_samp_factor = 1; + for (int i = 0; i < cinfo->num_components; ++i) { + jpeg_component_info* comp = &cinfo->comp_info[i]; + comp->component_index = i; + const int id = ReadUint8(data, &pos); + if (ids_seen[id]) { // (cf. section B.2.2, syntax of Ci) + JPEGLI_ERROR("Duplicate ID %d in SOF.", id); + } + ids_seen[id] = 1; + comp->component_id = id; + int factor = ReadUint8(data, &pos); + int h_samp_factor = factor >> 4; + int v_samp_factor = factor & 0xf; + JPEG_VERIFY_INPUT(h_samp_factor, 1, MAX_SAMP_FACTOR); + JPEG_VERIFY_INPUT(v_samp_factor, 1, MAX_SAMP_FACTOR); + comp->h_samp_factor = h_samp_factor; + comp->v_samp_factor = v_samp_factor; + cinfo->max_h_samp_factor = + std::max(cinfo->max_h_samp_factor, h_samp_factor); + cinfo->max_v_samp_factor = + std::max(cinfo->max_v_samp_factor, v_samp_factor); + int quant_tbl_idx = ReadUint8(data, &pos); + JPEG_VERIFY_INPUT(quant_tbl_idx, 0, NUM_QUANT_TBLS - 1); + comp->quant_tbl_no = quant_tbl_idx; + comp->quant_table = nullptr; // will be allocated after SOS marker + } + JPEG_VERIFY_MARKER_END(); + + // Set the input colorspace based on the markers we have seen and set + // default output colorspace. + if (cinfo->num_components == 1) { + cinfo->jpeg_color_space = JCS_GRAYSCALE; + cinfo->out_color_space = JCS_GRAYSCALE; + } else if (cinfo->num_components == 3) { + if (cinfo->saw_JFIF_marker) { + cinfo->jpeg_color_space = JCS_YCbCr; + } else if (cinfo->saw_Adobe_marker) { + cinfo->jpeg_color_space = + cinfo->Adobe_transform == 0 ? JCS_RGB : JCS_YCbCr; + } else { + cinfo->jpeg_color_space = JCS_YCbCr; + if (cinfo->comp_info[0].component_id == 'R' && // + cinfo->comp_info[1].component_id == 'G' && // + cinfo->comp_info[2].component_id == 'B') { + cinfo->jpeg_color_space = JCS_RGB; + } + } + cinfo->out_color_space = JCS_RGB; + } else if (cinfo->num_components == 4) { + if (cinfo->saw_Adobe_marker) { + cinfo->jpeg_color_space = + cinfo->Adobe_transform == 0 ? JCS_CMYK : JCS_YCCK; + } else { + cinfo->jpeg_color_space = JCS_CMYK; + } + cinfo->out_color_space = JCS_CMYK; + } + + // We have checked above that none of the sampling factors are 0, so the max + // sampling factors can not be 0. + cinfo->total_iMCU_rows = + DivCeil(cinfo->image_height, cinfo->max_v_samp_factor * DCTSIZE); + m->iMCU_cols_ = + DivCeil(cinfo->image_width, cinfo->max_h_samp_factor * DCTSIZE); + // Compute the block dimensions for each component. + for (int i = 0; i < cinfo->num_components; ++i) { + jpeg_component_info* comp = &cinfo->comp_info[i]; + if (cinfo->max_h_samp_factor % comp->h_samp_factor != 0 || + cinfo->max_v_samp_factor % comp->v_samp_factor != 0) { + JPEGLI_ERROR("Non-integral subsampling ratios."); + } + m->h_factor[i] = cinfo->max_h_samp_factor / comp->h_samp_factor; + m->v_factor[i] = cinfo->max_v_samp_factor / comp->v_samp_factor; + comp->downsampled_width = DivCeil(cinfo->image_width, m->h_factor[i]); + comp->downsampled_height = DivCeil(cinfo->image_height, m->v_factor[i]); + comp->width_in_blocks = DivCeil(comp->downsampled_width, DCTSIZE); + comp->height_in_blocks = DivCeil(comp->downsampled_height, DCTSIZE); + } + memset(m->scan_progression_, 0, sizeof(m->scan_progression_)); +} + +void ProcessSOS(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { + jpeg_decomp_master* m = cinfo->master; + if (!m->found_sof_) { + JPEGLI_ERROR("Unexpected SOS marker."); + } + m->found_sos_ = true; + size_t pos = 2; + JPEG_VERIFY_LEN(1); + cinfo->comps_in_scan = ReadUint8(data, &pos); + JPEG_VERIFY_INPUT(cinfo->comps_in_scan, 1, cinfo->num_components); + JPEG_VERIFY_INPUT(cinfo->comps_in_scan, 1, MAX_COMPS_IN_SCAN); + + JPEG_VERIFY_LEN(2 * cinfo->comps_in_scan); + bool is_interleaved = (cinfo->comps_in_scan > 1); + uint8_t ids_seen[256] = {0}; + cinfo->blocks_in_MCU = 0; + for (int i = 0; i < cinfo->comps_in_scan; ++i) { + int id = ReadUint8(data, &pos); + if (ids_seen[id]) { // (cf. section B.2.3, regarding CSj) + JPEGLI_ERROR("Duplicate ID %d in SOS.", id); + } + ids_seen[id] = 1; + jpeg_component_info* comp = nullptr; + for (int j = 0; j < cinfo->num_components; ++j) { + if (cinfo->comp_info[j].component_id == id) { + comp = &cinfo->comp_info[j]; + cinfo->cur_comp_info[i] = comp; + } + } + if (!comp) { + JPEGLI_ERROR("SOS marker: Could not find component with id %d", id); + } + int c = ReadUint8(data, &pos); + comp->dc_tbl_no = c >> 4; + comp->ac_tbl_no = c & 0xf; + JPEG_VERIFY_INPUT(comp->dc_tbl_no, 0, 3); + JPEG_VERIFY_INPUT(comp->ac_tbl_no, 0, 3); + comp->MCU_width = is_interleaved ? comp->h_samp_factor : 1; + comp->MCU_height = is_interleaved ? comp->v_samp_factor : 1; + comp->MCU_blocks = comp->MCU_width * comp->MCU_height; + if (cinfo->blocks_in_MCU + comp->MCU_blocks > D_MAX_BLOCKS_IN_MCU) { + JPEGLI_ERROR("Too many blocks in MCU."); + } + for (int j = 0; j < comp->MCU_blocks; ++j) { + cinfo->MCU_membership[cinfo->blocks_in_MCU++] = i; + } + } + JPEG_VERIFY_LEN(3); + cinfo->Ss = ReadUint8(data, &pos); + cinfo->Se = ReadUint8(data, &pos); + JPEG_VERIFY_INPUT(cinfo->Ss, 0, 63); + JPEG_VERIFY_INPUT(cinfo->Se, cinfo->Ss, 63); + int c = ReadUint8(data, &pos); + cinfo->Ah = c >> 4; + cinfo->Al = c & 0xf; + JPEG_VERIFY_MARKER_END(); + + if (cinfo->input_scan_number == 0) { + m->is_multiscan_ = (cinfo->comps_in_scan < cinfo->num_components || + FROM_JXL_BOOL(cinfo->progressive_mode)); + } + if (cinfo->Ah != 0 && cinfo->Al != cinfo->Ah - 1) { + // section G.1.1.1.2 : Successive approximation control only improves + // by one bit at a time. + JPEGLI_ERROR("Invalid progressive parameters: Al=%d Ah=%d", cinfo->Al, + cinfo->Ah); + } + if (!cinfo->progressive_mode) { + cinfo->Ss = 0; + cinfo->Se = 63; + cinfo->Ah = 0; + cinfo->Al = 0; + } + const uint16_t scan_bitmask = + cinfo->Ah == 0 ? (0xffff << cinfo->Al) : (1u << cinfo->Al); + const uint16_t refinement_bitmask = (1 << cinfo->Al) - 1; + if (!cinfo->coef_bits) { + cinfo->coef_bits = + Allocate(cinfo, cinfo->num_components * 2, JPOOL_IMAGE); + m->coef_bits_latch = + Allocate(cinfo, cinfo->num_components, JPOOL_IMAGE); + m->prev_coef_bits_latch = + Allocate(cinfo, cinfo->num_components, JPOOL_IMAGE); + + for (int c = 0; c < cinfo->num_components; ++c) { + for (int i = 0; i < DCTSIZE2; ++i) { + cinfo->coef_bits[c][i] = -1; + if (i < SAVED_COEFS) { + m->coef_bits_latch[c][i] = -1; + } + } + } + } + + for (int i = 0; i < cinfo->comps_in_scan; ++i) { + int comp_idx = cinfo->cur_comp_info[i]->component_index; + for (int k = cinfo->Ss; k <= cinfo->Se; ++k) { + if (m->scan_progression_[comp_idx][k] & scan_bitmask) { + JPEGLI_ERROR( + "Overlapping scans: component=%d k=%d prev_mask: %u cur_mask %u", + comp_idx, k, m->scan_progression_[i][k], scan_bitmask); + } + if (m->scan_progression_[comp_idx][k] & refinement_bitmask) { + JPEGLI_ERROR( + "Invalid scan order, a more refined scan was already done: " + "component=%d k=%d prev_mask=%u cur_mask=%u", + comp_idx, k, m->scan_progression_[i][k], scan_bitmask); + } + m->scan_progression_[comp_idx][k] |= scan_bitmask; + } + } + if (cinfo->Al > 10) { + JPEGLI_ERROR("Scan parameter Al=%d is not supported.", cinfo->Al); + } +} + +// Reads the Define Huffman Table (DHT) marker segment and builds the Huffman +// decoding table in either dc_huff_lut_ or ac_huff_lut_, depending on the type +// and solt_id of Huffman code being read. +void ProcessDHT(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { + size_t pos = 2; + if (pos == len) { + JPEGLI_ERROR("DHT marker: no Huffman table found"); + } + while (pos < len) { + JPEG_VERIFY_LEN(1 + kJpegHuffmanMaxBitLength); + // The index of the Huffman code in the current set of Huffman codes. For AC + // component Huffman codes, 0x10 is added to the index. + int slot_id = ReadUint8(data, &pos); + int huffman_index = slot_id; + bool is_ac_table = ((slot_id & 0x10) != 0); + JHUFF_TBL** table; + if (is_ac_table) { + huffman_index -= 0x10; + JPEG_VERIFY_INPUT(huffman_index, 0, NUM_HUFF_TBLS - 1); + table = &cinfo->ac_huff_tbl_ptrs[huffman_index]; + } else { + JPEG_VERIFY_INPUT(huffman_index, 0, NUM_HUFF_TBLS - 1); + table = &cinfo->dc_huff_tbl_ptrs[huffman_index]; + } + if (*table == nullptr) { + *table = jpegli_alloc_huff_table(reinterpret_cast(cinfo)); + } + int total_count = 0; + for (size_t i = 1; i <= kJpegHuffmanMaxBitLength; ++i) { + int count = ReadUint8(data, &pos); + (*table)->bits[i] = count; + total_count += count; + } + if (is_ac_table) { + JPEG_VERIFY_INPUT(total_count, 0, kJpegHuffmanAlphabetSize); + } else { + // Allow symbols up to 15 here, we check later whether any invalid symbols + // are actually decoded. + // TODO(szabadka) Make sure decoder works (does not crash) with up to + // 15-nbits DC symbols and then increase kJpegDCAlphabetSize. + JPEG_VERIFY_INPUT(total_count, 0, 16); + } + JPEG_VERIFY_LEN(total_count); + for (int i = 0; i < total_count; ++i) { + int value = ReadUint8(data, &pos); + if (!is_ac_table) { + JPEG_VERIFY_INPUT(value, 0, 15); + } + (*table)->huffval[i] = value; + } + for (int i = total_count; i < kJpegHuffmanAlphabetSize; ++i) { + (*table)->huffval[i] = 0; + } + } + JPEG_VERIFY_MARKER_END(); +} + +void ProcessDQT(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { + jpeg_decomp_master* m = cinfo->master; + if (m->found_sos_) { + JPEGLI_ERROR("Updating quant tables between scans is not supported."); + } + size_t pos = 2; + if (pos == len) { + JPEGLI_ERROR("DQT marker: no quantization table found"); + } + while (pos < len) { + JPEG_VERIFY_LEN(1); + int quant_table_index = ReadUint8(data, &pos); + int precision = quant_table_index >> 4; + JPEG_VERIFY_INPUT(precision, 0, 1); + quant_table_index &= 0xf; + JPEG_VERIFY_INPUT(quant_table_index, 0, NUM_QUANT_TBLS - 1); + JPEG_VERIFY_LEN((precision + 1) * DCTSIZE2); + + if (cinfo->quant_tbl_ptrs[quant_table_index] == nullptr) { + cinfo->quant_tbl_ptrs[quant_table_index] = + jpegli_alloc_quant_table(reinterpret_cast(cinfo)); + } + JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[quant_table_index]; + + for (size_t i = 0; i < DCTSIZE2; ++i) { + int quant_val = + precision ? ReadUint16(data, &pos) : ReadUint8(data, &pos); + JPEG_VERIFY_INPUT(quant_val, 1, 65535); + quant_table->quantval[kJPEGNaturalOrder[i]] = quant_val; + } + } + JPEG_VERIFY_MARKER_END(); +} + +void ProcessDNL(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { + // Ignore marker. +} + +void ProcessDRI(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { + jpeg_decomp_master* m = cinfo->master; + if (m->found_dri_) { + JPEGLI_ERROR("Duplicate DRI marker."); + } + m->found_dri_ = true; + size_t pos = 2; + JPEG_VERIFY_LEN(2); + cinfo->restart_interval = ReadUint16(data, &pos); + JPEG_VERIFY_MARKER_END(); +} + +void ProcessAPP(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { + jpeg_decomp_master* m = cinfo->master; + const uint8_t marker = cinfo->unread_marker; + const uint8_t* payload = data + 2; + size_t payload_size = len - 2; + if (marker == 0xE0) { + if (payload_size >= 14 && memcmp(payload, "JFIF", 4) == 0) { + cinfo->saw_JFIF_marker = TRUE; + cinfo->JFIF_major_version = payload[5]; + cinfo->JFIF_minor_version = payload[6]; + cinfo->density_unit = payload[7]; + cinfo->X_density = (payload[8] << 8) + payload[9]; + cinfo->Y_density = (payload[10] << 8) + payload[11]; + } + } else if (marker == 0xEE) { + if (payload_size >= 12 && memcmp(payload, "Adobe", 5) == 0) { + cinfo->saw_Adobe_marker = TRUE; + cinfo->Adobe_transform = payload[11]; + } + } else if (marker == 0xE2) { + if (payload_size >= sizeof(kIccProfileTag) && + memcmp(payload, kIccProfileTag, sizeof(kIccProfileTag)) == 0) { + payload += sizeof(kIccProfileTag); + payload_size -= sizeof(kIccProfileTag); + if (payload_size < 2) { + JPEGLI_ERROR("ICC chunk is too small."); + } + uint8_t index = payload[0]; + uint8_t total = payload[1]; + ++m->icc_index_; + if (m->icc_index_ != index) { + JPEGLI_ERROR("Invalid ICC chunk order."); + } + if (total == 0) { + JPEGLI_ERROR("Invalid ICC chunk total."); + } + if (m->icc_total_ == 0) { + m->icc_total_ = total; + } else if (m->icc_total_ != total) { + JPEGLI_ERROR("Invalid ICC chunk total."); + } + if (m->icc_index_ > m->icc_total_) { + JPEGLI_ERROR("Invalid ICC chunk index."); + } + m->icc_profile_.insert(m->icc_profile_.end(), payload + 2, + payload + payload_size); + } + } +} + +void ProcessCOM(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { + // Ignore marker. +} + +void ProcessSOI(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { + jpeg_decomp_master* m = cinfo->master; + if (m->found_soi_) { + JPEGLI_ERROR("Duplicate SOI marker"); + } + m->found_soi_ = true; +} + +void ProcessEOI(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { + cinfo->master->found_eoi_ = true; +} + +void SaveMarker(j_decompress_ptr cinfo, const uint8_t* data, size_t len) { + const uint8_t marker = cinfo->unread_marker; + const uint8_t* payload = data + 2; + size_t payload_size = len - 2; + + // Insert new saved marker to the head of the list. + jpeg_saved_marker_ptr next = cinfo->marker_list; + cinfo->marker_list = + jpegli::Allocate(cinfo, 1, JPOOL_IMAGE); + cinfo->marker_list->next = next; + cinfo->marker_list->marker = marker; + cinfo->marker_list->original_length = payload_size; + cinfo->marker_list->data_length = payload_size; + cinfo->marker_list->data = + jpegli::Allocate(cinfo, payload_size, JPOOL_IMAGE); + memcpy(cinfo->marker_list->data, payload, payload_size); +} + +uint8_t ProcessNextMarker(j_decompress_ptr cinfo, const uint8_t* const data, + const size_t len, size_t* pos) { + jpeg_decomp_master* m = cinfo->master; + size_t num_skipped = 0; + uint8_t marker = cinfo->unread_marker; + if (marker == 0) { + // kIsValidMarker[i] == 1 means (0xc0 + i) is a valid marker. + static const uint8_t kIsValidMarker[] = { + 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + }; + // Skip bytes between markers. + while (*pos + 1 < len && (data[*pos] != 0xff || data[*pos + 1] < 0xc0 || + !kIsValidMarker[data[*pos + 1] - 0xc0])) { + ++(*pos); + ++num_skipped; + } + if (*pos + 2 > len) { + return kNeedMoreInput; + } + marker = data[*pos + 1]; + if (num_skipped > 0) { + if (m->found_soi_) { + JPEGLI_WARN("Skipped %d bytes before marker 0x%02x", + static_cast(num_skipped), marker); + } else { + JPEGLI_ERROR("Did not find SOI marker."); + } + } + *pos += 2; + cinfo->unread_marker = marker; + } + if (!m->found_soi_ && marker != 0xd8) { + JPEGLI_ERROR("Did not find SOI marker."); + } + if (GetMarkerProcessor(cinfo)) { + return kHandleMarkerProcessor; + } + const uint8_t* marker_data = &data[*pos]; + size_t marker_len = 0; + if (marker != 0xd8 && marker != 0xd9) { + if (*pos + 2 > len) { + return kNeedMoreInput; + } + marker_len += (data[*pos] << 8) + data[*pos + 1]; + if (marker_len < 2) { + JPEGLI_ERROR("Invalid marker length"); + } + if (*pos + marker_len > len) { + // TODO(szabadka) Limit our memory usage by using the skip_input_data + // source manager callback on APP markers that are not saved. + return kNeedMoreInput; + } + if (marker >= 0xe0 && m->markers_to_save_[marker - 0xe0]) { + SaveMarker(cinfo, marker_data, marker_len); + } + } + if (marker == 0xc0 || marker == 0xc1 || marker == 0xc2) { + ProcessSOF(cinfo, marker_data, marker_len); + } else if (marker == 0xc4) { + ProcessDHT(cinfo, marker_data, marker_len); + } else if (marker == 0xda) { + ProcessSOS(cinfo, marker_data, marker_len); + } else if (marker == 0xdb) { + ProcessDQT(cinfo, marker_data, marker_len); + } else if (marker == 0xdc) { + ProcessDNL(cinfo, marker_data, marker_len); + } else if (marker == 0xdd) { + ProcessDRI(cinfo, marker_data, marker_len); + } else if (marker >= 0xe0 && marker <= 0xef) { + ProcessAPP(cinfo, marker_data, marker_len); + } else if (marker == 0xfe) { + ProcessCOM(cinfo, marker_data, marker_len); + } else if (marker == 0xd8) { + ProcessSOI(cinfo, marker_data, marker_len); + } else if (marker == 0xd9) { + ProcessEOI(cinfo, marker_data, marker_len); + } else { + JPEGLI_ERROR("Unexpected marker 0x%x", marker); + } + *pos += marker_len; + cinfo->unread_marker = 0; + if (marker == 0xda) { + return JPEG_REACHED_SOS; + } else if (marker == 0xd9) { + return JPEG_REACHED_EOI; + } + return kProcessNextMarker; +} + +} // namespace + +jpeg_marker_parser_method GetMarkerProcessor(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + uint8_t marker = cinfo->unread_marker; + jpeg_marker_parser_method callback = nullptr; + if (marker >= 0xe0 && marker <= 0xef) { + callback = m->app_marker_parsers[marker - 0xe0]; + } else if (marker == 0xfe) { + callback = m->com_marker_parser; + } + return callback; +} + +int ProcessMarkers(j_decompress_ptr cinfo, const uint8_t* const data, + const size_t len, size_t* pos) { + for (;;) { + int status = ProcessNextMarker(cinfo, data, len, pos); + if (status != kProcessNextMarker) { + return status; + } + } +} + +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/decode_marker.h b/media/libjxl/src/lib/jpegli/decode_marker.h new file mode 100644 index 0000000000..f3d47f6ad2 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/decode_marker.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_JPEGLI_DECODE_MARKER_H_ +#define LIB_JPEGLI_DECODE_MARKER_H_ + +#include + +#include "lib/jpegli/common.h" + +namespace jpegli { + +// Reads the available input in the source manager's input buffer until either +// the end of the next SOS marker or the end of the input. +// The corresponding fields of cinfo are updated with the processed input data. +// Upon return, the input buffer will be at the start or at the end of a marker +// data segment (inter-marker data is allowed). +// Return value is one of: +// * JPEG_SUSPENDED, if the current input buffer ends before the next SOS or +// EOI marker. Input buffer refill is handled by the caller; +// * JPEG_REACHED_SOS, if the next SOS marker is found; +// * JPEG_REACHED_EOR, if the end of the input is found. +int ProcessMarkers(j_decompress_ptr cinfo, const uint8_t* data, size_t len, + size_t* pos); + +jpeg_marker_parser_method GetMarkerProcessor(j_decompress_ptr cinfo); + +} // namespace jpegli + +#endif // LIB_JPEGLI_DECODE_MARKER_H_ diff --git a/media/libjxl/src/lib/jpegli/decode_scan.cc b/media/libjxl/src/lib/jpegli/decode_scan.cc new file mode 100644 index 0000000000..6c4e5ac5f7 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/decode_scan.cc @@ -0,0 +1,567 @@ +// 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/jpegli/decode_scan.h" + +#include + +#include // HWY_ALIGN_MAX + +#include "lib/jpegli/decode_internal.h" +#include "lib/jpegli/error.h" +#include "lib/jxl/base/status.h" + +namespace jpegli { +namespace { + +// Max 14 block per MCU (when 1 channel is subsampled) +// Max 64 nonzero coefficients per block +// Max 16 symbol bits plus 11 extra bits per nonzero symbol +// Max 2 bytes per 8 bits (worst case is all bytes are escaped 0xff) +constexpr int kMaxMCUByteSize = 6048; + +// Helper structure to read bits from the entropy coded data segment. +struct BitReaderState { + BitReaderState(const uint8_t* data, const size_t len, size_t pos) + : data_(data), len_(len), start_pos_(pos) { + Reset(pos); + } + + void Reset(size_t pos) { + pos_ = pos; + val_ = 0; + bits_left_ = 0; + next_marker_pos_ = len_; + FillBitWindow(); + } + + // Returns the next byte and skips the 0xff/0x00 escape sequences. + uint8_t GetNextByte() { + if (pos_ >= next_marker_pos_) { + ++pos_; + return 0; + } + uint8_t c = data_[pos_++]; + if (c == 0xff) { + uint8_t escape = pos_ < len_ ? data_[pos_] : 0; + if (escape == 0) { + ++pos_; + } else { + // 0xff was followed by a non-zero byte, which means that we found the + // start of the next marker segment. + next_marker_pos_ = pos_ - 1; + } + } + return c; + } + + void FillBitWindow() { + if (bits_left_ <= 16) { + while (bits_left_ <= 56) { + val_ <<= 8; + val_ |= static_cast(GetNextByte()); + bits_left_ += 8; + } + } + } + + int ReadBits(int nbits) { + FillBitWindow(); + uint64_t val = (val_ >> (bits_left_ - nbits)) & ((1ULL << nbits) - 1); + bits_left_ -= nbits; + return val; + } + + // Sets *pos to the next stream position, and *bit_pos to the bit position + // within the next byte where parsing should continue. + // Returns false if the stream ended too early. + bool FinishStream(size_t* pos, size_t* bit_pos) { + *bit_pos = (8 - (bits_left_ & 7)) & 7; + // Give back some bytes that we did not use. + int unused_bytes_left = DivCeil(bits_left_, 8); + while (unused_bytes_left-- > 0) { + --pos_; + // If we give back a 0 byte, we need to check if it was a 0xff/0x00 escape + // sequence, and if yes, we need to give back one more byte. + if (((pos_ == len_ && pos_ == next_marker_pos_) || + (pos_ > 0 && pos_ < next_marker_pos_ && data_[pos_] == 0)) && + (data_[pos_ - 1] == 0xff)) { + --pos_; + } + } + if (pos_ >= next_marker_pos_) { + *pos = next_marker_pos_; + if (pos_ > next_marker_pos_ || *bit_pos > 0) { + // Data ran out before the scan was complete. + return false; + } + } + *pos = pos_; + return true; + } + + const uint8_t* data_; + const size_t len_; + size_t pos_; + uint64_t val_; + int bits_left_; + size_t next_marker_pos_; + size_t start_pos_; +}; + +// Returns the next Huffman-coded symbol. +int ReadSymbol(const HuffmanTableEntry* table, BitReaderState* br) { + int nbits; + br->FillBitWindow(); + int val = (br->val_ >> (br->bits_left_ - 8)) & 0xff; + table += val; + nbits = table->bits - 8; + if (nbits > 0) { + br->bits_left_ -= 8; + table += table->value; + val = (br->val_ >> (br->bits_left_ - nbits)) & ((1 << nbits) - 1); + table += val; + } + br->bits_left_ -= table->bits; + return table->value; +} + +/** + * Returns the DC diff or AC value for extra bits value x and prefix code s. + * + * CCITT Rec. T.81 (1992 E) + * Table F.1 – Difference magnitude categories for DC coding + * SSSS | DIFF values + * ------+-------------------------- + * 0 | 0 + * 1 | –1, 1 + * 2 | –3, –2, 2, 3 + * 3 | –7..–4, 4..7 + * ......|.......................... + * 11 | –2047..–1024, 1024..2047 + * + * CCITT Rec. T.81 (1992 E) + * Table F.2 – Categories assigned to coefficient values + * [ Same as Table F.1, but does not include SSSS equal to 0 and 11] + * + * + * CCITT Rec. T.81 (1992 E) + * F.1.2.1.1 Structure of DC code table + * For each category,... additional bits... appended... to uniquely identify + * which difference... occurred... When DIFF is positive... SSSS... bits of DIFF + * are appended. When DIFF is negative... SSSS... bits of (DIFF – 1) are + * appended... Most significant bit... is 0 for negative differences and 1 for + * positive differences. + * + * In other words the upper half of extra bits range represents DIFF as is. + * The lower half represents the negative DIFFs with an offset. + */ +int HuffExtend(int x, int s) { + JXL_DASSERT(s > 0); + int half = 1 << (s - 1); + if (x >= half) { + JXL_DASSERT(x < (1 << s)); + return x; + } else { + return x - (1 << s) + 1; + } +} + +// Decodes one 8x8 block of DCT coefficients from the bit stream. +bool DecodeDCTBlock(const HuffmanTableEntry* dc_huff, + const HuffmanTableEntry* ac_huff, int Ss, int Se, int Al, + int* eobrun, BitReaderState* br, coeff_t* last_dc_coeff, + coeff_t* coeffs) { + // Nowadays multiplication is even faster than variable shift. + int Am = 1 << Al; + bool eobrun_allowed = Ss > 0; + if (Ss == 0) { + int s = ReadSymbol(dc_huff, br); + if (s >= kJpegDCAlphabetSize) { + return false; + } + int diff = 0; + if (s > 0) { + int bits = br->ReadBits(s); + diff = HuffExtend(bits, s); + } + int coeff = diff + *last_dc_coeff; + const int dc_coeff = coeff * Am; + coeffs[0] = dc_coeff; + // TODO(eustas): is there a more elegant / explicit way to check this? + if (dc_coeff != coeffs[0]) { + return false; + } + *last_dc_coeff = coeff; + ++Ss; + } + if (Ss > Se) { + return true; + } + if (*eobrun > 0) { + --(*eobrun); + return true; + } + for (int k = Ss; k <= Se; k++) { + int sr = ReadSymbol(ac_huff, br); + if (sr >= kJpegHuffmanAlphabetSize) { + return false; + } + int r = sr >> 4; + int s = sr & 15; + if (s > 0) { + k += r; + if (k > Se) { + return false; + } + if (s + Al >= kJpegDCAlphabetSize) { + return false; + } + int bits = br->ReadBits(s); + int coeff = HuffExtend(bits, s); + coeffs[kJPEGNaturalOrder[k]] = coeff * Am; + } else if (r == 15) { + k += 15; + } else { + *eobrun = 1 << r; + if (r > 0) { + if (!eobrun_allowed) { + return false; + } + *eobrun += br->ReadBits(r); + } + break; + } + } + --(*eobrun); + return true; +} + +bool RefineDCTBlock(const HuffmanTableEntry* ac_huff, int Ss, int Se, int Al, + int* eobrun, BitReaderState* br, coeff_t* coeffs) { + // Nowadays multiplication is even faster than variable shift. + int Am = 1 << Al; + bool eobrun_allowed = Ss > 0; + if (Ss == 0) { + int s = br->ReadBits(1); + coeff_t dc_coeff = coeffs[0]; + dc_coeff |= s * Am; + coeffs[0] = dc_coeff; + ++Ss; + } + if (Ss > Se) { + return true; + } + int p1 = Am; + int m1 = -Am; + int k = Ss; + int r; + int s; + bool in_zero_run = false; + if (*eobrun <= 0) { + for (; k <= Se; k++) { + s = ReadSymbol(ac_huff, br); + if (s >= kJpegHuffmanAlphabetSize) { + return false; + } + r = s >> 4; + s &= 15; + if (s) { + if (s != 1) { + return false; + } + s = br->ReadBits(1) ? p1 : m1; + in_zero_run = false; + } else { + if (r != 15) { + *eobrun = 1 << r; + if (r > 0) { + if (!eobrun_allowed) { + return false; + } + *eobrun += br->ReadBits(r); + } + break; + } + in_zero_run = true; + } + do { + coeff_t thiscoef = coeffs[kJPEGNaturalOrder[k]]; + if (thiscoef != 0) { + if (br->ReadBits(1)) { + if ((thiscoef & p1) == 0) { + if (thiscoef >= 0) { + thiscoef += p1; + } else { + thiscoef += m1; + } + } + } + coeffs[kJPEGNaturalOrder[k]] = thiscoef; + } else { + if (--r < 0) { + break; + } + } + k++; + } while (k <= Se); + if (s) { + if (k > Se) { + return false; + } + coeffs[kJPEGNaturalOrder[k]] = s; + } + } + } + if (in_zero_run) { + return false; + } + if (*eobrun > 0) { + for (; k <= Se; k++) { + coeff_t thiscoef = coeffs[kJPEGNaturalOrder[k]]; + if (thiscoef != 0) { + if (br->ReadBits(1)) { + if ((thiscoef & p1) == 0) { + if (thiscoef >= 0) { + thiscoef += p1; + } else { + thiscoef += m1; + } + } + } + coeffs[kJPEGNaturalOrder[k]] = thiscoef; + } + } + } + --(*eobrun); + return true; +} + +void SaveMCUCodingState(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + memcpy(m->mcu_.last_dc_coeff, m->last_dc_coeff_, sizeof(m->last_dc_coeff_)); + m->mcu_.eobrun = m->eobrun_; + size_t offset = 0; + 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; + size_t block_x = m->scan_mcu_col_ * comp->MCU_width; + for (int iy = 0; iy < comp->MCU_height; ++iy) { + size_t block_y = m->scan_mcu_row_ * comp->MCU_height + iy; + size_t biy = block_y % comp->v_samp_factor; + if (block_y >= comp->height_in_blocks) { + continue; + } + size_t nblocks = + std::min(comp->MCU_width, comp->width_in_blocks - block_x); + size_t ncoeffs = nblocks * DCTSIZE2; + coeff_t* coeffs = &m->coeff_rows[c][biy][block_x][0]; + memcpy(&m->mcu_.coeffs[offset], coeffs, ncoeffs * sizeof(coeffs[0])); + offset += ncoeffs; + } + } +} + +void RestoreMCUCodingState(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + memcpy(m->last_dc_coeff_, m->mcu_.last_dc_coeff, sizeof(m->last_dc_coeff_)); + m->eobrun_ = m->mcu_.eobrun; + size_t offset = 0; + 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; + size_t block_x = m->scan_mcu_col_ * comp->MCU_width; + for (int iy = 0; iy < comp->MCU_height; ++iy) { + size_t block_y = m->scan_mcu_row_ * comp->MCU_height + iy; + size_t biy = block_y % comp->v_samp_factor; + if (block_y >= comp->height_in_blocks) { + continue; + } + size_t nblocks = + std::min(comp->MCU_width, comp->width_in_blocks - block_x); + size_t ncoeffs = nblocks * DCTSIZE2; + coeff_t* coeffs = &m->coeff_rows[c][biy][block_x][0]; + memcpy(coeffs, &m->mcu_.coeffs[offset], ncoeffs * sizeof(coeffs[0])); + offset += ncoeffs; + } + } +} + +bool FinishScan(j_decompress_ptr cinfo, const uint8_t* data, const size_t len, + size_t* pos, size_t* bit_pos) { + jpeg_decomp_master* m = cinfo->master; + if (m->eobrun_ > 0) { + JPEGLI_ERROR("End-of-block run too long."); + } + m->eobrun_ = -1; + memset(m->last_dc_coeff_, 0, sizeof(m->last_dc_coeff_)); + if (*bit_pos == 0) { + return true; + } + if (data[*pos] == 0xff) { + // After last br.FinishStream we checked that there is at least 2 bytes + // in the buffer. + JXL_DASSERT(*pos + 1 < len); + // br.FinishStream would have detected an early marker. + JXL_DASSERT(data[*pos + 1] == 0); + *pos += 2; + } else { + *pos += 1; + } + *bit_pos = 0; + return true; +} + +} // namespace + +void PrepareForiMCURow(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + 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; + int by0 = cinfo->input_iMCU_row * comp->v_samp_factor; + int block_rows_left = comp->height_in_blocks - by0; + int max_block_rows = std::min(comp->v_samp_factor, block_rows_left); + int offset = m->streaming_mode_ ? 0 : by0; + m->coeff_rows[c] = (*cinfo->mem->access_virt_barray)( + reinterpret_cast(cinfo), m->coef_arrays[c], offset, + max_block_rows, TRUE); + } +} + +int ProcessScan(j_decompress_ptr cinfo, const uint8_t* const data, + const size_t len, size_t* pos, size_t* bit_pos) { + if (len == 0) { + return kNeedMoreInput; + } + jpeg_decomp_master* m = cinfo->master; + for (;;) { + // Handle the restart intervals. + if (cinfo->restart_interval > 0 && m->restarts_to_go_ == 0) { + if (!FinishScan(cinfo, data, len, pos, bit_pos)) { + return kNeedMoreInput; + } + // Go to the next marker, warn if we had to skip any data. + size_t num_skipped = 0; + while (*pos + 1 < len && (data[*pos] != 0xff || data[*pos + 1] == 0 || + data[*pos + 1] == 0xff)) { + ++(*pos); + ++num_skipped; + } + if (num_skipped > 0) { + JPEGLI_WARN("Skipped %d bytes before restart marker", + static_cast(num_skipped)); + } + if (*pos + 2 > len) { + return kNeedMoreInput; + } + cinfo->unread_marker = data[*pos + 1]; + *pos += 2; + return kHandleRestart; + } + + size_t start_pos = *pos; + BitReaderState br(data, len, start_pos); + if (*bit_pos > 0) { + br.ReadBits(*bit_pos); + } + if (start_pos + kMaxMCUByteSize > len) { + SaveMCUCodingState(cinfo); + } + + // Decode one MCU. + HWY_ALIGN_MAX static coeff_t sink_block[DCTSIZE2] = {0}; + 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 = + &m->dc_huff_lut_[comp->dc_tbl_no * kJpegHuffmanLutSize]; + const HuffmanTableEntry* ac_lut = + &m->ac_huff_lut_[comp->ac_tbl_no * kJpegHuffmanLutSize]; + for (int iy = 0; iy < comp->MCU_height; ++iy) { + size_t block_y = m->scan_mcu_row_ * comp->MCU_height + iy; + int biy = block_y % comp->v_samp_factor; + 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 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 = 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, + &m->eobrun_, &br, + &m->last_dc_coeff_[comp->component_index], + coeffs)) { + scan_ok = false; + } + } else { + if (!RefineDCTBlock(ac_lut, cinfo->Ss, cinfo->Se, cinfo->Al, + &m->eobrun_, &br, coeffs)) { + scan_ok = false; + } + } + } + } + } + size_t new_pos; + size_t new_bit_pos; + bool stream_ok = br.FinishStream(&new_pos, &new_bit_pos); + if (new_pos + 2 > len) { + // If reading stopped within the last two bytes, we have to request more + // input even if FinishStream() returned true, since the Huffman code + // reader could have peaked ahead some bits past the current input chunk + // and thus the last prefix code length could have been wrong. We can do + // this because a valid JPEG bit stream has two extra bytes at the end. + RestoreMCUCodingState(cinfo); + return kNeedMoreInput; + } + *pos = new_pos; + *bit_pos = new_bit_pos; + if (!stream_ok) { + // We hit a marker during parsing. + JXL_DASSERT(data[*pos] == 0xff); + JXL_DASSERT(data[*pos + 1] != 0); + RestoreMCUCodingState(cinfo); + JPEGLI_WARN("Incomplete scan detected."); + return JPEG_SCAN_COMPLETED; + } + if (!scan_ok) { + JPEGLI_ERROR("Failed to decode DCT block"); + } + if (m->restarts_to_go_ > 0) { + --m->restarts_to_go_; + } + ++m->scan_mcu_col_; + if (m->scan_mcu_col_ == cinfo->MCUs_per_row) { + ++m->scan_mcu_row_; + m->scan_mcu_col_ = 0; + if (m->scan_mcu_row_ == cinfo->MCU_rows_in_scan) { + if (!FinishScan(cinfo, data, len, pos, bit_pos)) { + return kNeedMoreInput; + } + break; + } else if ((m->scan_mcu_row_ % m->mcu_rows_per_iMCU_row_) == 0) { + // Current iMCU row is done. + break; + } + } + } + ++cinfo->input_iMCU_row; + if (cinfo->input_iMCU_row < cinfo->total_iMCU_rows) { + PrepareForiMCURow(cinfo); + return JPEG_ROW_COMPLETED; + } + return JPEG_SCAN_COMPLETED; +} + +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/decode_scan.h b/media/libjxl/src/lib/jpegli/decode_scan.h new file mode 100644 index 0000000000..dd1bfcd110 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/decode_scan.h @@ -0,0 +1,31 @@ +// 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_JPEGLI_DECODE_SCAN_H_ +#define LIB_JPEGLI_DECODE_SCAN_H_ + +#include + +#include "lib/jpegli/common.h" + +namespace jpegli { + +// Reads the available input in the source manager's input buffer until the end +// of the next iMCU row. +// The corresponding fields of cinfo are updated with the processed input data. +// Upon return, the input buffer will be at the start of an MCU, or at the end +// of the scan. +// Return value is one of: +// * JPEG_SUSPENDED, if the input buffer ends before the end of an iMCU row; +// * JPEG_ROW_COMPLETED, if the next iMCU row (but not the scan) is reached; +// * JPEG_SCAN_COMPLETED, if the end of the scan is reached. +int ProcessScan(j_decompress_ptr cinfo, const uint8_t* data, size_t len, + size_t* pos, size_t* bit_pos); + +void PrepareForiMCURow(j_decompress_ptr cinfo); + +} // namespace jpegli + +#endif // LIB_JPEGLI_DECODE_SCAN_H_ diff --git a/media/libjxl/src/lib/jpegli/destination_manager.cc b/media/libjxl/src/lib/jpegli/destination_manager.cc new file mode 100644 index 0000000000..05d35797b8 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/destination_manager.cc @@ -0,0 +1,148 @@ +// 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 "lib/jpegli/encode.h" +#include "lib/jpegli/error.h" +#include "lib/jpegli/memory_manager.h" + +namespace jpegli { + +constexpr size_t kDestBufferSize = 64 << 10; + +struct StdioDestinationManager { + jpeg_destination_mgr pub; + FILE* f; + uint8_t* buffer; + + static void init_destination(j_compress_ptr cinfo) { + auto* dest = reinterpret_cast(cinfo->dest); + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = kDestBufferSize; + } + + static boolean empty_output_buffer(j_compress_ptr cinfo) { + auto* dest = reinterpret_cast(cinfo->dest); + if (fwrite(dest->buffer, 1, kDestBufferSize, dest->f) != kDestBufferSize) { + JPEGLI_ERROR("Failed to write to output stream."); + } + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = kDestBufferSize; + return TRUE; + } + + static void term_destination(j_compress_ptr cinfo) { + auto* dest = reinterpret_cast(cinfo->dest); + size_t bytes_left = kDestBufferSize - dest->pub.free_in_buffer; + if (bytes_left && + fwrite(dest->buffer, 1, bytes_left, dest->f) != bytes_left) { + JPEGLI_ERROR("Failed to write to output stream."); + } + fflush(dest->f); + if (ferror(dest->f)) { + JPEGLI_ERROR("Failed to write to output stream."); + } + } +}; + +struct MemoryDestinationManager { + jpeg_destination_mgr pub; + // Output buffer supplied by the application + uint8_t** output; + unsigned long* output_size; // NOLINT + // Output buffer allocated by us. + uint8_t* temp_buffer; + // Current output buffer (either application supplied or allocated by us). + uint8_t* current_buffer; + size_t buffer_size; + + static void init_destination(j_compress_ptr cinfo) {} + + static boolean empty_output_buffer(j_compress_ptr cinfo) { + auto* dest = reinterpret_cast(cinfo->dest); + uint8_t* next_buffer = + reinterpret_cast(malloc(dest->buffer_size * 2)); + memcpy(next_buffer, dest->current_buffer, dest->buffer_size); + if (dest->temp_buffer != nullptr) { + free(dest->temp_buffer); + } + dest->temp_buffer = next_buffer; + dest->current_buffer = next_buffer; + *dest->output = next_buffer; + *dest->output_size = dest->buffer_size; + dest->pub.next_output_byte = next_buffer + dest->buffer_size; + dest->pub.free_in_buffer = dest->buffer_size; + dest->buffer_size *= 2; + return TRUE; + } + + static void term_destination(j_compress_ptr cinfo) { + auto* dest = reinterpret_cast(cinfo->dest); + *dest->output_size = dest->buffer_size - dest->pub.free_in_buffer; + } +}; + +} // namespace jpegli + +void jpegli_stdio_dest(j_compress_ptr cinfo, FILE* outfile) { + if (outfile == nullptr) { + JPEGLI_ERROR("jpegli_stdio_dest: Invalid destination."); + } + if (cinfo->dest && cinfo->dest->init_destination != + jpegli::StdioDestinationManager::init_destination) { + JPEGLI_ERROR("jpegli_stdio_dest: a different dest manager was already set"); + } + if (!cinfo->dest) { + cinfo->dest = reinterpret_cast( + jpegli::Allocate(cinfo, 1)); + } + auto* dest = reinterpret_cast(cinfo->dest); + dest->f = outfile; + dest->buffer = jpegli::Allocate(cinfo, jpegli::kDestBufferSize); + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = jpegli::kDestBufferSize; + dest->pub.init_destination = + jpegli::StdioDestinationManager::init_destination; + dest->pub.empty_output_buffer = + jpegli::StdioDestinationManager::empty_output_buffer; + dest->pub.term_destination = + jpegli::StdioDestinationManager::term_destination; +} + +void jpegli_mem_dest(j_compress_ptr cinfo, unsigned char** outbuffer, + unsigned long* outsize /* NOLINT */) { + if (outbuffer == nullptr || outsize == nullptr) { + JPEGLI_ERROR("jpegli_mem_dest: Invalid destination."); + } + if (cinfo->dest && cinfo->dest->init_destination != + jpegli::MemoryDestinationManager::init_destination) { + JPEGLI_ERROR("jpegli_mem_dest: a different dest manager was already set"); + } + if (!cinfo->dest) { + auto* dest = jpegli::Allocate(cinfo, 1); + dest->temp_buffer = nullptr; + cinfo->dest = reinterpret_cast(dest); + } + auto* dest = reinterpret_cast(cinfo->dest); + dest->pub.init_destination = + jpegli::MemoryDestinationManager::init_destination; + dest->pub.empty_output_buffer = + jpegli::MemoryDestinationManager::empty_output_buffer; + dest->pub.term_destination = + jpegli::MemoryDestinationManager::term_destination; + dest->output = outbuffer; + dest->output_size = outsize; + if (*outbuffer == nullptr || *outsize == 0) { + dest->temp_buffer = + reinterpret_cast(malloc(jpegli::kDestBufferSize)); + *outbuffer = dest->temp_buffer; + *outsize = jpegli::kDestBufferSize; + } + dest->current_buffer = *outbuffer; + dest->buffer_size = *outsize; + dest->pub.next_output_byte = dest->current_buffer; + dest->pub.free_in_buffer = dest->buffer_size; +} diff --git a/media/libjxl/src/lib/jpegli/downsample.cc b/media/libjxl/src/lib/jpegli/downsample.cc new file mode 100644 index 0000000000..1e5bb1563b --- /dev/null +++ b/media/libjxl/src/lib/jpegli/downsample.cc @@ -0,0 +1,357 @@ +// 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/jpegli/downsample.h" + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jpegli/downsample.cc" +#include +#include + +#include "lib/jpegli/encode_internal.h" +#include "lib/jpegli/error.h" + +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { + +// These templates are not found via ADL. +using hwy::HWY_NAMESPACE::Add; +using hwy::HWY_NAMESPACE::Mul; +using hwy::HWY_NAMESPACE::Vec; + +using D = HWY_CAPPED(float, 8); +constexpr D d; + +void DownsampleRow2x1(const float* row_in, size_t len, float* row_out) { + const size_t N = Lanes(d); + const size_t len_out = len / 2; + const auto mul = Set(d, 0.5f); + Vec v0, v1; // NOLINT + for (size_t x = 0; x < len_out; x += N) { + LoadInterleaved2(d, row_in + 2 * x, v0, v1); + Store(Mul(mul, Add(v0, v1)), d, row_out + x); + } +} + +void DownsampleRow3x1(const float* row_in, size_t len, float* row_out) { + const size_t N = Lanes(d); + const size_t len_out = len / 3; + const auto mul = Set(d, 1.0f / 3); + Vec v0, v1, v2; // NOLINT + for (size_t x = 0; x < len_out; x += N) { + LoadInterleaved3(d, row_in + 3 * x, v0, v1, v2); + Store(Mul(mul, Add(Add(v0, v1), v2)), d, row_out + x); + } +} + +void DownsampleRow4x1(const float* row_in, size_t len, float* row_out) { + const size_t N = Lanes(d); + const size_t len_out = len / 4; + const auto mul = Set(d, 0.25f); + Vec v0, v1, v2, v3; // NOLINT + for (size_t x = 0; x < len_out; x += N) { + LoadInterleaved4(d, row_in + 4 * x, v0, v1, v2, v3); + Store(Mul(mul, Add(Add(v0, v1), Add(v2, v3))), d, row_out + x); + } +} + +void Downsample2x1(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + DownsampleRow2x1(rows_in[0], len, row_out); +} + +void Downsample3x1(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + DownsampleRow3x1(rows_in[0], len, row_out); +} + +void Downsample4x1(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + DownsampleRow4x1(rows_in[0], len, row_out); +} + +void Downsample1x2(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + const size_t N = Lanes(d); + const auto mul = Set(d, 0.5f); + float* row0 = rows_in[0]; + float* row1 = rows_in[1]; + for (size_t x = 0; x < len; x += N) { + Store(Mul(mul, Add(Load(d, row0 + x), Load(d, row1 + x))), d, row_out + x); + } +} + +void Downsample2x2(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + const size_t N = Lanes(d); + const size_t len_out = len / 2; + const auto mul = Set(d, 0.25f); + float* row0 = rows_in[0]; + float* row1 = rows_in[1]; + Vec v0, v1, v2, v3; // NOLINT + for (size_t x = 0; x < len_out; x += N) { + LoadInterleaved2(d, row0 + 2 * x, v0, v1); + LoadInterleaved2(d, row1 + 2 * x, v2, v3); + Store(Mul(mul, Add(Add(v0, v1), Add(v2, v3))), d, row_out + x); + } +} + +void Downsample3x2(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + DownsampleRow3x1(rows_in[0], len, rows_in[0]); + DownsampleRow3x1(rows_in[1], len, rows_in[1]); + Downsample1x2(rows_in, len / 3, row_out); +} + +void Downsample4x2(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + DownsampleRow4x1(rows_in[0], len, rows_in[0]); + DownsampleRow4x1(rows_in[1], len, rows_in[1]); + Downsample1x2(rows_in, len / 4, row_out); +} + +void Downsample1x3(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + const size_t N = Lanes(d); + const auto mul = Set(d, 1.0f / 3); + float* row0 = rows_in[0]; + float* row1 = rows_in[1]; + float* row2 = rows_in[2]; + for (size_t x = 0; x < len; x += N) { + const auto in0 = Load(d, row0 + x); + const auto in1 = Load(d, row1 + x); + const auto in2 = Load(d, row2 + x); + Store(Mul(mul, Add(Add(in0, in1), in2)), d, row_out + x); + } +} + +void Downsample2x3(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + DownsampleRow2x1(rows_in[0], len, rows_in[0]); + DownsampleRow2x1(rows_in[1], len, rows_in[1]); + DownsampleRow2x1(rows_in[2], len, rows_in[2]); + Downsample1x3(rows_in, len / 2, row_out); +} + +void Downsample3x3(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + DownsampleRow3x1(rows_in[0], len, rows_in[0]); + DownsampleRow3x1(rows_in[1], len, rows_in[1]); + DownsampleRow3x1(rows_in[2], len, rows_in[2]); + Downsample1x3(rows_in, len / 3, row_out); +} + +void Downsample4x3(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + DownsampleRow4x1(rows_in[0], len, rows_in[0]); + DownsampleRow4x1(rows_in[1], len, rows_in[1]); + DownsampleRow4x1(rows_in[2], len, rows_in[2]); + Downsample1x3(rows_in, len / 4, row_out); +} + +void Downsample1x4(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + const size_t N = Lanes(d); + const auto mul = Set(d, 0.25f); + float* row0 = rows_in[0]; + float* row1 = rows_in[1]; + float* row2 = rows_in[2]; + float* row3 = rows_in[3]; + for (size_t x = 0; x < len; x += N) { + const auto in0 = Load(d, row0 + x); + const auto in1 = Load(d, row1 + x); + const auto in2 = Load(d, row2 + x); + const auto in3 = Load(d, row3 + x); + Store(Mul(mul, Add(Add(in0, in1), Add(in2, in3))), d, row_out + x); + } +} + +void Downsample2x4(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + DownsampleRow2x1(rows_in[0], len, rows_in[0]); + DownsampleRow2x1(rows_in[1], len, rows_in[1]); + DownsampleRow2x1(rows_in[2], len, rows_in[2]); + DownsampleRow2x1(rows_in[3], len, rows_in[3]); + Downsample1x4(rows_in, len / 2, row_out); +} + +void Downsample3x4(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + DownsampleRow3x1(rows_in[0], len, rows_in[0]); + DownsampleRow3x1(rows_in[1], len, rows_in[1]); + DownsampleRow3x1(rows_in[2], len, rows_in[2]); + DownsampleRow3x1(rows_in[3], len, rows_in[3]); + Downsample1x4(rows_in, len / 3, row_out); +} + +void Downsample4x4(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) { + DownsampleRow4x1(rows_in[0], len, rows_in[0]); + DownsampleRow4x1(rows_in[1], len, rows_in[1]); + DownsampleRow4x1(rows_in[2], len, rows_in[2]); + DownsampleRow4x1(rows_in[3], len, rows_in[3]); + Downsample1x4(rows_in, len / 4, row_out); +} + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace jpegli { + +HWY_EXPORT(Downsample1x2); +HWY_EXPORT(Downsample1x3); +HWY_EXPORT(Downsample1x4); +HWY_EXPORT(Downsample2x1); +HWY_EXPORT(Downsample2x2); +HWY_EXPORT(Downsample2x3); +HWY_EXPORT(Downsample2x4); +HWY_EXPORT(Downsample3x1); +HWY_EXPORT(Downsample3x2); +HWY_EXPORT(Downsample3x3); +HWY_EXPORT(Downsample3x4); +HWY_EXPORT(Downsample4x1); +HWY_EXPORT(Downsample4x2); +HWY_EXPORT(Downsample4x3); +HWY_EXPORT(Downsample4x4); + +void NullDownsample(float* rows_in[MAX_SAMP_FACTOR], size_t len, + float* row_out) {} + +void ChooseDownsampleMethods(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + for (int c = 0; c < cinfo->num_components; c++) { + m->downsample_method[c] = nullptr; + jpeg_component_info* comp = &cinfo->comp_info[c]; + const int h_factor = cinfo->max_h_samp_factor / comp->h_samp_factor; + const int v_factor = cinfo->max_v_samp_factor / comp->v_samp_factor; + if (v_factor == 1) { + if (h_factor == 1) { + m->downsample_method[c] = NullDownsample; + } else if (h_factor == 2) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample2x1); + } else if (h_factor == 3) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample3x1); + } else if (h_factor == 4) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample4x1); + } + } else if (v_factor == 2) { + if (h_factor == 1) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample1x2); + } else if (h_factor == 2) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample2x2); + } else if (h_factor == 3) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample3x2); + } else if (h_factor == 4) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample4x2); + } + } else if (v_factor == 3) { + if (h_factor == 1) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample1x2); + } else if (h_factor == 2) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample2x2); + } else if (h_factor == 3) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample3x2); + } else if (h_factor == 4) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample4x2); + } + } else if (v_factor == 4) { + if (h_factor == 1) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample1x4); + } else if (h_factor == 2) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample2x4); + } else if (h_factor == 3) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample3x4); + } else if (h_factor == 4) { + m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample4x4); + } + } + if (m->downsample_method[c] == nullptr) { + JPEGLI_ERROR("Unsupported downsampling ratio %dx%d", h_factor, v_factor); + } + } +} + +void DownsampleInputBuffer(j_compress_ptr cinfo) { + if (cinfo->max_h_samp_factor == 1 && cinfo->max_v_samp_factor == 1) { + return; + } + jpeg_comp_master* m = cinfo->master; + const size_t iMCU_height = DCTSIZE * cinfo->max_v_samp_factor; + const size_t y0 = m->next_iMCU_row * iMCU_height; + const size_t y1 = y0 + iMCU_height; + const size_t xsize_padded = m->xsize_blocks * DCTSIZE; + for (int c = 0; c < cinfo->num_components; c++) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + const int h_factor = cinfo->max_h_samp_factor / comp->h_samp_factor; + const int v_factor = cinfo->max_v_samp_factor / comp->v_samp_factor; + if (h_factor == 1 && v_factor == 1) { + continue; + } + auto& input = *m->smooth_input[c]; + auto& output = *m->raw_data[c]; + const size_t y_out0 = y0 / v_factor; + float* rows_in[MAX_SAMP_FACTOR]; + for (size_t y_in = y0, y_out = y_out0; y_in < y1; + y_in += v_factor, ++y_out) { + for (int iy = 0; iy < v_factor; ++iy) { + rows_in[iy] = input.Row(y_in + iy); + } + float* row_out = output.Row(y_out); + (*m->downsample_method[c])(rows_in, xsize_padded, row_out); + } + } +} + +void ApplyInputSmoothing(j_compress_ptr cinfo) { + if (!cinfo->smoothing_factor) { + return; + } + jpeg_comp_master* m = cinfo->master; + const float kW1 = cinfo->smoothing_factor / 1024.0; + const float kW0 = 1.0f - 8.0f * kW1; + const size_t iMCU_height = DCTSIZE * cinfo->max_v_samp_factor; + const ssize_t y0 = m->next_iMCU_row * iMCU_height; + const ssize_t y1 = y0 + iMCU_height; + const ssize_t xsize_padded = m->xsize_blocks * DCTSIZE; + for (int c = 0; c < cinfo->num_components; c++) { + auto& input = m->input_buffer[c]; + auto& output = *m->smooth_input[c]; + if (m->next_iMCU_row == 0) { + input.CopyRow(-1, 0, 1); + } + if (m->next_iMCU_row + 1 == cinfo->total_iMCU_rows) { + size_t last_row = m->ysize_blocks * DCTSIZE - 1; + input.CopyRow(last_row + 1, last_row, 1); + } + // TODO(szabadka) SIMDify this. + for (ssize_t y = y0; y < y1; ++y) { + const float* row_t = input.Row(y - 1); + const float* row_m = input.Row(y); + const float* row_b = input.Row(y + 1); + float* row_out = output.Row(y); + for (ssize_t x = 0; x < xsize_padded; ++x) { + float val_tl = row_t[x - 1]; + float val_tm = row_t[x]; + float val_tr = row_t[x + 1]; + float val_ml = row_m[x - 1]; + float val_mm = row_m[x]; + float val_mr = row_m[x + 1]; + float val_bl = row_b[x - 1]; + float val_bm = row_b[x]; + float val_br = row_b[x + 1]; + float val1 = (val_tl + val_tm + val_tr + val_ml + val_mr + val_bl + + val_bm + val_br); + row_out[x] = val_mm * kW0 + val1 * kW1; + } + } + } +} + +} // namespace jpegli +#endif // HWY_ONCE diff --git a/media/libjxl/src/lib/jpegli/downsample.h b/media/libjxl/src/lib/jpegli/downsample.h new file mode 100644 index 0000000000..3ccf069e4e --- /dev/null +++ b/media/libjxl/src/lib/jpegli/downsample.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_JPEGLI_DOWNSAMPLE_H_ +#define LIB_JPEGLI_DOWNSAMPLE_H_ + +#include "lib/jpegli/common.h" + +namespace jpegli { + +void ChooseDownsampleMethods(j_compress_ptr cinfo); + +void DownsampleInputBuffer(j_compress_ptr cinfo); + +void ApplyInputSmoothing(j_compress_ptr cinfo); + +} // namespace jpegli + +#endif // LIB_JPEGLI_DOWNSAMPLE_H_ diff --git a/media/libjxl/src/lib/jpegli/encode.cc b/media/libjxl/src/lib/jpegli/encode.cc new file mode 100644 index 0000000000..410eeed808 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/encode.cc @@ -0,0 +1,1274 @@ +// 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/jpegli/encode.h" + +#include + +#include +#include +#include + +#include "lib/jpegli/adaptive_quantization.h" +#include "lib/jpegli/bit_writer.h" +#include "lib/jpegli/bitstream.h" +#include "lib/jpegli/color_transform.h" +#include "lib/jpegli/downsample.h" +#include "lib/jpegli/encode_finish.h" +#include "lib/jpegli/encode_internal.h" +#include "lib/jpegli/encode_streaming.h" +#include "lib/jpegli/entropy_coding.h" +#include "lib/jpegli/error.h" +#include "lib/jpegli/huffman.h" +#include "lib/jpegli/input.h" +#include "lib/jpegli/memory_manager.h" +#include "lib/jpegli/quant.h" + +namespace jpegli { + +constexpr size_t kMaxBytesInMarker = 65533; + +void CheckState(j_compress_ptr cinfo, int state) { + if (cinfo->global_state != state) { + JPEGLI_ERROR("Unexpected global state %d [expected %d]", + cinfo->global_state, state); + } +} + +void CheckState(j_compress_ptr cinfo, int state1, int state2) { + if (cinfo->global_state != state1 && cinfo->global_state != state2) { + JPEGLI_ERROR("Unexpected global state %d [expected %d or %d]", + cinfo->global_state, state1, state2); + } +} + +// +// Parameter setup +// + +// Initialize cinfo fields that are not dependent on input image. This is shared +// between jpegli_CreateCompress() and jpegli_set_defaults() +void InitializeCompressParams(j_compress_ptr cinfo) { + cinfo->data_precision = 8; + cinfo->num_scans = 0; + cinfo->scan_info = nullptr; + cinfo->raw_data_in = FALSE; + cinfo->arith_code = FALSE; + cinfo->optimize_coding = FALSE; + cinfo->CCIR601_sampling = FALSE; + cinfo->smoothing_factor = 0; + cinfo->dct_method = JDCT_FLOAT; + cinfo->restart_interval = 0; + cinfo->restart_in_rows = 0; + cinfo->write_JFIF_header = FALSE; + cinfo->JFIF_major_version = 1; + cinfo->JFIF_minor_version = 1; + cinfo->density_unit = 0; + cinfo->X_density = 1; + cinfo->Y_density = 1; +#if JPEG_LIB_VERSION >= 70 + cinfo->scale_num = 1; + cinfo->scale_denom = 1; + cinfo->do_fancy_downsampling = FALSE; + cinfo->min_DCT_h_scaled_size = DCTSIZE; + cinfo->min_DCT_v_scaled_size = DCTSIZE; +#endif + cinfo->master->psnr_target = 0.0f; + cinfo->master->psnr_tolerance = 0.01f; + cinfo->master->min_distance = 0.1f; + cinfo->master->max_distance = 25.0f; +} + +float LinearQualityToDistance(int scale_factor) { + scale_factor = std::min(5000, std::max(0, scale_factor)); + int quality = + scale_factor < 100 ? 100 - scale_factor / 2 : 5000 / scale_factor; + return jpegli_quality_to_distance(quality); +} + +template +void SetSentTableFlag(T** table_ptrs, size_t num, boolean val) { + for (size_t i = 0; i < num; ++i) { + if (table_ptrs[i]) table_ptrs[i]->sent_table = val; + } +} + +// +// Compressor initialization +// + +struct ProgressiveScan { + int Ss, Se, Ah, Al; + bool interleaved; +}; + +void SetDefaultScanScript(j_compress_ptr cinfo) { + int level = cinfo->master->progressive_level; + std::vector progressive_mode; + bool interleave_dc = + (cinfo->max_h_samp_factor == 1 && cinfo->max_v_samp_factor == 1); + if (level == 0) { + progressive_mode.push_back({0, 63, 0, 0, true}); + } else if (level == 1) { + progressive_mode.push_back({0, 0, 0, 0, interleave_dc}); + progressive_mode.push_back({1, 63, 0, 1, false}); + progressive_mode.push_back({1, 63, 1, 0, false}); + } else { + progressive_mode.push_back({0, 0, 0, 0, interleave_dc}); + progressive_mode.push_back({1, 2, 0, 0, false}); + progressive_mode.push_back({3, 63, 0, 2, false}); + progressive_mode.push_back({3, 63, 2, 1, false}); + progressive_mode.push_back({3, 63, 1, 0, false}); + } + + cinfo->script_space_size = 0; + for (const auto& scan : progressive_mode) { + int comps = scan.interleaved ? MAX_COMPS_IN_SCAN : 1; + cinfo->script_space_size += DivCeil(cinfo->num_components, comps); + } + cinfo->script_space = + Allocate(cinfo, cinfo->script_space_size); + + jpeg_scan_info* next_scan = cinfo->script_space; + for (const auto& scan : progressive_mode) { + int comps = scan.interleaved ? MAX_COMPS_IN_SCAN : 1; + for (int c = 0; c < cinfo->num_components; c += comps) { + next_scan->Ss = scan.Ss; + next_scan->Se = scan.Se; + next_scan->Ah = scan.Ah; + next_scan->Al = scan.Al; + next_scan->comps_in_scan = std::min(comps, cinfo->num_components - c); + for (int j = 0; j < next_scan->comps_in_scan; ++j) { + next_scan->component_index[j] = c + j; + } + ++next_scan; + } + } + JPEGLI_CHECK(next_scan - cinfo->script_space == cinfo->script_space_size); + cinfo->scan_info = cinfo->script_space; + cinfo->num_scans = cinfo->script_space_size; +} + +void ValidateScanScript(j_compress_ptr cinfo) { + // Mask of coefficient bits defined by the scan script, for each component + // and coefficient index. + uint16_t comp_mask[kMaxComponents][DCTSIZE2] = {}; + static constexpr int kMaxRefinementBit = 10; + + for (int i = 0; i < cinfo->num_scans; ++i) { + const jpeg_scan_info& si = cinfo->scan_info[i]; + if (si.comps_in_scan < 1 || si.comps_in_scan > MAX_COMPS_IN_SCAN) { + JPEGLI_ERROR("Invalid number of components in scan %d", si.comps_in_scan); + } + int last_ci = -1; + for (int j = 0; j < si.comps_in_scan; ++j) { + int ci = si.component_index[j]; + if (ci < 0 || ci >= cinfo->num_components) { + JPEGLI_ERROR("Invalid component index %d in scan", ci); + } else if (ci == last_ci) { + JPEGLI_ERROR("Duplicate component index %d in scan", ci); + } else if (ci < last_ci) { + JPEGLI_ERROR("Out of order component index %d in scan", ci); + } + last_ci = ci; + } + if (si.Ss < 0 || si.Se < si.Ss || si.Se >= DCTSIZE2) { + JPEGLI_ERROR("Invalid spectral range %d .. %d in scan", si.Ss, si.Se); + } + if (si.Ah < 0 || si.Al < 0 || si.Al > kMaxRefinementBit) { + JPEGLI_ERROR("Invalid refinement bits %d/%d", si.Ah, si.Al); + } + if (!cinfo->progressive_mode) { + if (si.Ss != 0 || si.Se != DCTSIZE2 - 1 || si.Ah != 0 || si.Al != 0) { + JPEGLI_ERROR("Invalid scan for sequential mode"); + } + } else { + if (si.Ss == 0 && si.Se != 0) { + JPEGLI_ERROR("DC and AC together in progressive scan"); + } + } + if (si.Ss != 0 && si.comps_in_scan != 1) { + JPEGLI_ERROR("Interleaved AC only scan."); + } + for (int j = 0; j < si.comps_in_scan; ++j) { + int ci = si.component_index[j]; + if (si.Ss != 0 && comp_mask[ci][0] == 0) { + JPEGLI_ERROR("AC before DC in component %d of scan", ci); + } + for (int k = si.Ss; k <= si.Se; ++k) { + if (comp_mask[ci][k] == 0) { + if (si.Ah != 0) { + JPEGLI_ERROR("Invalid first scan refinement bit"); + } + comp_mask[ci][k] = ((0xffff << si.Al) & 0xffff); + } else { + if (comp_mask[ci][k] != ((0xffff << si.Ah) & 0xffff) || + si.Al != si.Ah - 1) { + JPEGLI_ERROR("Invalid refinement bit progression."); + } + comp_mask[ci][k] |= 1 << si.Al; + } + } + } + if (si.comps_in_scan > 1) { + size_t mcu_size = 0; + for (int j = 0; j < si.comps_in_scan; ++j) { + int ci = si.component_index[j]; + jpeg_component_info* comp = &cinfo->comp_info[ci]; + mcu_size += comp->h_samp_factor * comp->v_samp_factor; + } + if (mcu_size > C_MAX_BLOCKS_IN_MCU) { + JPEGLI_ERROR("MCU size too big"); + } + } + } + for (int c = 0; c < cinfo->num_components; ++c) { + for (int k = 0; k < DCTSIZE2; ++k) { + if (comp_mask[c][k] != 0xffff) { + JPEGLI_ERROR("Incomplete scan of component %d and frequency %d", c, k); + } + } + } +} + +void ProcessCompressionParams(j_compress_ptr cinfo) { + if (cinfo->dest == nullptr) { + JPEGLI_ERROR("Missing destination."); + } + if (cinfo->image_width < 1 || cinfo->image_height < 1 || + cinfo->input_components < 1) { + JPEGLI_ERROR("Empty input image."); + } + if (cinfo->image_width > static_cast(JPEG_MAX_DIMENSION) || + cinfo->image_height > static_cast(JPEG_MAX_DIMENSION) || + cinfo->input_components > static_cast(kMaxComponents)) { + JPEGLI_ERROR("Input image too big."); + } + if (cinfo->num_components < 1 || + cinfo->num_components > static_cast(kMaxComponents)) { + JPEGLI_ERROR("Invalid number of components."); + } + if (cinfo->data_precision != kJpegPrecision) { + JPEGLI_ERROR("Invalid data precision"); + } + if (cinfo->arith_code) { + JPEGLI_ERROR("Arithmetic coding is not implemented."); + } + if (cinfo->CCIR601_sampling) { + JPEGLI_ERROR("CCIR601 sampling is not implemented."); + } + if (cinfo->restart_interval > 65535u) { + JPEGLI_ERROR("Restart interval too big"); + } + if (cinfo->smoothing_factor < 0 || cinfo->smoothing_factor > 100) { + JPEGLI_ERROR("Invalid smoothing factor %d", cinfo->smoothing_factor); + } + jpeg_comp_master* m = cinfo->master; + cinfo->max_h_samp_factor = cinfo->max_v_samp_factor = 1; + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + if (comp->component_index != c) { + JPEGLI_ERROR("Invalid component index"); + } + for (int j = 0; j < c; ++j) { + if (cinfo->comp_info[j].component_id == comp->component_id) { + JPEGLI_ERROR("Duplicate component id %d", comp->component_id); + } + } + if (comp->h_samp_factor <= 0 || comp->v_samp_factor <= 0 || + comp->h_samp_factor > MAX_SAMP_FACTOR || + comp->v_samp_factor > MAX_SAMP_FACTOR) { + JPEGLI_ERROR("Invalid sampling factor %d x %d", comp->h_samp_factor, + comp->v_samp_factor); + } + if (cinfo->num_components == 1) { + // Force samp factors to 1x1 for single-component images. + comp->h_samp_factor = comp->v_samp_factor = 1; + } + cinfo->max_h_samp_factor = + std::max(comp->h_samp_factor, cinfo->max_h_samp_factor); + cinfo->max_v_samp_factor = + std::max(comp->v_samp_factor, cinfo->max_v_samp_factor); + } + size_t iMCU_width = DCTSIZE * cinfo->max_h_samp_factor; + size_t iMCU_height = DCTSIZE * cinfo->max_v_samp_factor; + size_t total_iMCU_cols = DivCeil(cinfo->image_width, iMCU_width); + cinfo->total_iMCU_rows = DivCeil(cinfo->image_height, iMCU_height); + m->xsize_blocks = total_iMCU_cols * cinfo->max_h_samp_factor; + m->ysize_blocks = cinfo->total_iMCU_rows * cinfo->max_v_samp_factor; + + size_t blocks_per_iMCU = 0; + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + if (cinfo->max_h_samp_factor % comp->h_samp_factor != 0 || + cinfo->max_v_samp_factor % comp->v_samp_factor != 0) { + JPEGLI_ERROR("Non-integral sampling ratios are not supported."); + } + m->h_factor[c] = cinfo->max_h_samp_factor / comp->h_samp_factor; + m->v_factor[c] = cinfo->max_v_samp_factor / comp->v_samp_factor; + comp->downsampled_width = DivCeil(cinfo->image_width, m->h_factor[c]); + comp->downsampled_height = DivCeil(cinfo->image_height, m->v_factor[c]); + comp->width_in_blocks = DivCeil(comp->downsampled_width, DCTSIZE); + comp->height_in_blocks = DivCeil(comp->downsampled_height, DCTSIZE); + blocks_per_iMCU += comp->h_samp_factor * comp->v_samp_factor; + } + m->blocks_per_iMCU_row = total_iMCU_cols * blocks_per_iMCU; + // Disable adaptive quantization for subsampled luma channel. + int y_channel = cinfo->jpeg_color_space == JCS_RGB ? 1 : 0; + jpeg_component_info* y_comp = &cinfo->comp_info[y_channel]; + if (y_comp->h_samp_factor != cinfo->max_h_samp_factor || + y_comp->v_samp_factor != cinfo->max_v_samp_factor) { + m->use_adaptive_quantization = false; + } + if (cinfo->scan_info == nullptr) { + SetDefaultScanScript(cinfo); + } + cinfo->progressive_mode = TO_JXL_BOOL(cinfo->scan_info->Ss != 0 || + cinfo->scan_info->Se != DCTSIZE2 - 1); + ValidateScanScript(cinfo); + m->scan_token_info = + Allocate(cinfo, cinfo->num_scans, JPOOL_IMAGE); + memset(m->scan_token_info, 0, cinfo->num_scans * sizeof(ScanTokenInfo)); + m->ac_ctx_offset = Allocate(cinfo, cinfo->num_scans, JPOOL_IMAGE); + size_t num_ac_contexts = 0; + for (int i = 0; i < cinfo->num_scans; ++i) { + const jpeg_scan_info* scan_info = &cinfo->scan_info[i]; + m->ac_ctx_offset[i] = 4 + num_ac_contexts; + if (scan_info->Se > 0) { + num_ac_contexts += scan_info->comps_in_scan; + } + if (num_ac_contexts > 252) { + JPEGLI_ERROR("Too many AC scans in image"); + } + ScanTokenInfo* sti = &m->scan_token_info[i]; + if (scan_info->comps_in_scan == 1) { + int comp_idx = scan_info->component_index[0]; + jpeg_component_info* comp = &cinfo->comp_info[comp_idx]; + sti->MCUs_per_row = comp->width_in_blocks; + sti->MCU_rows_in_scan = comp->height_in_blocks; + sti->blocks_in_MCU = 1; + } else { + sti->MCUs_per_row = + DivCeil(cinfo->image_width, DCTSIZE * cinfo->max_h_samp_factor); + sti->MCU_rows_in_scan = + DivCeil(cinfo->image_height, DCTSIZE * cinfo->max_v_samp_factor); + sti->blocks_in_MCU = 0; + for (int j = 0; j < scan_info->comps_in_scan; ++j) { + int comp_idx = scan_info->component_index[j]; + jpeg_component_info* comp = &cinfo->comp_info[comp_idx]; + sti->blocks_in_MCU += comp->h_samp_factor * comp->v_samp_factor; + } + } + size_t num_MCUs = sti->MCU_rows_in_scan * sti->MCUs_per_row; + sti->num_blocks = num_MCUs * sti->blocks_in_MCU; + if (cinfo->restart_in_rows <= 0) { + sti->restart_interval = cinfo->restart_interval; + } else { + sti->restart_interval = + std::min(sti->MCUs_per_row * cinfo->restart_in_rows, 65535u); + } + sti->num_restarts = sti->restart_interval > 0 + ? DivCeil(num_MCUs, sti->restart_interval) + : 1; + sti->restarts = Allocate(cinfo, sti->num_restarts, JPOOL_IMAGE); + } + m->num_contexts = 4 + num_ac_contexts; +} + +bool IsStreamingSupported(j_compress_ptr cinfo) { + if (cinfo->global_state == kEncWriteCoeffs) { + return false; + } + // TODO(szabadka) Remove this restriction. + if (cinfo->restart_interval > 0 || cinfo->restart_in_rows > 0) { + return false; + } + if (cinfo->num_scans > 1) { + return false; + } + if (cinfo->master->psnr_target > 0) { + return false; + } + return true; +} + +void AllocateBuffers(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + memset(m->last_dc_coeff, 0, sizeof(m->last_dc_coeff)); + if (!IsStreamingSupported(cinfo) || cinfo->optimize_coding) { + int ysize_blocks = DivCeil(cinfo->image_height, DCTSIZE); + int num_arrays = cinfo->num_scans * ysize_blocks; + m->token_arrays = Allocate(cinfo, num_arrays, JPOOL_IMAGE); + m->cur_token_array = 0; + memset(m->token_arrays, 0, num_arrays * sizeof(TokenArray)); + m->num_tokens = 0; + m->total_num_tokens = 0; + } + if (cinfo->global_state == kEncWriteCoeffs) { + return; + } + size_t iMCU_width = DCTSIZE * cinfo->max_h_samp_factor; + size_t iMCU_height = DCTSIZE * cinfo->max_v_samp_factor; + size_t total_iMCU_cols = DivCeil(cinfo->image_width, iMCU_width); + size_t xsize_full = total_iMCU_cols * iMCU_width; + size_t ysize_full = 3 * iMCU_height; + if (!cinfo->raw_data_in) { + int num_all_components = + std::max(cinfo->input_components, cinfo->num_components); + for (int c = 0; c < num_all_components; ++c) { + m->input_buffer[c].Allocate(cinfo, ysize_full, xsize_full); + } + } + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + size_t xsize = total_iMCU_cols * comp->h_samp_factor * DCTSIZE; + size_t ysize = 3 * comp->v_samp_factor * DCTSIZE; + if (cinfo->raw_data_in) { + m->input_buffer[c].Allocate(cinfo, ysize, xsize); + } + m->smooth_input[c] = &m->input_buffer[c]; + if (!cinfo->raw_data_in && cinfo->smoothing_factor) { + m->smooth_input[c] = Allocate>(cinfo, 1, JPOOL_IMAGE); + m->smooth_input[c]->Allocate(cinfo, ysize_full, xsize_full); + } + m->raw_data[c] = m->smooth_input[c]; + if (!cinfo->raw_data_in && (m->h_factor[c] > 1 || m->v_factor[c] > 1)) { + m->raw_data[c] = Allocate>(cinfo, 1, JPOOL_IMAGE); + m->raw_data[c]->Allocate(cinfo, ysize, xsize); + } + m->quant_mul[c] = Allocate(cinfo, DCTSIZE2, JPOOL_IMAGE_ALIGNED); + } + m->dct_buffer = Allocate(cinfo, 2 * DCTSIZE2, JPOOL_IMAGE_ALIGNED); + m->block_tmp = Allocate(cinfo, DCTSIZE2 * 4, JPOOL_IMAGE_ALIGNED); + if (!IsStreamingSupported(cinfo)) { + m->coeff_buffers = + Allocate(cinfo, cinfo->num_components, JPOOL_IMAGE); + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + const size_t xsize_blocks = comp->width_in_blocks; + const size_t ysize_blocks = comp->height_in_blocks; + m->coeff_buffers[c] = (*cinfo->mem->request_virt_barray)( + reinterpret_cast(cinfo), JPOOL_IMAGE, + /*pre_zero=*/FALSE, xsize_blocks, ysize_blocks, comp->v_samp_factor); + } + } + if (m->use_adaptive_quantization) { + int y_channel = cinfo->jpeg_color_space == JCS_RGB ? 1 : 0; + jpeg_component_info* y_comp = &cinfo->comp_info[y_channel]; + const size_t xsize_blocks = y_comp->width_in_blocks; + const size_t vecsize = VectorSize(); + const size_t xsize_padded = DivCeil(2 * xsize_blocks, vecsize) * vecsize; + m->diff_buffer = + Allocate(cinfo, xsize_blocks * DCTSIZE + 8, JPOOL_IMAGE_ALIGNED); + m->fuzzy_erosion_tmp.Allocate(cinfo, 2, xsize_padded); + m->pre_erosion.Allocate(cinfo, 6 * cinfo->max_v_samp_factor, xsize_padded); + size_t qf_height = cinfo->max_v_samp_factor; + if (m->psnr_target > 0) { + qf_height *= cinfo->total_iMCU_rows; + } + m->quant_field.Allocate(cinfo, qf_height, xsize_blocks); + } else { + m->quant_field.Allocate(cinfo, 1, m->xsize_blocks); + m->quant_field.FillRow(0, 0, m->xsize_blocks); + } + for (int c = 0; c < cinfo->num_components; ++c) { + m->zero_bias_offset[c] = + Allocate(cinfo, DCTSIZE2, JPOOL_IMAGE_ALIGNED); + m->zero_bias_mul[c] = Allocate(cinfo, DCTSIZE2, JPOOL_IMAGE_ALIGNED); + memset(m->zero_bias_mul[c], 0, DCTSIZE2 * sizeof(float)); + memset(m->zero_bias_offset[c], 0, DCTSIZE2 * sizeof(float)); + } +} + +void InitProgressMonitor(j_compress_ptr cinfo) { + if (cinfo->progress == nullptr) { + return; + } + if (IsStreamingSupported(cinfo)) { + // We have only one input pass. + cinfo->progress->total_passes = 1; + } else { + // We have one input pass, a histogram pass for each scan, and an encode + // pass for each scan. + cinfo->progress->total_passes = 1 + 2 * cinfo->num_scans; + } +} + +// Common setup code between streaming and transcoding code paths. Called in +// both jpegli_start_compress() and jpegli_write_coefficients(). +void InitCompress(j_compress_ptr cinfo, boolean write_all_tables) { + jpeg_comp_master* m = cinfo->master; + (*cinfo->err->reset_error_mgr)(reinterpret_cast(cinfo)); + ProcessCompressionParams(cinfo); + InitProgressMonitor(cinfo); + AllocateBuffers(cinfo); + if (cinfo->global_state != kEncWriteCoeffs) { + ChooseInputMethod(cinfo); + if (!cinfo->raw_data_in) { + ChooseColorTransform(cinfo); + ChooseDownsampleMethods(cinfo); + } + QuantPass pass = m->psnr_target > 0 ? QuantPass::SEARCH_FIRST_PASS + : QuantPass::NO_SEARCH; + InitQuantizer(cinfo, pass); + } + if (write_all_tables) { + jpegli_suppress_tables(cinfo, FALSE); + } + if (!cinfo->optimize_coding && !cinfo->progressive_mode) { + CopyHuffmanTables(cinfo); + InitEntropyCoder(cinfo); + } + (*cinfo->dest->init_destination)(cinfo); + WriteFileHeader(cinfo); + JpegBitWriterInit(cinfo); + m->next_iMCU_row = 0; + m->last_restart_interval = 0; + m->next_dht_index = 0; +} + +// +// Input streaming +// + +void ProgressMonitorInputPass(j_compress_ptr cinfo) { + if (cinfo->progress == nullptr) { + return; + } + cinfo->progress->completed_passes = 0; + cinfo->progress->pass_counter = cinfo->next_scanline; + cinfo->progress->pass_limit = cinfo->image_height; + (*cinfo->progress->progress_monitor)(reinterpret_cast(cinfo)); +} + +void ReadInputRow(j_compress_ptr cinfo, const uint8_t* scanline, + float* row[kMaxComponents]) { + jpeg_comp_master* m = cinfo->master; + int num_all_components = + std::max(cinfo->input_components, cinfo->num_components); + for (int c = 0; c < num_all_components; ++c) { + row[c] = m->input_buffer[c].Row(m->next_input_row); + } + ++m->next_input_row; + if (scanline == nullptr) { + for (int c = 0; c < cinfo->input_components; ++c) { + memset(row[c], 0, cinfo->image_width * sizeof(row[c][0])); + } + return; + } + (*m->input_method)(scanline, cinfo->image_width, row); +} + +void PadInputBuffer(j_compress_ptr cinfo, float* row[kMaxComponents]) { + jpeg_comp_master* m = cinfo->master; + const size_t len0 = cinfo->image_width; + const size_t len1 = m->xsize_blocks * DCTSIZE; + for (int c = 0; c < cinfo->num_components; ++c) { + // Pad row to a multiple of the iMCU width, plus create a border of 1 + // repeated pixel for adaptive quant field calculation. + float last_val = row[c][len0 - 1]; + for (size_t x = len0; x <= len1; ++x) { + row[c][x] = last_val; + } + row[c][-1] = row[c][0]; + } + if (m->next_input_row == cinfo->image_height) { + size_t num_rows = m->ysize_blocks * DCTSIZE - cinfo->image_height; + for (size_t i = 0; i < num_rows; ++i) { + for (int c = 0; c < cinfo->num_components; ++c) { + float* dest = m->input_buffer[c].Row(m->next_input_row) - 1; + memcpy(dest, row[c] - 1, (len1 + 2) * sizeof(dest[0])); + } + ++m->next_input_row; + } + } +} + +void ProcessiMCURow(j_compress_ptr cinfo) { + JPEGLI_CHECK(cinfo->master->next_iMCU_row < cinfo->total_iMCU_rows); + if (!cinfo->raw_data_in) { + ApplyInputSmoothing(cinfo); + DownsampleInputBuffer(cinfo); + } + ComputeAdaptiveQuantField(cinfo); + if (IsStreamingSupported(cinfo)) { + if (cinfo->optimize_coding) { + ComputeTokensForiMCURow(cinfo); + } else { + WriteiMCURow(cinfo); + } + } else { + ComputeCoefficientsForiMCURow(cinfo); + } + ++cinfo->master->next_iMCU_row; +} + +void ProcessiMCURows(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + size_t iMCU_height = DCTSIZE * cinfo->max_v_samp_factor; + // To have context rows both above and below the current iMCU row, we delay + // processing the first iMCU row and process two iMCU rows after we receive + // the last input row. + if (m->next_input_row % iMCU_height == 0 && m->next_input_row > iMCU_height) { + ProcessiMCURow(cinfo); + } + if (m->next_input_row >= cinfo->image_height) { + ProcessiMCURow(cinfo); + } +} + +// +// Non-streaming part +// + +void ZigZagShuffleBlocks(j_compress_ptr cinfo) { + JCOEF tmp[DCTSIZE2]; + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + for (JDIMENSION by = 0; by < comp->height_in_blocks; ++by) { + JBLOCKARRAY blocks = GetBlockRow(cinfo, c, by); + for (JDIMENSION bx = 0; bx < comp->width_in_blocks; ++bx) { + JCOEF* block = &blocks[0][bx][0]; + for (int k = 0; k < DCTSIZE2; ++k) { + tmp[k] = block[kJPEGNaturalOrder[k]]; + } + memcpy(block, tmp, sizeof(tmp)); + } + } + } +} + +} // namespace jpegli + +// +// Parameter setup +// + +void jpegli_CreateCompress(j_compress_ptr cinfo, int version, + size_t structsize) { + cinfo->mem = nullptr; + if (structsize != sizeof(*cinfo)) { + JPEGLI_ERROR("jpegli_compress_struct has wrong size."); + } + jpegli::InitMemoryManager(reinterpret_cast(cinfo)); + cinfo->progress = nullptr; + cinfo->is_decompressor = FALSE; + cinfo->global_state = jpegli::kEncStart; + cinfo->dest = nullptr; + cinfo->image_width = 0; + cinfo->image_height = 0; + cinfo->input_components = 0; + cinfo->in_color_space = JCS_UNKNOWN; + cinfo->input_gamma = 1.0f; + cinfo->num_components = 0; + cinfo->jpeg_color_space = JCS_UNKNOWN; + cinfo->comp_info = nullptr; + for (auto& quant_tbl_ptr : cinfo->quant_tbl_ptrs) { + quant_tbl_ptr = nullptr; + } + for (int i = 0; i < NUM_HUFF_TBLS; ++i) { + cinfo->dc_huff_tbl_ptrs[i] = nullptr; + cinfo->ac_huff_tbl_ptrs[i] = nullptr; + } + memset(cinfo->arith_dc_L, 0, sizeof(cinfo->arith_dc_L)); + memset(cinfo->arith_dc_U, 0, sizeof(cinfo->arith_dc_U)); + memset(cinfo->arith_ac_K, 0, sizeof(cinfo->arith_ac_K)); + cinfo->write_Adobe_marker = FALSE; + cinfo->master = jpegli::Allocate(cinfo, 1); + jpegli::InitializeCompressParams(cinfo); + cinfo->master->force_baseline = true; + cinfo->master->xyb_mode = false; + cinfo->master->cicp_transfer_function = 2; // unknown transfer function code + cinfo->master->use_std_tables = false; + cinfo->master->use_adaptive_quantization = true; + cinfo->master->progressive_level = jpegli::kDefaultProgressiveLevel; + cinfo->master->data_type = JPEGLI_TYPE_UINT8; + cinfo->master->endianness = JPEGLI_NATIVE_ENDIAN; + cinfo->master->coeff_buffers = nullptr; +} + +void jpegli_set_xyb_mode(j_compress_ptr cinfo) { + CheckState(cinfo, jpegli::kEncStart); + cinfo->master->xyb_mode = true; +} + +void jpegli_set_cicp_transfer_function(j_compress_ptr cinfo, int code) { + CheckState(cinfo, jpegli::kEncStart); + cinfo->master->cicp_transfer_function = code; +} + +void jpegli_set_defaults(j_compress_ptr cinfo) { + CheckState(cinfo, jpegli::kEncStart); + jpegli::InitializeCompressParams(cinfo); + jpegli_default_colorspace(cinfo); + jpegli_set_quality(cinfo, 90, TRUE); + jpegli_set_progressive_level(cinfo, jpegli::kDefaultProgressiveLevel); + jpegli::AddStandardHuffmanTables(reinterpret_cast(cinfo), + /*is_dc=*/false); + jpegli::AddStandardHuffmanTables(reinterpret_cast(cinfo), + /*is_dc=*/true); +} + +void jpegli_default_colorspace(j_compress_ptr cinfo) { + CheckState(cinfo, jpegli::kEncStart); + if (cinfo->in_color_space == JCS_RGB && cinfo->master->xyb_mode) { + jpegli_set_colorspace(cinfo, JCS_RGB); + return; + } + switch (cinfo->in_color_space) { + case JCS_GRAYSCALE: + jpegli_set_colorspace(cinfo, JCS_GRAYSCALE); + break; + case JCS_RGB: +#ifdef JCS_EXTENSIONS + case JCS_EXT_RGB: + case JCS_EXT_BGR: + case JCS_EXT_RGBX: + case JCS_EXT_BGRX: + case JCS_EXT_XRGB: + case JCS_EXT_XBGR: +#endif +#if JCS_ALPHA_EXTENSIONS + case JCS_EXT_RGBA: + case JCS_EXT_BGRA: + case JCS_EXT_ARGB: + case JCS_EXT_ABGR: +#endif + jpegli_set_colorspace(cinfo, JCS_YCbCr); + break; + case JCS_YCbCr: + jpegli_set_colorspace(cinfo, JCS_YCbCr); + break; + case JCS_CMYK: + jpegli_set_colorspace(cinfo, JCS_CMYK); + break; + case JCS_YCCK: + jpegli_set_colorspace(cinfo, JCS_YCCK); + break; + case JCS_UNKNOWN: + jpegli_set_colorspace(cinfo, JCS_UNKNOWN); + break; + default: + JPEGLI_ERROR("Unsupported input colorspace %d", cinfo->in_color_space); + } +} + +void jpegli_set_colorspace(j_compress_ptr cinfo, J_COLOR_SPACE colorspace) { + CheckState(cinfo, jpegli::kEncStart); + cinfo->jpeg_color_space = colorspace; + switch (colorspace) { + case JCS_GRAYSCALE: + cinfo->num_components = 1; + break; + case JCS_RGB: + case JCS_YCbCr: + cinfo->num_components = 3; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo->num_components = 4; + break; + case JCS_UNKNOWN: + cinfo->num_components = + std::min(jpegli::kMaxComponents, cinfo->input_components); + break; + default: + JPEGLI_ERROR("Unsupported jpeg colorspace %d", colorspace); + } + // Adobe marker is only needed to distinguish CMYK and YCCK JPEGs. + cinfo->write_Adobe_marker = TO_JXL_BOOL(cinfo->jpeg_color_space == JCS_YCCK); + if (cinfo->comp_info == nullptr) { + cinfo->comp_info = + jpegli::Allocate(cinfo, MAX_COMPONENTS); + } + memset(cinfo->comp_info, 0, + jpegli::kMaxComponents * sizeof(jpeg_component_info)); + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + comp->component_index = c; + comp->component_id = c + 1; + comp->h_samp_factor = 1; + comp->v_samp_factor = 1; + comp->quant_tbl_no = 0; + comp->dc_tbl_no = 0; + comp->ac_tbl_no = 0; + } + if (colorspace == JCS_RGB) { + cinfo->comp_info[0].component_id = 'R'; + cinfo->comp_info[1].component_id = 'G'; + cinfo->comp_info[2].component_id = 'B'; + if (cinfo->master->xyb_mode) { + // Subsample blue channel. + cinfo->comp_info[0].h_samp_factor = cinfo->comp_info[0].v_samp_factor = 2; + cinfo->comp_info[1].h_samp_factor = cinfo->comp_info[1].v_samp_factor = 2; + cinfo->comp_info[2].h_samp_factor = cinfo->comp_info[2].v_samp_factor = 1; + // Use separate quantization tables for each component + cinfo->comp_info[1].quant_tbl_no = 1; + cinfo->comp_info[2].quant_tbl_no = 2; + } + } else if (colorspace == JCS_CMYK) { + cinfo->comp_info[0].component_id = 'C'; + cinfo->comp_info[1].component_id = 'M'; + cinfo->comp_info[2].component_id = 'Y'; + cinfo->comp_info[3].component_id = 'K'; + } else if (colorspace == JCS_YCbCr || colorspace == JCS_YCCK) { + // Use separate quantization and Huffman tables for luma and chroma + cinfo->comp_info[1].quant_tbl_no = 1; + cinfo->comp_info[2].quant_tbl_no = 1; + cinfo->comp_info[1].dc_tbl_no = cinfo->comp_info[1].ac_tbl_no = 1; + cinfo->comp_info[2].dc_tbl_no = cinfo->comp_info[2].ac_tbl_no = 1; + // Use chroma subsampling by default + cinfo->comp_info[0].h_samp_factor = cinfo->comp_info[0].v_samp_factor = 2; + if (colorspace == JCS_YCCK) { + cinfo->comp_info[3].h_samp_factor = cinfo->comp_info[3].v_samp_factor = 2; + } + } +} + +void jpegli_set_distance(j_compress_ptr cinfo, float distance, + boolean force_baseline) { + CheckState(cinfo, jpegli::kEncStart); + cinfo->master->force_baseline = FROM_JXL_BOOL(force_baseline); + float distances[NUM_QUANT_TBLS] = {distance, distance, distance}; + jpegli::SetQuantMatrices(cinfo, distances, /*add_two_chroma_tables=*/true); +} + +float jpegli_quality_to_distance(int quality) { + return (quality >= 100 ? 0.01f + : quality >= 30 ? 0.1f + (100 - quality) * 0.09f + : 53.0f / 3000.0f * quality * quality - + 23.0f / 20.0f * quality + 25.0f); +} + +void jpegli_set_psnr(j_compress_ptr cinfo, float psnr, float tolerance, + float min_distance, float max_distance) { + CheckState(cinfo, jpegli::kEncStart); + cinfo->master->psnr_target = psnr; + cinfo->master->psnr_tolerance = tolerance; + cinfo->master->min_distance = min_distance; + cinfo->master->max_distance = max_distance; +} + +void jpegli_set_quality(j_compress_ptr cinfo, int quality, + boolean force_baseline) { + CheckState(cinfo, jpegli::kEncStart); + cinfo->master->force_baseline = FROM_JXL_BOOL(force_baseline); + float distance = jpegli_quality_to_distance(quality); + float distances[NUM_QUANT_TBLS] = {distance, distance, distance}; + jpegli::SetQuantMatrices(cinfo, distances, /*add_two_chroma_tables=*/false); +} + +void jpegli_set_linear_quality(j_compress_ptr cinfo, int scale_factor, + boolean force_baseline) { + CheckState(cinfo, jpegli::kEncStart); + cinfo->master->force_baseline = FROM_JXL_BOOL(force_baseline); + float distance = jpegli::LinearQualityToDistance(scale_factor); + float distances[NUM_QUANT_TBLS] = {distance, distance, distance}; + jpegli::SetQuantMatrices(cinfo, distances, /*add_two_chroma_tables=*/false); +} + +#if JPEG_LIB_VERSION >= 70 +void jpegli_default_qtables(j_compress_ptr cinfo, boolean force_baseline) { + CheckState(cinfo, jpegli::kEncStart); + cinfo->master->force_baseline = force_baseline; + float distances[NUM_QUANT_TBLS]; + for (int i = 0; i < NUM_QUANT_TBLS; ++i) { + distances[i] = jpegli::LinearQualityToDistance(cinfo->q_scale_factor[i]); + } + jpegli::SetQuantMatrices(cinfo, distances, /*add_two_chroma_tables=*/false); +} +#endif + +int jpegli_quality_scaling(int quality) { + quality = std::min(100, std::max(1, quality)); + return quality < 50 ? 5000 / quality : 200 - 2 * quality; +} + +void jpegli_use_standard_quant_tables(j_compress_ptr cinfo) { + CheckState(cinfo, jpegli::kEncStart); + cinfo->master->use_std_tables = true; +} + +void jpegli_add_quant_table(j_compress_ptr cinfo, int which_tbl, + const unsigned int* basic_table, int scale_factor, + boolean force_baseline) { + CheckState(cinfo, jpegli::kEncStart); + if (which_tbl < 0 || which_tbl > NUM_QUANT_TBLS) { + JPEGLI_ERROR("Invalid quant table index %d", which_tbl); + } + if (cinfo->quant_tbl_ptrs[which_tbl] == nullptr) { + cinfo->quant_tbl_ptrs[which_tbl] = + jpegli_alloc_quant_table(reinterpret_cast(cinfo)); + } + int max_qval = force_baseline ? 255 : 32767U; + JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[which_tbl]; + for (int k = 0; k < DCTSIZE2; ++k) { + int qval = (basic_table[k] * scale_factor + 50) / 100; + qval = std::max(1, std::min(qval, max_qval)); + quant_table->quantval[k] = qval; + } + quant_table->sent_table = FALSE; +} + +void jpegli_enable_adaptive_quantization(j_compress_ptr cinfo, boolean value) { + CheckState(cinfo, jpegli::kEncStart); + cinfo->master->use_adaptive_quantization = FROM_JXL_BOOL(value); +} + +void jpegli_simple_progression(j_compress_ptr cinfo) { + CheckState(cinfo, jpegli::kEncStart); + jpegli_set_progressive_level(cinfo, 2); +} + +void jpegli_set_progressive_level(j_compress_ptr cinfo, int level) { + CheckState(cinfo, jpegli::kEncStart); + if (level < 0) { + JPEGLI_ERROR("Invalid progressive level %d", level); + } + cinfo->master->progressive_level = level; +} + +void jpegli_set_input_format(j_compress_ptr cinfo, JpegliDataType data_type, + JpegliEndianness endianness) { + CheckState(cinfo, jpegli::kEncStart); + switch (data_type) { + case JPEGLI_TYPE_UINT8: + case JPEGLI_TYPE_UINT16: + case JPEGLI_TYPE_FLOAT: + cinfo->master->data_type = data_type; + break; + default: + JPEGLI_ERROR("Unsupported data type %d", data_type); + } + switch (endianness) { + case JPEGLI_NATIVE_ENDIAN: + case JPEGLI_LITTLE_ENDIAN: + case JPEGLI_BIG_ENDIAN: + cinfo->master->endianness = endianness; + break; + default: + JPEGLI_ERROR("Unsupported endianness %d", endianness); + } +} + +#if JPEG_LIB_VERSION >= 70 +void jpegli_calc_jpeg_dimensions(j_compress_ptr cinfo) { + // Since input scaling is not supported, we just copy the image dimensions. + cinfo->jpeg_width = cinfo->image_width; + cinfo->jpeg_height = cinfo->image_height; +} +#endif + +void jpegli_copy_critical_parameters(j_decompress_ptr srcinfo, + j_compress_ptr dstinfo) { + CheckState(dstinfo, jpegli::kEncStart); + // Image parameters. + dstinfo->image_width = srcinfo->image_width; + dstinfo->image_height = srcinfo->image_height; + dstinfo->input_components = srcinfo->num_components; + dstinfo->in_color_space = srcinfo->jpeg_color_space; + dstinfo->input_gamma = srcinfo->output_gamma; + // Compression parameters. + jpegli_set_defaults(dstinfo); + jpegli_set_colorspace(dstinfo, srcinfo->jpeg_color_space); + if (dstinfo->num_components != srcinfo->num_components) { + const auto& cinfo = dstinfo; + JPEGLI_ERROR("Mismatch between src colorspace and components"); + } + dstinfo->data_precision = srcinfo->data_precision; + dstinfo->CCIR601_sampling = srcinfo->CCIR601_sampling; + dstinfo->JFIF_major_version = srcinfo->JFIF_major_version; + dstinfo->JFIF_minor_version = srcinfo->JFIF_minor_version; + dstinfo->density_unit = srcinfo->density_unit; + dstinfo->X_density = srcinfo->X_density; + dstinfo->Y_density = srcinfo->Y_density; + for (int c = 0; c < dstinfo->num_components; ++c) { + jpeg_component_info* srccomp = &srcinfo->comp_info[c]; + jpeg_component_info* dstcomp = &dstinfo->comp_info[c]; + dstcomp->component_id = srccomp->component_id; + dstcomp->h_samp_factor = srccomp->h_samp_factor; + dstcomp->v_samp_factor = srccomp->v_samp_factor; + dstcomp->quant_tbl_no = srccomp->quant_tbl_no; + } + for (int i = 0; i < NUM_QUANT_TBLS; ++i) { + if (!srcinfo->quant_tbl_ptrs[i]) continue; + if (dstinfo->quant_tbl_ptrs[i] == nullptr) { + dstinfo->quant_tbl_ptrs[i] = jpegli::Allocate(dstinfo, 1); + } + memcpy(dstinfo->quant_tbl_ptrs[i], srcinfo->quant_tbl_ptrs[i], + sizeof(JQUANT_TBL)); + dstinfo->quant_tbl_ptrs[i]->sent_table = FALSE; + } +} + +void jpegli_suppress_tables(j_compress_ptr cinfo, boolean suppress) { + jpegli::SetSentTableFlag(cinfo->quant_tbl_ptrs, NUM_QUANT_TBLS, suppress); + jpegli::SetSentTableFlag(cinfo->dc_huff_tbl_ptrs, NUM_HUFF_TBLS, suppress); + jpegli::SetSentTableFlag(cinfo->ac_huff_tbl_ptrs, NUM_HUFF_TBLS, suppress); +} + +// +// Compressor initialization +// + +void jpegli_start_compress(j_compress_ptr cinfo, boolean write_all_tables) { + CheckState(cinfo, jpegli::kEncStart); + cinfo->global_state = jpegli::kEncHeader; + jpegli::InitCompress(cinfo, write_all_tables); + cinfo->next_scanline = 0; + cinfo->master->next_input_row = 0; +} + +void jpegli_write_coefficients(j_compress_ptr cinfo, + jvirt_barray_ptr* coef_arrays) { + CheckState(cinfo, jpegli::kEncStart); + cinfo->global_state = jpegli::kEncWriteCoeffs; + jpegli::InitCompress(cinfo, /*write_all_tables=*/TRUE); + cinfo->master->coeff_buffers = coef_arrays; + cinfo->next_scanline = cinfo->image_height; + cinfo->master->next_input_row = cinfo->image_height; +} + +void jpegli_write_tables(j_compress_ptr cinfo) { + CheckState(cinfo, jpegli::kEncStart); + if (cinfo->dest == nullptr) { + JPEGLI_ERROR("Missing destination."); + } + jpeg_comp_master* m = cinfo->master; + (*cinfo->err->reset_error_mgr)(reinterpret_cast(cinfo)); + (*cinfo->dest->init_destination)(cinfo); + jpegli::WriteOutput(cinfo, {0xFF, 0xD8}); // SOI + jpegli::EncodeDQT(cinfo, /*write_all_tables=*/true); + jpegli::CopyHuffmanTables(cinfo); + jpegli::EncodeDHT(cinfo, 0, m->num_huffman_tables); + jpegli::WriteOutput(cinfo, {0xFF, 0xD9}); // EOI + (*cinfo->dest->term_destination)(cinfo); + jpegli_suppress_tables(cinfo, TRUE); +} + +// +// Marker writing +// + +void jpegli_write_m_header(j_compress_ptr cinfo, int marker, + unsigned int datalen) { + CheckState(cinfo, jpegli::kEncHeader, jpegli::kEncWriteCoeffs); + if (datalen > jpegli::kMaxBytesInMarker) { + JPEGLI_ERROR("Invalid marker length %u", datalen); + } + if (marker != 0xfe && (marker < 0xe0 || marker > 0xef)) { + JPEGLI_ERROR( + "jpegli_write_m_header: Only APP and COM markers are supported."); + } + std::vector marker_data(4 + datalen); + marker_data[0] = 0xff; + marker_data[1] = marker; + marker_data[2] = (datalen + 2) >> 8; + marker_data[3] = (datalen + 2) & 0xff; + jpegli::WriteOutput(cinfo, marker_data.data(), 4); +} + +void jpegli_write_m_byte(j_compress_ptr cinfo, int val) { + uint8_t data = val; + jpegli::WriteOutput(cinfo, &data, 1); +} + +void jpegli_write_marker(j_compress_ptr cinfo, int marker, + const JOCTET* dataptr, unsigned int datalen) { + jpegli_write_m_header(cinfo, marker, datalen); + jpegli::WriteOutput(cinfo, dataptr, datalen); +} + +void jpegli_write_icc_profile(j_compress_ptr cinfo, const JOCTET* icc_data_ptr, + unsigned int icc_data_len) { + constexpr size_t kMaxIccBytesInMarker = + jpegli::kMaxBytesInMarker - sizeof jpegli::kICCSignature - 2; + const int num_markers = + static_cast(jpegli::DivCeil(icc_data_len, kMaxIccBytesInMarker)); + size_t begin = 0; + for (int current_marker = 0; current_marker < num_markers; ++current_marker) { + const size_t length = std::min(kMaxIccBytesInMarker, icc_data_len - begin); + jpegli_write_m_header( + cinfo, jpegli::kICCMarker, + static_cast(length + sizeof jpegli::kICCSignature + 2)); + for (const unsigned char c : jpegli::kICCSignature) { + jpegli_write_m_byte(cinfo, c); + } + jpegli_write_m_byte(cinfo, current_marker + 1); + jpegli_write_m_byte(cinfo, num_markers); + for (size_t i = 0; i < length; ++i) { + jpegli_write_m_byte(cinfo, icc_data_ptr[begin]); + ++begin; + } + } +} + +// +// Input streaming +// + +JDIMENSION jpegli_write_scanlines(j_compress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION num_lines) { + CheckState(cinfo, jpegli::kEncHeader, jpegli::kEncReadImage); + if (cinfo->raw_data_in) { + JPEGLI_ERROR("jpegli_write_raw_data() must be called for raw data mode."); + } + jpegli::ProgressMonitorInputPass(cinfo); + if (cinfo->global_state == jpegli::kEncHeader && + jpegli::IsStreamingSupported(cinfo) && !cinfo->optimize_coding) { + jpegli::WriteFrameHeader(cinfo); + jpegli::WriteScanHeader(cinfo, 0); + } + cinfo->global_state = jpegli::kEncReadImage; + jpeg_comp_master* m = cinfo->master; + if (num_lines + cinfo->next_scanline > cinfo->image_height) { + num_lines = cinfo->image_height - cinfo->next_scanline; + } + JDIMENSION prev_scanline = cinfo->next_scanline; + size_t input_lag = (std::min(cinfo->image_height, m->next_input_row) - + cinfo->next_scanline); + if (input_lag > num_lines) { + JPEGLI_ERROR("Need at least %u lines to continue", input_lag); + } + if (input_lag > 0) { + if (!jpegli::EmptyBitWriterBuffer(&m->bw)) { + return 0; + } + cinfo->next_scanline += input_lag; + } + float* rows[jpegli::kMaxComponents]; + for (size_t i = input_lag; i < num_lines; ++i) { + jpegli::ReadInputRow(cinfo, scanlines[i], rows); + (*m->color_transform)(rows, cinfo->image_width); + jpegli::PadInputBuffer(cinfo, rows); + jpegli::ProcessiMCURows(cinfo); + if (!jpegli::EmptyBitWriterBuffer(&m->bw)) { + break; + } + ++cinfo->next_scanline; + } + return cinfo->next_scanline - prev_scanline; +} + +JDIMENSION jpegli_write_raw_data(j_compress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION num_lines) { + CheckState(cinfo, jpegli::kEncHeader, jpegli::kEncReadImage); + if (!cinfo->raw_data_in) { + JPEGLI_ERROR("jpegli_write_raw_data(): raw data mode was not set"); + } + jpegli::ProgressMonitorInputPass(cinfo); + if (cinfo->global_state == jpegli::kEncHeader && + jpegli::IsStreamingSupported(cinfo) && !cinfo->optimize_coding) { + jpegli::WriteFrameHeader(cinfo); + jpegli::WriteScanHeader(cinfo, 0); + } + cinfo->global_state = jpegli::kEncReadImage; + jpeg_comp_master* m = cinfo->master; + if (cinfo->next_scanline >= cinfo->image_height) { + return 0; + } + size_t iMCU_height = DCTSIZE * cinfo->max_v_samp_factor; + if (num_lines < iMCU_height) { + JPEGLI_ERROR("Missing input lines, minimum is %u", iMCU_height); + } + if (cinfo->next_scanline < m->next_input_row) { + JPEGLI_CHECK(m->next_input_row - cinfo->next_scanline == iMCU_height); + if (!jpegli::EmptyBitWriterBuffer(&m->bw)) { + return 0; + } + cinfo->next_scanline = m->next_input_row; + return iMCU_height; + } + size_t iMCU_y = m->next_input_row / iMCU_height; + float* rows[jpegli::kMaxComponents]; + for (int c = 0; c < cinfo->num_components; ++c) { + JSAMPARRAY plane = data[c]; + jpeg_component_info* comp = &cinfo->comp_info[c]; + size_t xsize = comp->width_in_blocks * DCTSIZE; + size_t ysize = comp->v_samp_factor * DCTSIZE; + size_t y0 = iMCU_y * ysize; + auto& buffer = m->input_buffer[c]; + for (size_t i = 0; i < ysize; ++i) { + rows[0] = buffer.Row(y0 + i); + if (plane[i] == nullptr) { + memset(rows[0], 0, xsize * sizeof(rows[0][0])); + } else { + (*m->input_method)(plane[i], xsize, rows); + } + // We need a border of 1 repeated pixel for adaptive quant field. + buffer.PadRow(y0 + i, xsize, /*border=*/1); + } + } + m->next_input_row += iMCU_height; + jpegli::ProcessiMCURows(cinfo); + if (!jpegli::EmptyBitWriterBuffer(&m->bw)) { + return 0; + } + cinfo->next_scanline += iMCU_height; + return iMCU_height; +} + +// +// Non-streaming part +// + +void jpegli_finish_compress(j_compress_ptr cinfo) { + CheckState(cinfo, jpegli::kEncReadImage, jpegli::kEncWriteCoeffs); + jpeg_comp_master* m = cinfo->master; + if (cinfo->next_scanline < cinfo->image_height) { + JPEGLI_ERROR("Incomplete image, expected %d rows, got %d", + cinfo->image_height, cinfo->next_scanline); + } + + if (cinfo->global_state == jpegli::kEncWriteCoeffs) { + // Zig-zag shuffle all the blocks. For non-transcoding case it was already + // done in EncodeiMCURow(). + jpegli::ZigZagShuffleBlocks(cinfo); + } + + if (m->psnr_target > 0) { + jpegli::QuantizetoPSNR(cinfo); + } + + const bool tokens_done = jpegli::IsStreamingSupported(cinfo); + const bool bitstream_done = + tokens_done && !FROM_JXL_BOOL(cinfo->optimize_coding); + + if (!tokens_done) { + jpegli::TokenizeJpeg(cinfo); + } + + if (cinfo->optimize_coding || cinfo->progressive_mode) { + jpegli::OptimizeHuffmanCodes(cinfo); + jpegli::InitEntropyCoder(cinfo); + } + + if (!bitstream_done) { + jpegli::WriteFrameHeader(cinfo); + for (int i = 0; i < cinfo->num_scans; ++i) { + jpegli::WriteScanHeader(cinfo, i); + jpegli::WriteScanData(cinfo, i); + } + } else { + JumpToByteBoundary(&m->bw); + if (!EmptyBitWriterBuffer(&m->bw)) { + JPEGLI_ERROR("Output suspension is not supported in finish_compress"); + } + } + + jpegli::WriteOutput(cinfo, {0xFF, 0xD9}); // EOI + (*cinfo->dest->term_destination)(cinfo); + + // Release memory and reset global state. + jpegli_abort_compress(cinfo); +} + +void jpegli_abort_compress(j_compress_ptr cinfo) { + jpegli_abort(reinterpret_cast(cinfo)); +} + +void jpegli_destroy_compress(j_compress_ptr cinfo) { + jpegli_destroy(reinterpret_cast(cinfo)); +} diff --git a/media/libjxl/src/lib/jpegli/encode.h b/media/libjxl/src/lib/jpegli/encode.h new file mode 100644 index 0000000000..54ff9d1f9e --- /dev/null +++ b/media/libjxl/src/lib/jpegli/encode.h @@ -0,0 +1,159 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// This file contains the C API of the encoder part of the libjpegli library, +// which is based on the C API of libjpeg, with the function names changed from +// jpeg_* to jpegli_*, while compressor object definitions are included directly +// from jpeglib.h +// +// Applications can use the libjpegli library in one of the following ways: +// +// (1) Include jpegli/encode.h and/or jpegli/decode.h, update the function +// names of the API and link against libjpegli. +// +// (2) Leave the application code unchanged, but replace the libjpeg.so library +// with the one built by this project that is API- and ABI-compatible with +// libjpeg-turbo's version of libjpeg.so. + +#ifndef LIB_JPEGLI_ENCODE_H_ +#define LIB_JPEGLI_ENCODE_H_ + +#include "lib/jpegli/common.h" +#include "lib/jpegli/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define jpegli_create_compress(cinfo) \ + jpegli_CreateCompress((cinfo), JPEG_LIB_VERSION, \ + (size_t)sizeof(struct jpeg_compress_struct)) +void jpegli_CreateCompress(j_compress_ptr cinfo, int version, + size_t structsize); + +void jpegli_stdio_dest(j_compress_ptr cinfo, FILE* outfile); + +void jpegli_mem_dest(j_compress_ptr cinfo, unsigned char** outbuffer, + unsigned long* outsize /* NOLINT */); + +void jpegli_set_defaults(j_compress_ptr cinfo); + +void jpegli_default_colorspace(j_compress_ptr cinfo); + +void jpegli_set_colorspace(j_compress_ptr cinfo, J_COLOR_SPACE colorspace); + +void jpegli_set_quality(j_compress_ptr cinfo, int quality, + boolean force_baseline); + +void jpegli_set_linear_quality(j_compress_ptr cinfo, int scale_factor, + boolean force_baseline); + +#if JPEG_LIB_VERSION >= 70 +void jpegli_default_qtables(j_compress_ptr cinfo, boolean force_baseline); +#endif + +int jpegli_quality_scaling(int quality); + +void jpegli_add_quant_table(j_compress_ptr cinfo, int which_tbl, + const unsigned int* basic_table, int scale_factor, + boolean force_baseline); + +void jpegli_simple_progression(j_compress_ptr cinfo); + +void jpegli_suppress_tables(j_compress_ptr cinfo, boolean suppress); + +#if JPEG_LIB_VERSION >= 70 +void jpegli_calc_jpeg_dimensions(j_compress_ptr cinfo); +#endif + +void jpegli_copy_critical_parameters(j_decompress_ptr srcinfo, + j_compress_ptr dstinfo); + +void jpegli_write_m_header(j_compress_ptr cinfo, int marker, + unsigned int datalen); + +void jpegli_write_m_byte(j_compress_ptr cinfo, int val); + +void jpegli_write_marker(j_compress_ptr cinfo, int marker, + const JOCTET* dataptr, unsigned int datalen); + +void jpegli_write_icc_profile(j_compress_ptr cinfo, const JOCTET* icc_data_ptr, + unsigned int icc_data_len); + +void jpegli_start_compress(j_compress_ptr cinfo, boolean write_all_tables); + +void jpegli_write_tables(j_compress_ptr cinfo); + +JDIMENSION jpegli_write_scanlines(j_compress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION num_lines); + +JDIMENSION jpegli_write_raw_data(j_compress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION num_lines); + +void jpegli_write_coefficients(j_compress_ptr cinfo, + jvirt_barray_ptr* coef_arrays); + +void jpegli_finish_compress(j_compress_ptr cinfo); + +void jpegli_abort_compress(j_compress_ptr cinfo); + +void jpegli_destroy_compress(j_compress_ptr cinfo); + +// +// New API functions that are not available in libjpeg +// +// NOTE: This part of the API is still experimental and will probably change in +// the future. +// + +// Sets the butteraugli target distance for the compressor. This may override +// the default quantization table indexes based on jpeg colorspace, therefore +// it must be called after jpegli_set_defaults() or after the last +// jpegli_set_colorspace() or jpegli_default_colorspace() calls. +void jpegli_set_distance(j_compress_ptr cinfo, float distance, + boolean force_baseline); + +// Returns the butteraugli target distance for the given quality parameter. +float jpegli_quality_to_distance(int quality); + +// Enables distance parameter search to meet the given psnr target. +void jpegli_set_psnr(j_compress_ptr cinfo, float psnr, float tolerance, + float min_distance, float max_distance); + +// Changes the default behaviour of the encoder in the selection of quantization +// matrices and chroma subsampling. Must be called before jpegli_set_defaults() +// because some default setting depend on the XYB mode. +void jpegli_set_xyb_mode(j_compress_ptr cinfo); + +// Signals to the encoder that the pixel data that will be provided later +// through jpegli_write_scanlines() has this transfer function. This must be +// called before jpegli_set_defaults() because it changes the default +// quantization tables. +void jpegli_set_cicp_transfer_function(j_compress_ptr cinfo, int code); + +void jpegli_set_input_format(j_compress_ptr cinfo, JpegliDataType data_type, + JpegliEndianness endianness); + +// Sets whether or not the encoder uses adaptive quantization for creating more +// zero coefficients based on the local properties of the image. +// Enabled by default. +void jpegli_enable_adaptive_quantization(j_compress_ptr cinfo, boolean value); + +// Sets the default progression parameters, where level 0 is sequential, and +// greater level value means more progression steps. Default is 2. +void jpegli_set_progressive_level(j_compress_ptr cinfo, int level); + +// If this function is called before starting compression, the quality and +// linear quality parameters will be used to scale the standard quantization +// tables from Annex K of the JPEG standard. By default jpegli uses a different +// set of quantization tables and used different scaling parameters for DC and +// AC coefficients. Must be called before jpegli_set_defaults(). +void jpegli_use_standard_quant_tables(j_compress_ptr cinfo); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // LIB_JPEGLI_ENCODE_H_ diff --git a/media/libjxl/src/lib/jpegli/encode_api_test.cc b/media/libjxl/src/lib/jpegli/encode_api_test.cc new file mode 100644 index 0000000000..c8a8e21f67 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/encode_api_test.cc @@ -0,0 +1,873 @@ +// 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 "lib/jpegli/encode.h" +#include "lib/jpegli/libjpeg_test_util.h" +#include "lib/jpegli/test_params.h" +#include "lib/jpegli/test_utils.h" +#include "lib/jpegli/testing.h" +#include "lib/jpegli/types.h" + +namespace jpegli { +namespace { + +struct TestConfig { + TestImage input; + CompressParams jparams; + JpegIOMode input_mode = PIXELS; + double max_bpp; + double max_dist; +}; + +class EncodeAPITestParam : public ::testing::TestWithParam {}; + +void GenerateInput(JpegIOMode input_mode, const CompressParams& jparams, + TestImage* input) { + GeneratePixels(input); + if (input_mode == RAW_DATA) { + GenerateRawData(jparams, input); + } else if (input_mode == COEFFICIENTS) { + GenerateCoeffs(jparams, input); + } +} + +TEST_P(EncodeAPITestParam, TestAPI) { + TestConfig config = GetParam(); + GenerateInput(config.input_mode, config.jparams, &config.input); + std::vector compressed; + ASSERT_TRUE(EncodeWithJpegli(config.input, config.jparams, &compressed)); + if (config.jparams.icc.empty()) { + double bpp = + compressed.size() * 8.0 / (config.input.xsize * config.input.ysize); + printf("bpp: %f\n", bpp); + EXPECT_LT(bpp, config.max_bpp); + } + DecompressParams dparams; + dparams.output_mode = + config.input_mode == COEFFICIENTS ? COEFFICIENTS : PIXELS; + if (config.jparams.set_jpeg_colorspace && + config.jparams.jpeg_color_space == JCS_GRAYSCALE) { + ConvertToGrayscale(&config.input); + } else { + dparams.set_out_color_space = true; + dparams.out_color_space = config.input.color_space; + } + TestImage output; + DecodeWithLibjpeg(config.jparams, dparams, compressed, &output); + VerifyOutputImage(config.input, output, config.max_dist); +} + +TEST(EncodeAPITest, ReuseCinfoSameImageTwice) { + TestImage input; + input.xsize = 129; + input.ysize = 73; + CompressParams jparams; + GenerateInput(PIXELS, jparams, &input); + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + std::vector compressed0; + std::vector compressed1; + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + EncodeWithJpegli(input, jparams, &cinfo); + compressed0.assign(buffer, buffer + buffer_size); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + EncodeWithJpegli(input, jparams, &cinfo); + compressed1.assign(buffer, buffer + buffer_size); + return true; + }; + EXPECT_TRUE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); + ASSERT_EQ(compressed0.size(), compressed1.size()); + EXPECT_EQ(0, + memcmp(compressed0.data(), compressed1.data(), compressed0.size())); +} + +std::vector GenerateBasicConfigs() { + std::vector all_configs; + for (int samp : {1, 2}) { + for (int progr : {0, 2}) { + for (int optimize : {0, 1}) { + if (progr && optimize) continue; + TestConfig config; + config.input.xsize = 257 + samp * 37; + config.input.ysize = 265 + optimize * 17; + config.jparams.h_sampling = {samp, 1, 1}; + config.jparams.v_sampling = {samp, 1, 1}; + config.jparams.progressive_mode = progr; + config.jparams.optimize_coding = optimize; + config.max_dist = 2.4f; + GeneratePixels(&config.input); + all_configs.push_back(config); + } + } + } + return all_configs; +} + +TEST(EncodeAPITest, ReuseCinfoSameMemOutput) { + std::vector all_configs = GenerateBasicConfigs(); + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + { + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + for (const TestConfig& config : all_configs) { + EncodeWithJpegli(config.input, config.jparams, &cinfo); + } + return true; + }; + EXPECT_TRUE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + } + size_t pos = 0; + for (auto& config : all_configs) { + TestImage output; + pos += DecodeWithLibjpeg(config.jparams, DecompressParams(), nullptr, 0, + buffer + pos, buffer_size - pos, &output); + VerifyOutputImage(config.input, output, config.max_dist); + } + if (buffer) free(buffer); +} + +TEST(EncodeAPITest, ReuseCinfoSameStdOutput) { + std::vector all_configs = GenerateBasicConfigs(); + FILE* tmpf = tmpfile(); + ASSERT_TRUE(tmpf); + { + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_stdio_dest(&cinfo, tmpf); + for (const TestConfig& config : all_configs) { + EncodeWithJpegli(config.input, config.jparams, &cinfo); + } + return true; + }; + EXPECT_TRUE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + } + size_t total_size = ftell(tmpf); + fseek(tmpf, 0, SEEK_SET); + std::vector compressed(total_size); + ASSERT_TRUE(total_size == fread(compressed.data(), 1, total_size, tmpf)); + fclose(tmpf); + size_t pos = 0; + for (auto& config : all_configs) { + TestImage output; + pos += + DecodeWithLibjpeg(config.jparams, DecompressParams(), nullptr, 0, + &compressed[pos], compressed.size() - pos, &output); + VerifyOutputImage(config.input, output, config.max_dist); + } +} + +TEST(EncodeAPITest, ReuseCinfoChangeParams) { + TestImage input; + TestImage output; + CompressParams jparams; + DecompressParams dparams; + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + std::vector compressed; + jpeg_compress_struct cinfo; + const auto max_rms = [](int q, int hs, int vs) { + if (hs == 1 && vs == 1) return q == 90 ? 2.2 : 0.6; + if (hs == 2 && vs == 2) return q == 90 ? 2.8 : 1.2; + return q == 90 ? 2.4 : 1.0; + }; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + input.xsize = 129; + input.ysize = 73; + dparams.set_out_color_space = true; + for (JpegIOMode input_mode : {PIXELS, RAW_DATA, PIXELS, COEFFICIENTS}) { + for (int h_samp : {2, 1}) { + for (int v_samp : {2, 1}) { + for (int progr : {0, 2}) { + for (int quality : {90, 100}) { + input.Clear(); + input.color_space = + (input_mode == RAW_DATA ? JCS_YCbCr : JCS_RGB); + jparams.quality = quality; + jparams.h_sampling = {h_samp, 1, 1}; + jparams.v_sampling = {v_samp, 1, 1}; + jparams.progressive_mode = progr; + printf( + "Generating input with quality %d chroma subsampling %dx%d " + "input mode %d progressive_mode %d\n", + quality, h_samp, v_samp, input_mode, progr); + GenerateInput(input_mode, jparams, &input); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + if (input_mode != COEFFICIENTS) { + cinfo.image_width = input.xsize; + cinfo.image_height = input.ysize; + cinfo.input_components = input.components; + jpegli_set_defaults(&cinfo); + jpegli_start_compress(&cinfo, TRUE); + jpegli_abort_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + } + EncodeWithJpegli(input, jparams, &cinfo); + compressed.resize(buffer_size); + std::copy_n(buffer, buffer_size, compressed.data()); + dparams.output_mode = + input_mode == COEFFICIENTS ? COEFFICIENTS : PIXELS; + dparams.out_color_space = input.color_space; + output.Clear(); + DecodeWithLibjpeg(jparams, dparams, compressed, &output); + VerifyOutputImage(input, output, + max_rms(quality, h_samp, v_samp)); + } + } + } + } + } + return true; + }; + EXPECT_TRUE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncodeAPITest, AbbreviatedStreams) { + uint8_t* table_stream = nullptr; + unsigned long table_stream_size = 0; // NOLINT + uint8_t* data_stream = nullptr; + unsigned long data_stream_size = 0; // NOLINT + { + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &table_stream, &table_stream_size); + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + jpegli_set_defaults(&cinfo); + jpegli_write_tables(&cinfo); + jpegli_mem_dest(&cinfo, &data_stream, &data_stream_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.optimize_coding = FALSE; + jpegli_set_progressive_level(&cinfo, 0); + jpegli_start_compress(&cinfo, FALSE); + JSAMPLE image[3] = {0}; + JSAMPROW row[] = {image}; + jpegli_write_scanlines(&cinfo, row, 1); + jpegli_finish_compress(&cinfo); + return true; + }; + EXPECT_TRUE(try_catch_block()); + EXPECT_LT(data_stream_size, 50); + jpegli_destroy_compress(&cinfo); + } + TestImage output; + DecodeWithLibjpeg(CompressParams(), DecompressParams(), table_stream, + table_stream_size, data_stream, data_stream_size, &output); + EXPECT_EQ(1, output.xsize); + EXPECT_EQ(1, output.ysize); + EXPECT_EQ(3, output.components); + EXPECT_EQ(0, output.pixels[0]); + EXPECT_EQ(0, output.pixels[1]); + EXPECT_EQ(0, output.pixels[2]); + if (table_stream) free(table_stream); + if (data_stream) free(data_stream); +} + +void CopyQuantTables(j_compress_ptr cinfo, uint16_t* quant_tables) { + for (int c = 0; c < cinfo->num_components; ++c) { + int quant_idx = cinfo->comp_info[c].quant_tbl_no; + JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[quant_idx]; + for (int k = 0; k < DCTSIZE2; ++k) { + quant_tables[c * DCTSIZE2 + k] = quant_table->quantval[k]; + } + } +} + +TEST(EncodeAPITest, QualitySettings) { + // Test that jpegli_set_quality, jpegli_set_linear_quality and + // jpegli_quality_scaling are consistent with each other. + uint16_t quant_tables0[3 * DCTSIZE2]; + uint16_t quant_tables1[3 * DCTSIZE2]; + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + jpegli_set_defaults(&cinfo); + for (boolean baseline : {FALSE, TRUE}) { + for (int q = 1; q <= 100; ++q) { + jpegli_set_quality(&cinfo, q, baseline); + CopyQuantTables(&cinfo, quant_tables0); + jpegli_set_linear_quality(&cinfo, jpegli_quality_scaling(q), baseline); + CopyQuantTables(&cinfo, quant_tables1); + EXPECT_EQ(0, + memcmp(quant_tables0, quant_tables1, sizeof(quant_tables0))); +#if JPEG_LIB_VERSION >= 70 + for (int i = 0; i < NUM_QUANT_TBLS; ++i) { + cinfo.q_scale_factor[i] = jpegli_quality_scaling(q); + } + jpegli_default_qtables(&cinfo, baseline); + CopyQuantTables(&cinfo, quant_tables1); + EXPECT_EQ(0, + memcmp(quant_tables0, quant_tables1, sizeof(quant_tables0))); +#endif + } + } + return true; + }; + EXPECT_TRUE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + // Test jpegli_quality_scaling for some specific values . + EXPECT_EQ(5000, jpegli_quality_scaling(-1)); + EXPECT_EQ(5000, jpegli_quality_scaling(0)); + EXPECT_EQ(5000, jpegli_quality_scaling(1)); + EXPECT_EQ(100, jpegli_quality_scaling(50)); + EXPECT_EQ(50, jpegli_quality_scaling(75)); + EXPECT_EQ(20, jpegli_quality_scaling(90)); + EXPECT_EQ(0, jpegli_quality_scaling(100)); + EXPECT_EQ(0, jpegli_quality_scaling(101)); +} + +std::vector GenerateTests() { + std::vector all_tests; + for (int h_samp : {1, 2}) { + for (int v_samp : {1, 2}) { + for (int progr : {0, 2}) { + for (int optimize : {0, 1}) { + if (progr && optimize) continue; + TestConfig config; + config.jparams.h_sampling = {h_samp, 1, 1}; + config.jparams.v_sampling = {v_samp, 1, 1}; + config.jparams.progressive_mode = progr; + if (!progr) { + config.jparams.optimize_coding = optimize; + } + const float kMaxBpp[4] = {1.55, 1.4, 1.4, 1.32}; + const float kMaxDist[4] = {1.95, 2.2, 2.2, 2.0}; + const int idx = v_samp * 2 + h_samp - 3; + config.max_bpp = + kMaxBpp[idx] * (optimize ? 0.97 : 1.0) * (progr ? 0.97 : 1.0); + config.max_dist = kMaxDist[idx]; + all_tests.push_back(config); + } + } + } + } + { + TestConfig config; + config.jparams.quality = 100; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; + config.max_bpp = 6.6; + config.max_dist = 0.6; + all_tests.push_back(config); + } + { + TestConfig config; + config.jparams.quality = 80; + config.max_bpp = 1.05; + config.max_dist = 2.7; + all_tests.push_back(config); + } + for (int samp : {1, 2}) { + for (int progr : {0, 2}) { + for (int optimize : {0, 1}) { + if (progr && optimize) continue; + TestConfig config; + config.input.xsize = 257; + config.input.ysize = 265; + config.jparams.h_sampling = {samp, 1, 1}; + config.jparams.v_sampling = {samp, 1, 1}; + config.jparams.progressive_mode = progr; + if (!progr) { + config.jparams.optimize_coding = optimize; + } + config.jparams.use_adaptive_quantization = false; + config.max_bpp = 2.05f; + config.max_dist = 2.3f; + all_tests.push_back(config); + } + } + } + for (int h0_samp : {1, 2, 4}) { + for (int v0_samp : {1, 2, 4}) { + for (int h2_samp : {1, 2, 4}) { + for (int v2_samp : {1, 2, 4}) { + TestConfig config; + config.input.xsize = 137; + config.input.ysize = 75; + config.jparams.progressive_mode = 2; + config.jparams.h_sampling = {h0_samp, 1, h2_samp}; + config.jparams.v_sampling = {v0_samp, 1, v2_samp}; + config.max_bpp = 2.5; + config.max_dist = 12.0; + all_tests.push_back(config); + } + } + } + } + for (int h0_samp : {1, 3}) { + for (int v0_samp : {1, 3}) { + for (int h2_samp : {1, 3}) { + for (int v2_samp : {1, 3}) { + TestConfig config; + config.input.xsize = 205; + config.input.ysize = 99; + config.jparams.progressive_mode = 2; + config.jparams.h_sampling = {h0_samp, 1, h2_samp}; + config.jparams.v_sampling = {v0_samp, 1, v2_samp}; + config.max_bpp = 2.5; + config.max_dist = 10.0; + all_tests.push_back(config); + } + } + } + } + for (int h0_samp : {1, 2, 3, 4}) { + for (int v0_samp : {1, 2, 3, 4}) { + TestConfig config; + config.input.xsize = 217; + config.input.ysize = 129; + config.jparams.progressive_mode = 2; + config.jparams.h_sampling = {h0_samp, 1, 1}; + config.jparams.v_sampling = {v0_samp, 1, 1}; + config.max_bpp = 2.0; + config.max_dist = 5.5; + all_tests.push_back(config); + } + } + for (int p = 0; p < 3 + NumTestScanScripts(); ++p) { + for (int samp : {1, 2}) { + for (int quality : {100, 90, 1}) { + for (int r : {0, 1024, 1}) { + for (int optimize : {0, 1}) { + bool progressive = p == 1 || p == 2 || p > 4; + if (progressive && !optimize) continue; + TestConfig config; + config.input.xsize = 273; + config.input.ysize = 265; + config.jparams.progressive_mode = p; + if (!progressive) { + config.jparams.optimize_coding = optimize; + } + config.jparams.h_sampling = {samp, 1, 1}; + config.jparams.v_sampling = {samp, 1, 1}; + config.jparams.quality = quality; + config.jparams.restart_interval = r; + config.max_bpp = quality == 100 ? 8.0 : 1.9; + if (r == 1) { + config.max_bpp += 10.0; + } + config.max_dist = quality == 1 ? 20.0 : 2.1; + all_tests.push_back(config); + } + } + } + } + } + { + TestConfig config; + config.jparams.simple_progression = true; + config.max_bpp = 1.48; + config.max_dist = 2.0; + all_tests.push_back(config); + } + { + TestConfig config; + config.input_mode = COEFFICIENTS; + config.jparams.h_sampling = {2, 1, 1}; + config.jparams.v_sampling = {2, 1, 1}; + config.jparams.progressive_mode = 0; + config.jparams.optimize_coding = 0; + config.max_bpp = 16; + config.max_dist = 0.0; + all_tests.push_back(config); + } + { + TestConfig config; + config.jparams.xyb_mode = true; + config.jparams.progressive_mode = 2; + config.max_bpp = 1.5; + config.max_dist = 3.5; + all_tests.push_back(config); + } + { + TestConfig config; + config.jparams.libjpeg_mode = true; + config.max_bpp = 2.1; + config.max_dist = 1.7; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; + all_tests.push_back(config); + } + + for (J_COLOR_SPACE in_color_space : + {JCS_RGB, JCS_YCbCr, JCS_GRAYSCALE, JCS_EXT_RGB, JCS_EXT_BGR, + JCS_EXT_RGBA, JCS_EXT_BGRA, JCS_EXT_ARGB, JCS_EXT_ABGR}) { + for (J_COLOR_SPACE jpeg_color_space : {JCS_RGB, JCS_YCbCr, JCS_GRAYSCALE}) { + if (jpeg_color_space == JCS_RGB && in_color_space >= JCS_YCbCr) continue; + TestConfig config; + config.input.xsize = config.input.ysize = 256; + config.input.color_space = in_color_space; + config.jparams.set_jpeg_colorspace = true; + config.jparams.jpeg_color_space = jpeg_color_space; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; + config.max_bpp = jpeg_color_space == JCS_RGB ? 4.5 : 1.85; + config.max_dist = jpeg_color_space == JCS_RGB ? 1.4 : 2.05; + all_tests.push_back(config); + } + } + for (J_COLOR_SPACE in_color_space : {JCS_CMYK, JCS_YCCK}) { + for (J_COLOR_SPACE jpeg_color_space : {JCS_CMYK, JCS_YCCK}) { + if (jpeg_color_space == JCS_CMYK && in_color_space == JCS_YCCK) continue; + TestConfig config; + config.input.xsize = config.input.ysize = 256; + config.input.color_space = in_color_space; + if (in_color_space != jpeg_color_space) { + config.jparams.set_jpeg_colorspace = true; + config.jparams.jpeg_color_space = jpeg_color_space; + } + config.jparams.h_sampling = {1, 1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1, 1}; + config.max_bpp = jpeg_color_space == JCS_CMYK ? 4.0 : 3.6; + config.max_dist = jpeg_color_space == JCS_CMYK ? 1.2 : 1.5; + all_tests.push_back(config); + } + } + { + TestConfig config; + config.input.color_space = JCS_YCbCr; + config.max_bpp = 1.6; + config.max_dist = 1.35; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; + all_tests.push_back(config); + } + for (bool xyb : {false, true}) { + TestConfig config; + config.input.color_space = JCS_GRAYSCALE; + config.jparams.xyb_mode = xyb; + config.max_bpp = 1.35; + config.max_dist = 1.4; + all_tests.push_back(config); + } + for (int channels = 1; channels <= 4; ++channels) { + TestConfig config; + config.input.color_space = JCS_UNKNOWN; + config.input.components = channels; + config.max_bpp = 1.35 * channels; + config.max_dist = 1.4; + all_tests.push_back(config); + } + for (size_t r : {1, 3, 17, 1024}) { + for (int progr : {0, 2}) { + TestConfig config; + config.jparams.restart_interval = r; + config.jparams.progressive_mode = progr; + config.max_bpp = 1.58 + 5.5 / r; + config.max_dist = 2.2; + all_tests.push_back(config); + } + } + for (size_t rr : {1, 3, 8, 100}) { + TestConfig config; + config.jparams.restart_in_rows = rr; + config.max_bpp = 1.6; + config.max_dist = 2.2; + all_tests.push_back(config); + } + for (int type : {0, 1, 10, 100, 10000}) { + for (int scale : {1, 50, 100, 200, 500}) { + for (bool add_raw : {false, true}) { + for (bool baseline : {true, false}) { + if (!baseline && (add_raw || type * scale < 25500)) continue; + TestConfig config; + config.input.xsize = 64; + config.input.ysize = 64; + CustomQuantTable table; + table.table_type = type; + table.scale_factor = scale; + table.force_baseline = baseline; + table.add_raw = add_raw; + table.Generate(); + config.jparams.optimize_coding = 1; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; + config.jparams.quant_tables.push_back(table); + config.jparams.quant_indexes = {0, 0, 0}; + float q = (type == 0 ? 16 : type) * scale * 0.01f; + if (baseline && !add_raw) q = std::max(1.0f, std::min(255.0f, q)); + config.max_bpp = 1.5f + 25.0f / q; + config.max_dist = 0.6f + 0.25f * q; + all_tests.push_back(config); + } + } + } + } + for (int qidx = 0; qidx < 8; ++qidx) { + if (qidx == 3) continue; + TestConfig config; + config.input.xsize = 256; + config.input.ysize = 256; + config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1, + (qidx >> 0) & 1}; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; + config.max_bpp = 2.25; + config.max_dist = 2.8; + all_tests.push_back(config); + } + for (int qidx = 0; qidx < 8; ++qidx) { + for (int slot_idx = 0; slot_idx < 2; ++slot_idx) { + if (qidx == 0 && slot_idx == 0) continue; + TestConfig config; + config.input.xsize = 256; + config.input.ysize = 256; + config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1, + (qidx >> 0) & 1}; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; + CustomQuantTable table; + table.slot_idx = slot_idx; + table.Generate(); + config.jparams.quant_tables.push_back(table); + config.max_bpp = 2.3; + config.max_dist = 2.9; + all_tests.push_back(config); + } + } + for (int qidx = 0; qidx < 8; ++qidx) { + for (bool xyb : {false, true}) { + TestConfig config; + config.input.xsize = 256; + config.input.ysize = 256; + config.jparams.xyb_mode = xyb; + config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1, + (qidx >> 0) & 1}; + if (!xyb) { + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; + } + { + CustomQuantTable table; + table.slot_idx = 0; + table.Generate(); + config.jparams.quant_tables.push_back(table); + } + { + CustomQuantTable table; + table.slot_idx = 1; + table.table_type = 20; + table.Generate(); + config.jparams.quant_tables.push_back(table); + } + config.max_bpp = 2.0; + config.max_dist = 3.85; + all_tests.push_back(config); + } + } + for (bool xyb : {false, true}) { + TestConfig config; + config.input.xsize = 256; + config.input.ysize = 256; + config.jparams.xyb_mode = xyb; + config.jparams.quant_indexes = {0, 1, 2}; + if (!xyb) { + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; + } + { + CustomQuantTable table; + table.slot_idx = 0; + table.Generate(); + config.jparams.quant_tables.push_back(table); + } + { + CustomQuantTable table; + table.slot_idx = 1; + table.table_type = 20; + table.Generate(); + config.jparams.quant_tables.push_back(table); + } + { + CustomQuantTable table; + table.slot_idx = 2; + table.table_type = 30; + table.Generate(); + config.jparams.quant_tables.push_back(table); + } + config.max_bpp = 1.5; + config.max_dist = 3.75; + all_tests.push_back(config); + } + { + TestConfig config; + config.jparams.comp_ids = {7, 17, 177}; + config.input.xsize = config.input.ysize = 128; + config.max_bpp = 2.25; + config.max_dist = 2.4; + all_tests.push_back(config); + } + for (int override_JFIF : {-1, 0, 1}) { + for (int override_Adobe : {-1, 0, 1}) { + if (override_JFIF == -1 && override_Adobe == -1) continue; + TestConfig config; + config.input.xsize = config.input.ysize = 128; + config.jparams.override_JFIF = override_JFIF; + config.jparams.override_Adobe = override_Adobe; + config.max_bpp = 2.25; + config.max_dist = 2.4; + all_tests.push_back(config); + } + } + { + TestConfig config; + config.input.xsize = config.input.ysize = 256; + config.max_bpp = 1.85; + config.max_dist = 2.05; + config.jparams.add_marker = true; + all_tests.push_back(config); + } + for (size_t icc_size : {728, 70000, 1000000}) { + TestConfig config; + config.input.xsize = config.input.ysize = 256; + config.max_dist = 2.05; + config.jparams.icc.resize(icc_size); + for (size_t i = 0; i < icc_size; ++i) { + config.jparams.icc[i] = (i * 17) & 0xff; + } + all_tests.push_back(config); + } + for (JpegIOMode input_mode : {PIXELS, RAW_DATA, COEFFICIENTS}) { + TestConfig config; + config.input.xsize = config.input.ysize = 256; + config.input_mode = input_mode; + if (input_mode == RAW_DATA) { + config.input.color_space = JCS_YCbCr; + } + config.jparams.progressive_mode = 0; + config.jparams.optimize_coding = 0; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {1, 1, 1}; + config.max_bpp = 1.85; + config.max_dist = 2.05; + if (input_mode == COEFFICIENTS) { + config.max_bpp = 3.5; + config.max_dist = 0.0; + } + all_tests.push_back(config); + config.jparams.use_flat_dc_luma_code = true; + all_tests.push_back(config); + } + for (int xsize : {640, 641, 648, 649}) { + for (int ysize : {640, 641, 648, 649}) { + for (int h_sampling : {1, 2}) { + for (int v_sampling : {1, 2}) { + if (h_sampling == 1 && v_sampling == 1) continue; + for (int progr : {0, 2}) { + TestConfig config; + config.input.xsize = xsize; + config.input.ysize = ysize; + config.input.color_space = JCS_YCbCr; + config.jparams.h_sampling = {h_sampling, 1, 1}; + config.jparams.v_sampling = {v_sampling, 1, 1}; + config.jparams.progressive_mode = progr; + config.input_mode = RAW_DATA; + config.max_bpp = 1.75; + config.max_dist = 2.0; + all_tests.push_back(config); + config.input_mode = COEFFICIENTS; + if (xsize & 1) { + config.jparams.add_marker = true; + } + config.max_bpp = 24.0; + all_tests.push_back(config); + } + } + } + } + } + for (JpegliDataType data_type : {JPEGLI_TYPE_UINT16, JPEGLI_TYPE_FLOAT}) { + for (JpegliEndianness endianness : + {JPEGLI_LITTLE_ENDIAN, JPEGLI_BIG_ENDIAN, JPEGLI_NATIVE_ENDIAN}) { + J_COLOR_SPACE colorspace[4] = {JCS_GRAYSCALE, JCS_UNKNOWN, JCS_RGB, + JCS_CMYK}; + float max_bpp[4] = {1.32, 2.7, 1.6, 4.0}; + for (int channels = 1; channels <= 4; ++channels) { + TestConfig config; + config.input.data_type = data_type; + config.input.endianness = endianness; + config.input.components = channels; + config.input.color_space = colorspace[channels - 1]; + config.max_bpp = max_bpp[channels - 1]; + config.max_dist = 2.2; + all_tests.push_back(config); + } + } + } + for (int smoothing : {1, 5, 50, 100}) { + for (int h_sampling : {1, 2}) { + for (int v_sampling : {1, 2}) { + TestConfig config; + config.input.xsize = 257; + config.input.ysize = 265; + config.jparams.smoothing_factor = smoothing; + config.jparams.h_sampling = {h_sampling, 1, 1}; + config.jparams.v_sampling = {v_sampling, 1, 1}; + config.max_bpp = 1.85; + config.max_dist = 3.05f; + all_tests.push_back(config); + } + } + } + return all_tests; +}; + +std::ostream& operator<<(std::ostream& os, const TestConfig& c) { + os << c.input; + os << c.jparams; + if (c.input_mode == RAW_DATA) { + os << "RawDataIn"; + } else if (c.input_mode == COEFFICIENTS) { + os << "WriteCoeffs"; + } + return os; +} + +std::string TestDescription( + const testing::TestParamInfo& info) { + std::stringstream name; + name << info.param; + return name.str(); +} + +JPEGLI_INSTANTIATE_TEST_SUITE_P(EncodeAPITest, EncodeAPITestParam, + testing::ValuesIn(GenerateTests()), + TestDescription); +} // namespace +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/encode_finish.cc b/media/libjxl/src/lib/jpegli/encode_finish.cc new file mode 100644 index 0000000000..8cc3b8d275 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/encode_finish.cc @@ -0,0 +1,232 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include "lib/jpegli/encode_finish.h" + +#include +#include + +#include "lib/jpegli/error.h" +#include "lib/jpegli/memory_manager.h" +#include "lib/jpegli/quant.h" + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jpegli/encode_finish.cc" +#include +#include + +#include "lib/jpegli/dct-inl.h" + +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { + +// These templates are not found via ADL. +using hwy::HWY_NAMESPACE::GetLane; + +using D = HWY_FULL(float); +using DI = HWY_FULL(int32_t); +using DI16 = Rebind; + +void ReQuantizeBlock(int16_t* block, const float* qmc, float aq_strength, + const float* zero_bias_offset, + const float* zero_bias_mul) { + D d; + DI di; + DI16 di16; + const auto aq_mul = Set(d, aq_strength); + for (size_t k = 0; k < DCTSIZE2; k += Lanes(d)) { + const auto in = Load(di16, block + k); + const auto val = ConvertTo(d, PromoteTo(di, in)); + const auto q = Load(d, qmc + k); + const auto qval = Mul(val, q); + const auto zb_offset = Load(d, zero_bias_offset + k); + const auto zb_mul = Load(d, zero_bias_mul + k); + const auto threshold = Add(zb_offset, Mul(zb_mul, aq_mul)); + const auto nzero_mask = Ge(Abs(qval), threshold); + const auto iqval = IfThenElseZero(nzero_mask, Round(qval)); + Store(DemoteTo(di16, ConvertTo(di, iqval)), di16, block + k); + } +} + +float BlockError(const int16_t* block, const float* qmc, const float* iqmc, + const float aq_strength, const float* zero_bias_offset, + const float* zero_bias_mul) { + D d; + DI di; + DI16 di16; + auto err = Zero(d); + const auto scale = Set(d, 1.0 / 16); + const auto aq_mul = Set(d, aq_strength); + for (size_t k = 0; k < DCTSIZE2; k += Lanes(d)) { + const auto in = Load(di16, block + k); + const auto val = ConvertTo(d, PromoteTo(di, in)); + const auto q = Load(d, qmc + k); + const auto qval = Mul(val, q); + const auto zb_offset = Load(d, zero_bias_offset + k); + const auto zb_mul = Load(d, zero_bias_mul + k); + const auto threshold = Add(zb_offset, Mul(zb_mul, aq_mul)); + const auto nzero_mask = Ge(Abs(qval), threshold); + const auto iqval = IfThenElseZero(nzero_mask, Round(qval)); + const auto invq = Load(d, iqmc + k); + const auto rval = Mul(iqval, invq); + const auto diff = Mul(Sub(val, rval), scale); + err = Add(err, Mul(diff, diff)); + } + return GetLane(SumOfLanes(d, err)); +} + +void ComputeInverseWeights(const float* qmc, float* iqmc) { + for (int k = 0; k < 64; ++k) { + iqmc[k] = 1.0f / qmc[k]; + } +} + +float ComputePSNR(j_compress_ptr cinfo, int sampling) { + jpeg_comp_master* m = cinfo->master; + InitQuantizer(cinfo, QuantPass::SEARCH_SECOND_PASS); + double error = 0.0; + size_t num = 0; + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + const float* qmc = m->quant_mul[c]; + const int h_factor = m->h_factor[c]; + const int v_factor = m->v_factor[c]; + const float* zero_bias_offset = m->zero_bias_offset[c]; + const float* zero_bias_mul = m->zero_bias_mul[c]; + HWY_ALIGN float iqmc[64]; + ComputeInverseWeights(qmc, iqmc); + for (JDIMENSION by = 0; by < comp->height_in_blocks; by += sampling) { + JBLOCKARRAY blocks = GetBlockRow(cinfo, c, by); + const float* qf = m->quant_field.Row(by * v_factor); + for (JDIMENSION bx = 0; bx < comp->width_in_blocks; bx += sampling) { + error += BlockError(&blocks[0][bx][0], qmc, iqmc, qf[bx * h_factor], + zero_bias_offset, zero_bias_mul); + num += DCTSIZE2; + } + } + } + return 4.3429448f * log(num / (error / 255. / 255.)); +} + +void ReQuantizeCoeffs(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + InitQuantizer(cinfo, QuantPass::SEARCH_SECOND_PASS); + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + const float* qmc = m->quant_mul[c]; + const int h_factor = m->h_factor[c]; + const int v_factor = m->v_factor[c]; + const float* zero_bias_offset = m->zero_bias_offset[c]; + const float* zero_bias_mul = m->zero_bias_mul[c]; + for (JDIMENSION by = 0; by < comp->height_in_blocks; ++by) { + JBLOCKARRAY block = GetBlockRow(cinfo, c, by); + const float* qf = m->quant_field.Row(by * v_factor); + for (JDIMENSION bx = 0; bx < comp->width_in_blocks; ++bx) { + ReQuantizeBlock(&block[0][bx][0], qmc, qf[bx * h_factor], + zero_bias_offset, zero_bias_mul); + } + } + } +} + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace jpegli { +namespace { +HWY_EXPORT(ComputePSNR); +HWY_EXPORT(ReQuantizeCoeffs); + +void ReQuantizeCoeffs(j_compress_ptr cinfo) { + HWY_DYNAMIC_DISPATCH(ReQuantizeCoeffs)(cinfo); +} + +float ComputePSNR(j_compress_ptr cinfo, int sampling) { + return HWY_DYNAMIC_DISPATCH(ComputePSNR)(cinfo, sampling); +} + +void UpdateDistance(j_compress_ptr cinfo, float distance) { + float distances[NUM_QUANT_TBLS] = {distance, distance, distance}; + SetQuantMatrices(cinfo, distances, /*add_two_chroma_tables=*/true); +} + +float Clamp(float val, float minval, float maxval) { + return std::max(minval, std::min(maxval, val)); +} + +#define PSNR_SEARCH_DBG 0 + +float FindDistanceForPSNR(j_compress_ptr cinfo) { + constexpr int kMaxIters = 20; + const float psnr_target = cinfo->master->psnr_target; + const float tolerance = cinfo->master->psnr_tolerance; + const float min_dist = cinfo->master->min_distance; + const float max_dist = cinfo->master->max_distance; + float d = Clamp(1.0f, min_dist, max_dist); + for (int sampling : {4, 1}) { + float best_diff = std::numeric_limits::max(); + float best_distance = 0.0f; + float best_psnr = 0.0; + float dmin = min_dist; + float dmax = max_dist; + bool found_lower_bound = false; + bool found_upper_bound = false; + for (int i = 0; i < kMaxIters; ++i) { + UpdateDistance(cinfo, d); + float psnr = ComputePSNR(cinfo, sampling); + if (psnr > psnr_target) { + dmin = d; + found_lower_bound = true; + } else { + dmax = d; + found_upper_bound = true; + } +#if (PSNR_SEARCH_DBG > 1) + printf("sampling %d iter %2d d %7.4f psnr %.2f", sampling, i, d, psnr); + if (found_upper_bound && found_lower_bound) { + printf(" d-interval: [ %7.4f .. %7.4f ]", dmin, dmax); + } + printf("\n"); +#endif + float diff = std::abs(psnr - psnr_target); + if (diff < best_diff) { + best_diff = diff; + best_distance = d; + best_psnr = psnr; + } + if (diff < tolerance * psnr_target || dmin == dmax) { + break; + } + if (!found_lower_bound || !found_upper_bound) { + d *= std::exp(0.15f * (psnr - psnr_target)); + } else { + d = 0.5f * (dmin + dmax); + } + d = Clamp(d, min_dist, max_dist); + } + d = best_distance; + if (sampling == 1 && PSNR_SEARCH_DBG) { + printf("Final PSNR %.2f at distance %.4f\n", best_psnr, d); + } else { + (void)best_psnr; + } + } + return d; +} + +} // namespace + +void QuantizetoPSNR(j_compress_ptr cinfo) { + float distance = FindDistanceForPSNR(cinfo); + UpdateDistance(cinfo, distance); + ReQuantizeCoeffs(cinfo); +} + +} // namespace jpegli +#endif // HWY_ONCE diff --git a/media/libjxl/src/lib/jpegli/encode_finish.h b/media/libjxl/src/lib/jpegli/encode_finish.h new file mode 100644 index 0000000000..f6862decb9 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/encode_finish.h @@ -0,0 +1,17 @@ +// 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_JPEGLI_ENCODE_FINISH_H_ +#define LIB_JPEGLI_ENCODE_FINISH_H_ + +#include "lib/jpegli/encode_internal.h" + +namespace jpegli { + +void QuantizetoPSNR(j_compress_ptr cinfo); + +} // namespace jpegli + +#endif // LIB_JPEGLI_ENCODE_FINISH_H_ diff --git a/media/libjxl/src/lib/jpegli/encode_internal.h b/media/libjxl/src/lib/jpegli/encode_internal.h new file mode 100644 index 0000000000..4dbef97538 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/encode_internal.h @@ -0,0 +1,141 @@ +// 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_JPEGLI_ENCODE_INTERNAL_H_ +#define LIB_JPEGLI_ENCODE_INTERNAL_H_ + +#include + +#include "lib/jpegli/bit_writer.h" +#include "lib/jpegli/common.h" +#include "lib/jpegli/common_internal.h" +#include "lib/jpegli/encode.h" + +namespace jpegli { + +constexpr unsigned char kICCSignature[12] = { + 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00}; +constexpr int kICCMarker = JPEG_APP0 + 2; + +constexpr int kDefaultProgressiveLevel = 0; + +typedef int16_t coeff_t; + +struct HuffmanCodeTable { + int depth[256]; + int code[256]; +}; + +struct Token { + uint8_t context; + uint8_t symbol; + uint16_t bits; + Token(int c, int s, int b) : context(c), symbol(s), bits(b) {} +}; + +struct TokenArray { + Token* tokens; + size_t num_tokens; +}; + +struct RefToken { + uint8_t symbol; + uint8_t refbits; +}; + +struct ScanTokenInfo { + RefToken* tokens; + size_t num_tokens; + uint8_t* refbits; + uint16_t* eobruns; + size_t* restarts; + size_t num_restarts; + size_t num_nonzeros; + size_t num_future_nonzeros; + size_t token_offset; + size_t restart_interval; + size_t MCUs_per_row; + size_t MCU_rows_in_scan; + size_t blocks_in_MCU; + size_t num_blocks; +}; + +} // namespace jpegli + +struct jpeg_comp_master { + jpegli::RowBuffer input_buffer[jpegli::kMaxComponents]; + jpegli::RowBuffer* smooth_input[jpegli::kMaxComponents]; + jpegli::RowBuffer* raw_data[jpegli::kMaxComponents]; + bool force_baseline; + bool xyb_mode; + uint8_t cicp_transfer_function; + bool use_std_tables; + bool use_adaptive_quantization; + int progressive_level; + size_t xsize_blocks; + size_t ysize_blocks; + size_t blocks_per_iMCU_row; + jpegli::ScanTokenInfo* scan_token_info; + JpegliDataType data_type; + JpegliEndianness endianness; + void (*input_method)(const uint8_t* row_in, size_t len, + float* row_out[jpegli::kMaxComponents]); + void (*color_transform)(float* row[jpegli::kMaxComponents], size_t len); + void (*downsample_method[jpegli::kMaxComponents])( + float* rows_in[MAX_SAMP_FACTOR], size_t len, float* row_out); + float* quant_mul[jpegli::kMaxComponents]; + float* zero_bias_offset[jpegli::kMaxComponents]; + float* zero_bias_mul[jpegli::kMaxComponents]; + int h_factor[jpegli::kMaxComponents]; + int v_factor[jpegli::kMaxComponents]; + // Array of Huffman tables that will be encoded in one or more DHT segments. + // In progressive mode we compute all Huffman tables that will be used in any + // of the scans, thus we can have more than 4 tables here. + JHUFF_TBL* huffman_tables; + size_t num_huffman_tables; + // Array of num_huffman_tables slot ids, where the ith element is the slot id + // of the ith Huffman table, as it appears in the DHT segment. The range of + // the slot ids is 0..3 for DC and 16..19 for AC Huffman codes. + uint8_t* slot_id_map; + // Maps context ids to an index in the huffman_tables array. Each component in + // each scan has a DC and AC context id, which are defined as follows: + // - DC context id is the component index (relative to cinfo->comp_info) of + // the scan component + // - AC context ids start at 4 and are increased for each component of each + // scan that have AC components (i.e. Se > 0) + uint8_t* context_map; + size_t num_contexts; + // Array of cinfo->num_scans context ids, where the ith element is the context + // id of the first AC component of the ith scan. + uint8_t* ac_ctx_offset; + // Array of num_huffman tables derived coding tables. + jpegli::HuffmanCodeTable* coding_tables; + float* diff_buffer; + jpegli::RowBuffer fuzzy_erosion_tmp; + jpegli::RowBuffer pre_erosion; + jpegli::RowBuffer quant_field; + jvirt_barray_ptr* coeff_buffers; + size_t next_input_row; + size_t next_iMCU_row; + size_t next_dht_index; + size_t last_restart_interval; + JCOEF last_dc_coeff[MAX_COMPS_IN_SCAN]; + jpegli::JpegBitWriter bw; + float* dct_buffer; + int32_t* block_tmp; + jpegli::TokenArray* token_arrays; + size_t cur_token_array; + jpegli::Token* next_token; + size_t num_tokens; + size_t total_num_tokens; + jpegli::RefToken* next_refinement_token; + uint8_t* next_refinement_bit; + float psnr_target; + float psnr_tolerance; + float min_distance; + float max_distance; +}; + +#endif // LIB_JPEGLI_ENCODE_INTERNAL_H_ diff --git a/media/libjxl/src/lib/jpegli/encode_streaming.cc b/media/libjxl/src/lib/jpegli/encode_streaming.cc new file mode 100644 index 0000000000..ff43864a47 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/encode_streaming.cc @@ -0,0 +1,259 @@ +// 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/jpegli/encode_streaming.h" + +#include + +#include "lib/jpegli/bit_writer.h" +#include "lib/jpegli/bitstream.h" +#include "lib/jpegli/entropy_coding.h" +#include "lib/jpegli/error.h" +#include "lib/jpegli/memory_manager.h" +#include "lib/jxl/base/bits.h" + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jpegli/encode_streaming.cc" +#include +#include + +#include "lib/jpegli/dct-inl.h" +#include "lib/jpegli/entropy_coding-inl.h" + +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { + +static const int kStreamingModeCoefficients = 0; +static const int kStreamingModeTokens = 1; +static const int kStreamingModeBits = 2; + +namespace { +void ZigZagShuffle(int32_t* JXL_RESTRICT block) { + // TODO(szabadka) SIMDify this. + int32_t tmp[DCTSIZE2]; + tmp[0] = block[0]; + tmp[1] = block[1]; + tmp[2] = block[8]; + tmp[3] = block[16]; + tmp[4] = block[9]; + tmp[5] = block[2]; + tmp[6] = block[3]; + tmp[7] = block[10]; + tmp[8] = block[17]; + tmp[9] = block[24]; + tmp[10] = block[32]; + tmp[11] = block[25]; + tmp[12] = block[18]; + tmp[13] = block[11]; + tmp[14] = block[4]; + tmp[15] = block[5]; + tmp[16] = block[12]; + tmp[17] = block[19]; + tmp[18] = block[26]; + tmp[19] = block[33]; + tmp[20] = block[40]; + tmp[21] = block[48]; + tmp[22] = block[41]; + tmp[23] = block[34]; + tmp[24] = block[27]; + tmp[25] = block[20]; + tmp[26] = block[13]; + tmp[27] = block[6]; + tmp[28] = block[7]; + tmp[29] = block[14]; + tmp[30] = block[21]; + tmp[31] = block[28]; + tmp[32] = block[35]; + tmp[33] = block[42]; + tmp[34] = block[49]; + tmp[35] = block[56]; + tmp[36] = block[57]; + tmp[37] = block[50]; + tmp[38] = block[43]; + tmp[39] = block[36]; + tmp[40] = block[29]; + tmp[41] = block[22]; + tmp[42] = block[15]; + tmp[43] = block[23]; + tmp[44] = block[30]; + tmp[45] = block[37]; + tmp[46] = block[44]; + tmp[47] = block[51]; + tmp[48] = block[58]; + tmp[49] = block[59]; + tmp[50] = block[52]; + tmp[51] = block[45]; + tmp[52] = block[38]; + tmp[53] = block[31]; + tmp[54] = block[39]; + tmp[55] = block[46]; + tmp[56] = block[53]; + tmp[57] = block[60]; + tmp[58] = block[61]; + tmp[59] = block[54]; + tmp[60] = block[47]; + tmp[61] = block[55]; + tmp[62] = block[62]; + tmp[63] = block[63]; + memcpy(block, tmp, DCTSIZE2 * sizeof(tmp[0])); +} +} // namespace + +template +void ProcessiMCURow(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + JpegBitWriter* bw = &m->bw; + int xsize_mcus = DivCeil(cinfo->image_width, 8 * cinfo->max_h_samp_factor); + int ysize_mcus = DivCeil(cinfo->image_height, 8 * cinfo->max_v_samp_factor); + int mcu_y = m->next_iMCU_row; + int32_t* block = m->block_tmp; + int32_t* symbols = m->block_tmp + DCTSIZE2; + int32_t* nonzero_idx = m->block_tmp + 3 * DCTSIZE2; + coeff_t* JXL_RESTRICT last_dc_coeff = m->last_dc_coeff; + bool adaptive_quant = m->use_adaptive_quantization && m->psnr_target == 0; + JBLOCKARRAY blocks[kMaxComponents]; + if (kMode == kStreamingModeCoefficients) { + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + int by0 = mcu_y * comp->v_samp_factor; + int block_rows_left = comp->height_in_blocks - by0; + int max_block_rows = std::min(comp->v_samp_factor, block_rows_left); + blocks[c] = (*cinfo->mem->access_virt_barray)( + reinterpret_cast(cinfo), m->coeff_buffers[c], by0, + max_block_rows, true); + } + } + if (kMode == kStreamingModeTokens) { + TokenArray* ta = &m->token_arrays[m->cur_token_array]; + int max_tokens_per_mcu_row = MaxNumTokensPerMCURow(cinfo); + if (ta->num_tokens + max_tokens_per_mcu_row > m->num_tokens) { + if (ta->tokens) { + m->total_num_tokens += ta->num_tokens; + ++m->cur_token_array; + ta = &m->token_arrays[m->cur_token_array]; + } + m->num_tokens = + EstimateNumTokens(cinfo, mcu_y, ysize_mcus, m->total_num_tokens, + max_tokens_per_mcu_row); + ta->tokens = Allocate(cinfo, m->num_tokens, JPOOL_IMAGE); + m->next_token = ta->tokens; + } + } + const float* imcu_start[kMaxComponents]; + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + imcu_start[c] = m->raw_data[c]->Row(mcu_y * comp->v_samp_factor * DCTSIZE); + } + const float* qf = nullptr; + if (adaptive_quant) { + qf = m->quant_field.Row(0); + } + HuffmanCodeTable* dc_code = nullptr; + HuffmanCodeTable* ac_code = nullptr; + const size_t qf_stride = m->quant_field.stride(); + for (int mcu_x = 0; mcu_x < xsize_mcus; ++mcu_x) { + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + if (kMode == kStreamingModeBits) { + dc_code = &m->coding_tables[m->context_map[c]]; + ac_code = &m->coding_tables[m->context_map[c + 4]]; + } + float* JXL_RESTRICT qmc = m->quant_mul[c]; + const size_t stride = m->raw_data[c]->stride(); + const int h_factor = m->h_factor[c]; + const float* zero_bias_offset = m->zero_bias_offset[c]; + const float* zero_bias_mul = m->zero_bias_mul[c]; + float aq_strength = 0.0f; + for (int iy = 0; iy < comp->v_samp_factor; ++iy) { + for (int ix = 0; ix < comp->h_samp_factor; ++ix) { + size_t by = mcu_y * comp->v_samp_factor + iy; + size_t bx = mcu_x * comp->h_samp_factor + ix; + if (bx >= comp->width_in_blocks || by >= comp->height_in_blocks) { + if (kMode == kStreamingModeTokens) { + *m->next_token++ = Token(c, 0, 0); + *m->next_token++ = Token(c + 4, 0, 0); + } else if (kMode == kStreamingModeBits) { + WriteBits(bw, dc_code->depth[0], dc_code->code[0]); + WriteBits(bw, ac_code->depth[0], ac_code->code[0]); + } + continue; + } + if (adaptive_quant) { + aq_strength = qf[iy * qf_stride + bx * h_factor]; + } + const float* pixels = imcu_start[c] + (iy * stride + bx) * DCTSIZE; + ComputeCoefficientBlock(pixels, stride, qmc, last_dc_coeff[c], + aq_strength, zero_bias_offset, zero_bias_mul, + m->dct_buffer, block); + if (kMode == kStreamingModeCoefficients) { + JCOEF* cblock = &blocks[c][iy][bx][0]; + for (int k = 0; k < DCTSIZE2; ++k) { + cblock[k] = block[kJPEGNaturalOrder[k]]; + } + } + block[0] -= last_dc_coeff[c]; + last_dc_coeff[c] += block[0]; + if (kMode == kStreamingModeTokens) { + ComputeTokensForBlock(block, 0, c, c + 4, + &m->next_token); + } else if (kMode == kStreamingModeBits) { + ZigZagShuffle(block); + const int num_nonzeros = CompactBlock(block, nonzero_idx); + const bool emit_eob = nonzero_idx[num_nonzeros - 1] < 1008; + ComputeSymbols(num_nonzeros, nonzero_idx, block, symbols); + WriteBlock(symbols, block, num_nonzeros, emit_eob, dc_code, ac_code, + bw); + } + } + } + } + } + if (kMode == kStreamingModeTokens) { + TokenArray* ta = &m->token_arrays[m->cur_token_array]; + ta->num_tokens = m->next_token - ta->tokens; + ScanTokenInfo* sti = &m->scan_token_info[0]; + sti->num_tokens = m->total_num_tokens + ta->num_tokens; + sti->restarts[0] = sti->num_tokens; + } +} + +void ComputeCoefficientsForiMCURow(j_compress_ptr cinfo) { + ProcessiMCURow(cinfo); +} + +void ComputeTokensForiMCURow(j_compress_ptr cinfo) { + ProcessiMCURow(cinfo); +} + +void WriteiMCURow(j_compress_ptr cinfo) { + ProcessiMCURow(cinfo); +} + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace jpegli { +HWY_EXPORT(ComputeCoefficientsForiMCURow); +HWY_EXPORT(ComputeTokensForiMCURow); +HWY_EXPORT(WriteiMCURow); + +void ComputeCoefficientsForiMCURow(j_compress_ptr cinfo) { + HWY_DYNAMIC_DISPATCH(ComputeCoefficientsForiMCURow)(cinfo); +} + +void ComputeTokensForiMCURow(j_compress_ptr cinfo) { + HWY_DYNAMIC_DISPATCH(ComputeTokensForiMCURow)(cinfo); +} + +void WriteiMCURow(j_compress_ptr cinfo) { + HWY_DYNAMIC_DISPATCH(WriteiMCURow)(cinfo); +} + +} // namespace jpegli +#endif // HWY_ONCE diff --git a/media/libjxl/src/lib/jpegli/encode_streaming.h b/media/libjxl/src/lib/jpegli/encode_streaming.h new file mode 100644 index 0000000000..69acff4eaf --- /dev/null +++ b/media/libjxl/src/lib/jpegli/encode_streaming.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_JPEGLI_ENCODE_STREAMING_H_ +#define LIB_JPEGLI_ENCODE_STREAMING_H_ + +#include "lib/jpegli/encode_internal.h" + +namespace jpegli { + +void ComputeCoefficientsForiMCURow(j_compress_ptr cinfo); + +void ComputeTokensForiMCURow(j_compress_ptr cinfo); + +void WriteiMCURow(j_compress_ptr cinfo); + +} // namespace jpegli + +#endif // LIB_JPEGLI_ENCODE_STREAMING_H_ diff --git a/media/libjxl/src/lib/jpegli/entropy_coding-inl.h b/media/libjxl/src/lib/jpegli/entropy_coding-inl.h new file mode 100644 index 0000000000..bfb436d795 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/entropy_coding-inl.h @@ -0,0 +1,213 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#if defined(LIB_JPEGLI_ENTROPY_CODING_INL_H_) == defined(HWY_TARGET_TOGGLE) +#ifdef LIB_JPEGLI_ENTROPY_CODING_INL_H_ +#undef LIB_JPEGLI_ENTROPY_CODING_INL_H_ +#else +#define LIB_JPEGLI_ENTROPY_CODING_INL_H_ +#endif + +#include "lib/jxl/base/compiler_specific.h" + +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { +namespace { + +// These templates are not found via ADL. +using hwy::HWY_NAMESPACE::Abs; +using hwy::HWY_NAMESPACE::Add; +using hwy::HWY_NAMESPACE::And; +using hwy::HWY_NAMESPACE::AndNot; +using hwy::HWY_NAMESPACE::Compress; +using hwy::HWY_NAMESPACE::CountTrue; +using hwy::HWY_NAMESPACE::Eq; +using hwy::HWY_NAMESPACE::GetLane; +using hwy::HWY_NAMESPACE::MaskFromVec; +using hwy::HWY_NAMESPACE::Max; +using hwy::HWY_NAMESPACE::Not; +using hwy::HWY_NAMESPACE::Or; +using hwy::HWY_NAMESPACE::ShiftRight; +using hwy::HWY_NAMESPACE::Shl; +using hwy::HWY_NAMESPACE::Sub; + +using DI = HWY_FULL(int32_t); +constexpr DI di; + +template +JXL_INLINE V NumBits(DI di, const V x) { + // TODO(szabadka) Add faster implementations for some specific architectures. + const auto b1 = And(x, Set(di, 1)); + const auto b2 = And(x, Set(di, 2)); + const auto b3 = Sub((And(x, Set(di, 4))), Set(di, 1)); + const auto b4 = Sub((And(x, Set(di, 8))), Set(di, 4)); + const auto b5 = Sub((And(x, Set(di, 16))), Set(di, 11)); + const auto b6 = Sub((And(x, Set(di, 32))), Set(di, 26)); + const auto b7 = Sub((And(x, Set(di, 64))), Set(di, 57)); + const auto b8 = Sub((And(x, Set(di, 128))), Set(di, 120)); + const auto b9 = Sub((And(x, Set(di, 256))), Set(di, 247)); + const auto b10 = Sub((And(x, Set(di, 512))), Set(di, 502)); + const auto b11 = Sub((And(x, Set(di, 1024))), Set(di, 1013)); + const auto b12 = Sub((And(x, Set(di, 2048))), Set(di, 2036)); + return Max(Max(Max(Max(b1, b2), Max(b3, b4)), Max(Max(b5, b6), Max(b7, b8))), + Max(Max(b9, b10), Max(b11, b12))); +} + +// Coefficient indexes pre-multiplied by 16 for the symbol calculation. +HWY_ALIGN constexpr int32_t kIndexes[64] = { + 0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, + 208, 224, 240, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400, + 416, 432, 448, 464, 480, 496, 512, 528, 544, 560, 576, 592, 608, + 624, 640, 656, 672, 688, 704, 720, 736, 752, 768, 784, 800, 816, + 832, 848, 864, 880, 896, 912, 928, 944, 960, 976, 992, 1008, +}; + +JXL_INLINE int CompactBlock(int32_t* JXL_RESTRICT block, + int32_t* JXL_RESTRICT nonzero_idx) { + const auto zero = Zero(di); + HWY_ALIGN constexpr int32_t dc_mask_lanes[HWY_LANES(DI)] = {-1}; + const auto dc_mask = MaskFromVec(Load(di, dc_mask_lanes)); + int num_nonzeros = 0; + int k = 0; + { + const auto coef = Load(di, block); + const auto idx = Load(di, kIndexes); + const auto nonzero_mask = Or(dc_mask, Not(Eq(coef, zero))); + const auto nzero_coef = Compress(coef, nonzero_mask); + const auto nzero_idx = Compress(idx, nonzero_mask); + StoreU(nzero_coef, di, &block[num_nonzeros]); + StoreU(nzero_idx, di, &nonzero_idx[num_nonzeros]); + num_nonzeros += CountTrue(di, nonzero_mask); + k += Lanes(di); + } + for (; k < DCTSIZE2; k += Lanes(di)) { + const auto coef = Load(di, &block[k]); + const auto idx = Load(di, &kIndexes[k]); + const auto nonzero_mask = Not(Eq(coef, zero)); + const auto nzero_coef = Compress(coef, nonzero_mask); + const auto nzero_idx = Compress(idx, nonzero_mask); + StoreU(nzero_coef, di, &block[num_nonzeros]); + StoreU(nzero_idx, di, &nonzero_idx[num_nonzeros]); + num_nonzeros += CountTrue(di, nonzero_mask); + } + return num_nonzeros; +} + +JXL_INLINE void ComputeSymbols(const int num_nonzeros, + int32_t* JXL_RESTRICT nonzero_idx, + int32_t* JXL_RESTRICT block, + int32_t* JXL_RESTRICT symbols) { + nonzero_idx[-1] = -16; + const auto one = Set(di, 1); + const auto offset = Set(di, 16); + for (int i = 0; i < num_nonzeros; i += Lanes(di)) { + const auto idx = Load(di, &nonzero_idx[i]); + const auto prev_idx = LoadU(di, &nonzero_idx[i - 1]); + const auto coeff = Load(di, &block[i]); + const auto nbits = NumBits(di, Abs(coeff)); + const auto mask = ShiftRight<8 * sizeof(int32_t) - 1>(coeff); + const auto bits = And(Add(coeff, mask), Sub(Shl(one, nbits), one)); + const auto symbol = Sub(Add(nbits, idx), Add(prev_idx, offset)); + Store(symbol, di, symbols + i); + Store(bits, di, block + i); + } +} + +template +int NumNonZero8x8ExceptDC(const T* block) { + const HWY_CAPPED(T, 8) di; + + const auto zero = Zero(di); + // Add FFFF for every zero coefficient, negate to get #zeros. + auto neg_sum_zero = zero; + { + // First row has DC, so mask + const size_t y = 0; + HWY_ALIGN const T dc_mask_lanes[8] = {-1}; + + for (size_t x = 0; x < 8; x += Lanes(di)) { + const auto dc_mask = Load(di, dc_mask_lanes + x); + + // DC counts as zero so we don't include it in nzeros. + const auto coef = AndNot(dc_mask, Load(di, &block[y * 8 + x])); + + neg_sum_zero = Add(neg_sum_zero, VecFromMask(di, Eq(coef, zero))); + } + } + // Remaining rows: no mask + for (size_t y = 1; y < 8; y++) { + for (size_t x = 0; x < 8; x += Lanes(di)) { + const auto coef = Load(di, &block[y * 8 + x]); + neg_sum_zero = Add(neg_sum_zero, VecFromMask(di, Eq(coef, zero))); + } + } + + // We want 64 - sum_zero, add because neg_sum_zero is already negated. + return kDCTBlockSize + GetLane(SumOfLanes(di, neg_sum_zero)); +} + +template +void ComputeTokensForBlock(const T* block, int last_dc, int dc_ctx, int ac_ctx, + Token** tokens_ptr) { + Token* next_token = *tokens_ptr; + coeff_t temp2; + coeff_t temp; + temp = block[0] - last_dc; + if (temp == 0) { + *next_token++ = Token(dc_ctx, 0, 0); + } else { + temp2 = temp; + if (temp < 0) { + temp = -temp; + temp2--; + } + int dc_nbits = jxl::FloorLog2Nonzero(temp) + 1; + int dc_mask = (1 << dc_nbits) - 1; + *next_token++ = Token(dc_ctx, dc_nbits, temp2 & dc_mask); + } + int num_nonzeros = NumNonZero8x8ExceptDC(block); + for (int k = 1; k < 64; ++k) { + if (num_nonzeros == 0) { + *next_token++ = Token(ac_ctx, 0, 0); + break; + } + int r = 0; + if (zig_zag_order) { + while ((temp = block[k]) == 0) { + r++; + k++; + } + } else { + while ((temp = block[kJPEGNaturalOrder[k]]) == 0) { + r++; + k++; + } + } + --num_nonzeros; + if (temp < 0) { + temp = -temp; + temp2 = ~temp; + } else { + temp2 = temp; + } + while (r > 15) { + *next_token++ = Token(ac_ctx, 0xf0, 0); + r -= 16; + } + int ac_nbits = jxl::FloorLog2Nonzero(temp) + 1; + int ac_mask = (1 << ac_nbits) - 1; + int symbol = (r << 4u) + ac_nbits; + *next_token++ = Token(ac_ctx, symbol, temp2 & ac_mask); + } + *tokens_ptr = next_token; +} + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); +#endif // LIB_JPEGLI_ENTROPY_CODING_INL_H_ diff --git a/media/libjxl/src/lib/jpegli/entropy_coding.cc b/media/libjxl/src/lib/jpegli/entropy_coding.cc new file mode 100644 index 0000000000..d7d928099b --- /dev/null +++ b/media/libjxl/src/lib/jpegli/entropy_coding.cc @@ -0,0 +1,826 @@ +// 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/jpegli/entropy_coding.h" + +#include + +#include "lib/jpegli/encode_internal.h" +#include "lib/jpegli/error.h" +#include "lib/jpegli/huffman.h" +#include "lib/jxl/base/bits.h" + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jpegli/entropy_coding.cc" +#include +#include + +#include "lib/jpegli/entropy_coding-inl.h" + +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { + +void ComputeTokensSequential(const coeff_t* block, int last_dc, int dc_ctx, + int ac_ctx, Token** tokens_ptr) { + ComputeTokensForBlock(block, last_dc, dc_ctx, ac_ctx, + tokens_ptr); +} + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace jpegli { + +size_t MaxNumTokensPerMCURow(j_compress_ptr cinfo) { + int MCUs_per_row = DivCeil(cinfo->image_width, 8 * cinfo->max_h_samp_factor); + size_t blocks_per_mcu = 0; + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + blocks_per_mcu += comp->h_samp_factor * comp->v_samp_factor; + } + return kDCTBlockSize * blocks_per_mcu * MCUs_per_row; +} + +size_t EstimateNumTokens(j_compress_ptr cinfo, size_t mcu_y, size_t ysize_mcus, + size_t num_tokens, size_t max_per_row) { + size_t estimate; + if (mcu_y == 0) { + estimate = 16 * max_per_row; + } else { + estimate = (4 * ysize_mcus * num_tokens) / (3 * mcu_y); + } + size_t mcus_left = ysize_mcus - mcu_y; + return std::min(mcus_left * max_per_row, + std::max(max_per_row, estimate - num_tokens)); +} + +namespace { +HWY_EXPORT(ComputeTokensSequential); + +void TokenizeProgressiveDC(const coeff_t* coeffs, int context, int Al, + coeff_t* last_dc_coeff, Token** next_token) { + coeff_t temp2; + coeff_t temp; + temp2 = coeffs[0] >> Al; + temp = temp2 - *last_dc_coeff; + *last_dc_coeff = temp2; + temp2 = temp; + if (temp < 0) { + temp = -temp; + temp2--; + } + int nbits = (temp == 0) ? 0 : (jxl::FloorLog2Nonzero(temp) + 1); + int bits = temp2 & ((1 << nbits) - 1); + *(*next_token)++ = Token(context, nbits, bits); +} + +void TokenizeACProgressiveScan(j_compress_ptr cinfo, int scan_index, + int context, ScanTokenInfo* sti) { + jpeg_comp_master* m = cinfo->master; + const jpeg_scan_info* scan_info = &cinfo->scan_info[scan_index]; + const int comp_idx = scan_info->component_index[0]; + const jpeg_component_info* comp = &cinfo->comp_info[comp_idx]; + const int Al = scan_info->Al; + const int Ss = scan_info->Ss; + const int Se = scan_info->Se; + const size_t restart_interval = sti->restart_interval; + int restarts_to_go = restart_interval; + size_t num_blocks = comp->height_in_blocks * comp->width_in_blocks; + size_t num_restarts = + restart_interval > 0 ? DivCeil(num_blocks, restart_interval) : 1; + size_t restart_idx = 0; + int eob_run = 0; + TokenArray* ta = &m->token_arrays[m->cur_token_array]; + sti->token_offset = m->total_num_tokens + ta->num_tokens; + sti->restarts = Allocate(cinfo, num_restarts, JPOOL_IMAGE); + const auto emit_eob_run = [&]() { + int nbits = jxl::FloorLog2Nonzero(eob_run); + int symbol = nbits << 4u; + *m->next_token++ = Token(context, symbol, eob_run & ((1 << nbits) - 1)); + eob_run = 0; + }; + for (JDIMENSION by = 0; by < comp->height_in_blocks; ++by) { + JBLOCKARRAY blocks = (*cinfo->mem->access_virt_barray)( + reinterpret_cast(cinfo), m->coeff_buffers[comp_idx], by, + 1, FALSE); + // Each coefficient can appear in at most one token, but we have to reserve + // one extra EOBrun token that was rolled over from the previous block-row + // and has to be flushed at the end. + int max_tokens_per_row = 1 + comp->width_in_blocks * (Se - Ss + 1); + if (ta->num_tokens + max_tokens_per_row > m->num_tokens) { + if (ta->tokens) { + m->total_num_tokens += ta->num_tokens; + ++m->cur_token_array; + ta = &m->token_arrays[m->cur_token_array]; + } + m->num_tokens = + EstimateNumTokens(cinfo, by, comp->height_in_blocks, + m->total_num_tokens, max_tokens_per_row); + ta->tokens = Allocate(cinfo, m->num_tokens, JPOOL_IMAGE); + m->next_token = ta->tokens; + } + for (JDIMENSION bx = 0; bx < comp->width_in_blocks; ++bx) { + if (restart_interval > 0 && restarts_to_go == 0) { + if (eob_run > 0) emit_eob_run(); + ta->num_tokens = m->next_token - ta->tokens; + sti->restarts[restart_idx++] = m->total_num_tokens + ta->num_tokens; + restarts_to_go = restart_interval; + } + const coeff_t* block = &blocks[0][bx][0]; + coeff_t temp2; + coeff_t temp; + int r = 0; + int num_nzeros = 0; + int num_future_nzeros = 0; + for (int k = Ss; k <= Se; ++k) { + temp = block[k]; + if (temp == 0) { + r++; + continue; + } + if (temp < 0) { + temp = -temp; + temp >>= Al; + temp2 = ~temp; + } else { + temp >>= Al; + temp2 = temp; + } + if (temp == 0) { + r++; + num_future_nzeros++; + continue; + } + if (eob_run > 0) emit_eob_run(); + while (r > 15) { + *m->next_token++ = Token(context, 0xf0, 0); + r -= 16; + } + int nbits = jxl::FloorLog2Nonzero(temp) + 1; + int symbol = (r << 4u) + nbits; + *m->next_token++ = Token(context, symbol, temp2 & ((1 << nbits) - 1)); + ++num_nzeros; + r = 0; + } + if (r > 0) { + ++eob_run; + if (eob_run == 0x7FFF) emit_eob_run(); + } + sti->num_nonzeros += num_nzeros; + sti->num_future_nonzeros += num_future_nzeros; + --restarts_to_go; + } + ta->num_tokens = m->next_token - ta->tokens; + } + if (eob_run > 0) { + emit_eob_run(); + ++ta->num_tokens; + } + sti->num_tokens = m->total_num_tokens + ta->num_tokens - sti->token_offset; + sti->restarts[restart_idx++] = m->total_num_tokens + ta->num_tokens; +} + +void TokenizeACRefinementScan(j_compress_ptr cinfo, int scan_index, + ScanTokenInfo* sti) { + jpeg_comp_master* m = cinfo->master; + const jpeg_scan_info* scan_info = &cinfo->scan_info[scan_index]; + const int comp_idx = scan_info->component_index[0]; + const jpeg_component_info* comp = &cinfo->comp_info[comp_idx]; + const int Al = scan_info->Al; + const int Ss = scan_info->Ss; + const int Se = scan_info->Se; + const size_t restart_interval = sti->restart_interval; + int restarts_to_go = restart_interval; + RefToken token; + int eob_run = 0; + int eob_refbits = 0; + size_t num_blocks = comp->height_in_blocks * comp->width_in_blocks; + size_t num_restarts = + restart_interval > 0 ? DivCeil(num_blocks, restart_interval) : 1; + sti->tokens = m->next_refinement_token; + sti->refbits = m->next_refinement_bit; + sti->eobruns = Allocate(cinfo, num_blocks / 2, JPOOL_IMAGE); + sti->restarts = Allocate(cinfo, num_restarts, JPOOL_IMAGE); + RefToken* next_token = sti->tokens; + RefToken* next_eob_token = next_token; + uint8_t* next_ref_bit = sti->refbits; + uint16_t* next_eobrun = sti->eobruns; + size_t restart_idx = 0; + for (JDIMENSION by = 0; by < comp->height_in_blocks; ++by) { + JBLOCKARRAY blocks = (*cinfo->mem->access_virt_barray)( + reinterpret_cast(cinfo), m->coeff_buffers[comp_idx], by, + 1, FALSE); + for (JDIMENSION bx = 0; bx < comp->width_in_blocks; ++bx) { + if (restart_interval > 0 && restarts_to_go == 0) { + sti->restarts[restart_idx++] = next_token - sti->tokens; + restarts_to_go = restart_interval; + next_eob_token = next_token; + eob_run = eob_refbits = 0; + } + const coeff_t* block = &blocks[0][bx][0]; + int num_eob_refinement_bits = 0; + int num_refinement_bits = 0; + int num_nzeros = 0; + int r = 0; + for (int k = Ss; k <= Se; ++k) { + int absval = block[k]; + if (absval == 0) { + r++; + continue; + } + const int mask = absval >> (8 * sizeof(int) - 1); + absval += mask; + absval ^= mask; + absval >>= Al; + if (absval == 0) { + r++; + continue; + } + while (r > 15) { + token.symbol = 0xf0; + token.refbits = num_refinement_bits; + *next_token++ = token; + r -= 16; + num_eob_refinement_bits += num_refinement_bits; + num_refinement_bits = 0; + } + if (absval > 1) { + *next_ref_bit++ = absval & 1u; + ++num_refinement_bits; + continue; + } + int symbol = (r << 4u) + 1 + ((mask + 1) << 1); + token.symbol = symbol; + token.refbits = num_refinement_bits; + *next_token++ = token; + ++num_nzeros; + num_refinement_bits = 0; + num_eob_refinement_bits = 0; + r = 0; + next_eob_token = next_token; + eob_run = eob_refbits = 0; + } + if (r > 0 || num_eob_refinement_bits + num_refinement_bits > 0) { + ++eob_run; + eob_refbits += num_eob_refinement_bits + num_refinement_bits; + if (eob_refbits > 255) { + ++next_eob_token; + eob_refbits = num_eob_refinement_bits + num_refinement_bits; + eob_run = 1; + } + next_token = next_eob_token; + next_token->refbits = eob_refbits; + if (eob_run == 1) { + next_token->symbol = 0; + } else if (eob_run == 2) { + next_token->symbol = 16; + *next_eobrun++ = 0; + } else if ((eob_run & (eob_run - 1)) == 0) { + next_token->symbol += 16; + next_eobrun[-1] = 0; + } else { + ++next_eobrun[-1]; + } + ++next_token; + if (eob_run == 0x7fff) { + next_eob_token = next_token; + eob_run = eob_refbits = 0; + } + } + sti->num_nonzeros += num_nzeros; + --restarts_to_go; + } + } + sti->num_tokens = next_token - sti->tokens; + sti->restarts[restart_idx++] = sti->num_tokens; + m->next_refinement_token = next_token; + m->next_refinement_bit = next_ref_bit; +} + +void TokenizeScan(j_compress_ptr cinfo, size_t scan_index, int ac_ctx_offset, + ScanTokenInfo* sti) { + const jpeg_scan_info* scan_info = &cinfo->scan_info[scan_index]; + if (scan_info->Ss > 0) { + if (scan_info->Ah == 0) { + TokenizeACProgressiveScan(cinfo, scan_index, ac_ctx_offset, sti); + } else { + TokenizeACRefinementScan(cinfo, scan_index, sti); + } + return; + } + + jpeg_comp_master* m = cinfo->master; + size_t restart_interval = sti->restart_interval; + int restarts_to_go = restart_interval; + coeff_t last_dc_coeff[MAX_COMPS_IN_SCAN] = {0}; + + // "Non-interleaved" means color data comes in separate scans, in other words + // each scan can contain only one color component. + const bool is_interleaved = (scan_info->comps_in_scan > 1); + const bool is_progressive = FROM_JXL_BOOL(cinfo->progressive_mode); + const int Ah = scan_info->Ah; + const int Al = scan_info->Al; + 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; + + if (Ah > 0) { + sti->refbits = Allocate(cinfo, sti->num_blocks, JPOOL_IMAGE); + } else if (cinfo->progressive_mode) { + if (ta->num_tokens + sti->num_blocks > m->num_tokens) { + if (ta->tokens) { + m->total_num_tokens += ta->num_tokens; + ++m->cur_token_array; + ta = &m->token_arrays[m->cur_token_array]; + } + m->num_tokens = sti->num_blocks; + ta->tokens = Allocate(cinfo, m->num_tokens, JPOOL_IMAGE); + m->next_token = ta->tokens; + } + } + + JBLOCKARRAY blocks[MAX_COMPS_IN_SCAN]; + size_t block_idx = 0; + for (size_t mcu_y = 0; mcu_y < sti->MCU_rows_in_scan; ++mcu_y) { + for (int i = 0; i < scan_info->comps_in_scan; ++i) { + int comp_idx = scan_info->component_index[i]; + jpeg_component_info* comp = &cinfo->comp_info[comp_idx]; + int n_blocks_y = is_interleaved ? comp->v_samp_factor : 1; + int by0 = mcu_y * n_blocks_y; + int block_rows_left = comp->height_in_blocks - by0; + int max_block_rows = std::min(n_blocks_y, block_rows_left); + blocks[i] = (*cinfo->mem->access_virt_barray)( + reinterpret_cast(cinfo), m->coeff_buffers[comp_idx], + by0, max_block_rows, FALSE); + } + if (!cinfo->progressive_mode) { + int max_tokens_per_mcu_row = MaxNumTokensPerMCURow(cinfo); + if (ta->num_tokens + max_tokens_per_mcu_row > m->num_tokens) { + if (ta->tokens) { + m->total_num_tokens += ta->num_tokens; + ++m->cur_token_array; + ta = &m->token_arrays[m->cur_token_array]; + } + m->num_tokens = + EstimateNumTokens(cinfo, mcu_y, sti->MCU_rows_in_scan, + m->total_num_tokens, max_tokens_per_mcu_row); + ta->tokens = Allocate(cinfo, m->num_tokens, JPOOL_IMAGE); + m->next_token = ta->tokens; + } + } + for (size_t mcu_x = 0; mcu_x < sti->MCUs_per_row; ++mcu_x) { + // Possibly emit a restart marker. + if (restart_interval > 0 && restarts_to_go == 0) { + restarts_to_go = restart_interval; + memset(last_dc_coeff, 0, sizeof(last_dc_coeff)); + ta->num_tokens = m->next_token - ta->tokens; + sti->restarts[restart_idx++] = + Ah > 0 ? block_idx : m->total_num_tokens + ta->num_tokens; + } + // Encode one MCU + for (int i = 0; i < scan_info->comps_in_scan; ++i) { + int comp_idx = scan_info->component_index[i]; + jpeg_component_info* comp = &cinfo->comp_info[comp_idx]; + int n_blocks_y = is_interleaved ? comp->v_samp_factor : 1; + int n_blocks_x = is_interleaved ? comp->h_samp_factor : 1; + for (int iy = 0; iy < n_blocks_y; ++iy) { + for (int ix = 0; ix < n_blocks_x; ++ix) { + 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 = kSinkBlock; + } else { + block = &blocks[i][iy][block_x][0]; + } + if (!is_progressive) { + HWY_DYNAMIC_DISPATCH(ComputeTokensSequential) + (block, last_dc_coeff[i], comp_idx, ac_ctx_offset + i, + &m->next_token); + last_dc_coeff[i] = block[0]; + } else { + if (Ah == 0) { + TokenizeProgressiveDC(block, comp_idx, Al, last_dc_coeff + i, + &m->next_token); + } else { + sti->refbits[block_idx] = (block[0] >> Al) & 1; + } + } + ++block_idx; + } + } + } + --restarts_to_go; + } + ta->num_tokens = m->next_token - ta->tokens; + } + JXL_DASSERT(block_idx == sti->num_blocks); + sti->num_tokens = + Ah > 0 ? sti->num_blocks + : m->total_num_tokens + ta->num_tokens - sti->token_offset; + sti->restarts[restart_idx++] = + Ah > 0 ? sti->num_blocks : m->total_num_tokens + ta->num_tokens; + if (Ah == 0 && cinfo->progressive_mode) { + JXL_DASSERT(sti->num_blocks == sti->num_tokens); + } +} + +} // namespace + +void TokenizeJpeg(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + std::vector processed(cinfo->num_scans); + size_t max_refinement_tokens = 0; + size_t num_refinement_bits = 0; + int num_refinement_scans[kMaxComponents][DCTSIZE2] = {}; + int max_num_refinement_scans = 0; + for (int i = 0; i < cinfo->num_scans; ++i) { + const jpeg_scan_info* si = &cinfo->scan_info[i]; + ScanTokenInfo* sti = &m->scan_token_info[i]; + if (si->Ss > 0 && si->Ah == 0 && si->Al > 0) { + int offset = m->ac_ctx_offset[i]; + int comp_idx = si->component_index[0]; + TokenizeScan(cinfo, i, offset, sti); + processed[i] = 1; + max_refinement_tokens += sti->num_future_nonzeros; + for (int k = si->Ss; k <= si->Se; ++k) { + num_refinement_scans[comp_idx][k] = si->Al; + } + max_num_refinement_scans = std::max(max_num_refinement_scans, si->Al); + num_refinement_bits += sti->num_nonzeros; + } + if (si->Ss > 0 && si->Ah > 0) { + int comp_idx = si->component_index[0]; + const jpeg_component_info* comp = &cinfo->comp_info[comp_idx]; + size_t num_blocks = comp->width_in_blocks * comp->height_in_blocks; + max_refinement_tokens += (1 + (si->Se - si->Ss) / 16) * num_blocks; + } + } + if (max_refinement_tokens > 0) { + m->next_refinement_token = + Allocate(cinfo, max_refinement_tokens, JPOOL_IMAGE); + } + for (int j = 0; j < max_num_refinement_scans; ++j) { + uint8_t* refinement_bits = + Allocate(cinfo, num_refinement_bits, JPOOL_IMAGE); + m->next_refinement_bit = refinement_bits; + size_t new_refinement_bits = 0; + for (int i = 0; i < cinfo->num_scans; ++i) { + const jpeg_scan_info* si = &cinfo->scan_info[i]; + int comp_idx = si->component_index[0]; + ScanTokenInfo* sti = &m->scan_token_info[i]; + if (si->Ss > 0 && si->Ah > 0 && + si->Ah == num_refinement_scans[comp_idx][si->Ss] - j) { + int offset = m->ac_ctx_offset[i]; + TokenizeScan(cinfo, i, offset, sti); + processed[i] = 1; + new_refinement_bits += sti->num_nonzeros; + } + } + JXL_DASSERT(m->next_refinement_bit <= + refinement_bits + num_refinement_bits); + num_refinement_bits += new_refinement_bits; + } + for (int i = 0; i < cinfo->num_scans; ++i) { + if (processed[i]) { + continue; + } + int offset = m->ac_ctx_offset[i]; + TokenizeScan(cinfo, i, offset, &m->scan_token_info[i]); + processed[i] = 1; + } +} + +namespace { + +struct Histogram { + int count[kJpegHuffmanAlphabetSize]; + Histogram() { memset(count, 0, sizeof(count)); } +}; + +void BuildHistograms(j_compress_ptr cinfo, Histogram* histograms) { + jpeg_comp_master* m = cinfo->master; + size_t num_token_arrays = m->cur_token_array + 1; + for (size_t i = 0; i < num_token_arrays; ++i) { + Token* tokens = m->token_arrays[i].tokens; + size_t num_tokens = m->token_arrays[i].num_tokens; + for (size_t j = 0; j < num_tokens; ++j) { + Token t = tokens[j]; + ++histograms[t.context].count[t.symbol]; + } + } + for (int i = 0; i < cinfo->num_scans; ++i) { + const jpeg_scan_info& si = cinfo->scan_info[i]; + const ScanTokenInfo& sti = m->scan_token_info[i]; + if (si.Ss > 0 && si.Ah > 0) { + int context = m->ac_ctx_offset[i]; + int* ac_histo = &histograms[context].count[0]; + for (size_t j = 0; j < sti.num_tokens; ++j) { + ++ac_histo[sti.tokens[j].symbol & 253]; + } + } + } +} + +struct JpegClusteredHistograms { + std::vector histograms; + std::vector histogram_indexes; + std::vector slot_ids; +}; + +float HistogramCost(const Histogram& histo) { + std::vector counts(kJpegHuffmanAlphabetSize + 1); + std::vector depths(kJpegHuffmanAlphabetSize + 1); + for (size_t i = 0; i < kJpegHuffmanAlphabetSize; ++i) { + counts[i] = histo.count[i]; + } + counts[kJpegHuffmanAlphabetSize] = 1; + CreateHuffmanTree(counts.data(), counts.size(), kJpegHuffmanMaxBitLength, + depths.data()); + size_t header_bits = (1 + kJpegHuffmanMaxBitLength) * 8; + size_t data_bits = 0; + for (size_t i = 0; i < kJpegHuffmanAlphabetSize; ++i) { + if (depths[i] > 0) { + header_bits += 8; + data_bits += counts[i] * depths[i]; + } + } + return header_bits + data_bits; +} + +void AddHistograms(const Histogram& a, const Histogram& b, Histogram* c) { + for (size_t i = 0; i < kJpegHuffmanAlphabetSize; ++i) { + c->count[i] = a.count[i] + b.count[i]; + } +} + +bool IsEmptyHistogram(const Histogram& histo) { + for (int count : histo.count) { + if (count) return false; + } + return true; +} + +void ClusterJpegHistograms(j_compress_ptr cinfo, const Histogram* histograms, + size_t num, JpegClusteredHistograms* clusters) { + clusters->histogram_indexes.resize(num); + std::vector slot_histograms; + std::vector slot_costs; + for (size_t i = 0; i < num; ++i) { + const Histogram& cur = histograms[i]; + if (IsEmptyHistogram(cur)) { + continue; + } + float best_cost = HistogramCost(cur); + size_t best_slot = slot_histograms.size(); + for (size_t j = 0; j < slot_histograms.size(); ++j) { + size_t prev_idx = slot_histograms[j]; + const Histogram& prev = clusters->histograms[prev_idx]; + Histogram combined; + AddHistograms(prev, cur, &combined); + float combined_cost = HistogramCost(combined); + float cost = combined_cost - slot_costs[j]; + if (cost < best_cost) { + best_cost = cost; + best_slot = j; + } + } + if (best_slot == slot_histograms.size()) { + // Create new histogram. + size_t histogram_index = clusters->histograms.size(); + clusters->histograms.push_back(cur); + clusters->histogram_indexes[i] = histogram_index; + if (best_slot < 4) { + // We have a free slot, so we put the new histogram there. + slot_histograms.push_back(histogram_index); + slot_costs.push_back(best_cost); + } else { + // TODO(szabadka) Find the best histogram to replce. + best_slot = (clusters->slot_ids.back() + 1) % 4; + } + slot_histograms[best_slot] = histogram_index; + slot_costs[best_slot] = best_cost; + clusters->slot_ids.push_back(best_slot); + } else { + // Merge this histogram with a previous one. + size_t histogram_index = slot_histograms[best_slot]; + const Histogram& prev = clusters->histograms[histogram_index]; + AddHistograms(prev, cur, &clusters->histograms[histogram_index]); + clusters->histogram_indexes[i] = histogram_index; + JPEGLI_CHECK(clusters->slot_ids[histogram_index] == best_slot); + slot_costs[best_slot] += best_cost; + } + } +} + +void CopyHuffmanTable(j_compress_ptr cinfo, int index, bool is_dc, + int* inv_slot_map, uint8_t* slot_id_map, + JHUFF_TBL* huffman_tables, size_t* num_huffman_tables) { + const char* type = is_dc ? "DC" : "AC"; + if (index < 0 || index >= NUM_HUFF_TBLS) { + JPEGLI_ERROR("Invalid %s Huffman table index %d", type, index); + } + // Check if we have already copied this Huffman table. + int slot_idx = index + (is_dc ? 0 : NUM_HUFF_TBLS); + if (inv_slot_map[slot_idx] != -1) { + return; + } + inv_slot_map[slot_idx] = *num_huffman_tables; + // Look up and validate Huffman table. + JHUFF_TBL* table = + is_dc ? cinfo->dc_huff_tbl_ptrs[index] : cinfo->ac_huff_tbl_ptrs[index]; + if (table == nullptr) { + JPEGLI_ERROR("Missing %s Huffman table %d", type, index); + } + ValidateHuffmanTable(reinterpret_cast(cinfo), table, is_dc); + // Copy Huffman table to the end of the list and save slot id. + slot_id_map[*num_huffman_tables] = index + (is_dc ? 0 : 0x10); + memcpy(&huffman_tables[*num_huffman_tables], table, sizeof(JHUFF_TBL)); + ++(*num_huffman_tables); +} + +void BuildJpegHuffmanTable(const Histogram& histo, JHUFF_TBL* table) { + std::vector counts(kJpegHuffmanAlphabetSize + 1); + std::vector depths(kJpegHuffmanAlphabetSize + 1); + for (size_t j = 0; j < kJpegHuffmanAlphabetSize; ++j) { + counts[j] = histo.count[j]; + } + counts[kJpegHuffmanAlphabetSize] = 1; + CreateHuffmanTree(counts.data(), counts.size(), kJpegHuffmanMaxBitLength, + depths.data()); + memset(table, 0, sizeof(JHUFF_TBL)); + for (size_t i = 0; i < kJpegHuffmanAlphabetSize; ++i) { + if (depths[i] > 0) { + ++table->bits[depths[i]]; + } + } + int offset[kJpegHuffmanMaxBitLength + 1] = {0}; + for (size_t i = 1; i <= kJpegHuffmanMaxBitLength; ++i) { + offset[i] = offset[i - 1] + table->bits[i - 1]; + } + for (size_t i = 0; i < kJpegHuffmanAlphabetSize; ++i) { + if (depths[i] > 0) { + table->huffval[offset[depths[i]]++] = i; + } + } +} + +} // namespace + +void CopyHuffmanTables(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + size_t max_huff_tables = 2 * cinfo->num_components; + // Copy Huffman tables and save slot ids. + m->huffman_tables = Allocate(cinfo, max_huff_tables, JPOOL_IMAGE); + m->slot_id_map = Allocate(cinfo, max_huff_tables, JPOOL_IMAGE); + m->num_huffman_tables = 0; + int inv_slot_map[8] = {-1, -1, -1, -1, -1, -1, -1, -1}; + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + CopyHuffmanTable(cinfo, comp->dc_tbl_no, /*is_dc=*/true, &inv_slot_map[0], + m->slot_id_map, m->huffman_tables, &m->num_huffman_tables); + CopyHuffmanTable(cinfo, comp->ac_tbl_no, /*is_dc=*/false, &inv_slot_map[0], + m->slot_id_map, m->huffman_tables, &m->num_huffman_tables); + } + // Compute context map. + m->context_map = Allocate(cinfo, 8, JPOOL_IMAGE); + memset(m->context_map, 0, 8); + for (int c = 0; c < cinfo->num_components; ++c) { + m->context_map[c] = inv_slot_map[cinfo->comp_info[c].dc_tbl_no]; + } + int ac_ctx = 4; + for (int i = 0; i < cinfo->num_scans; ++i) { + const jpeg_scan_info* si = &cinfo->scan_info[i]; + if (si->Se > 0) { + for (int j = 0; j < si->comps_in_scan; ++j) { + int c = si->component_index[j]; + jpeg_component_info* comp = &cinfo->comp_info[c]; + m->context_map[ac_ctx++] = inv_slot_map[comp->ac_tbl_no + 4]; + } + } + } +} + +void OptimizeHuffmanCodes(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + // Build DC and AC histograms. + std::vector histograms(m->num_contexts); + BuildHistograms(cinfo, histograms.data()); + + // Cluster DC histograms. + JpegClusteredHistograms dc_clusters; + ClusterJpegHistograms(cinfo, histograms.data(), cinfo->num_components, + &dc_clusters); + + // Cluster AC histograms. + JpegClusteredHistograms ac_clusters; + ClusterJpegHistograms(cinfo, histograms.data() + 4, m->num_contexts - 4, + &ac_clusters); + + // Create Huffman tables and slot ids clusters. + size_t num_dc_huff = dc_clusters.histograms.size(); + m->num_huffman_tables = num_dc_huff + ac_clusters.histograms.size(); + m->huffman_tables = + Allocate(cinfo, m->num_huffman_tables, JPOOL_IMAGE); + m->slot_id_map = Allocate(cinfo, m->num_huffman_tables, JPOOL_IMAGE); + for (size_t i = 0; i < m->num_huffman_tables; ++i) { + JHUFF_TBL huff_table = {}; + if (i < dc_clusters.histograms.size()) { + m->slot_id_map[i] = i; + BuildJpegHuffmanTable(dc_clusters.histograms[i], &huff_table); + } else { + m->slot_id_map[i] = 16 + ac_clusters.slot_ids[i - num_dc_huff]; + BuildJpegHuffmanTable(ac_clusters.histograms[i - num_dc_huff], + &huff_table); + } + memcpy(&m->huffman_tables[i], &huff_table, sizeof(huff_table)); + } + + // Create context map from clustered histogram indexes. + m->context_map = Allocate(cinfo, m->num_contexts, JPOOL_IMAGE); + memset(m->context_map, 0, m->num_contexts); + for (size_t i = 0; i < m->num_contexts; ++i) { + if (i < static_cast(cinfo->num_components)) { + m->context_map[i] = dc_clusters.histogram_indexes[i]; + } else if (i >= 4) { + m->context_map[i] = num_dc_huff + ac_clusters.histogram_indexes[i - 4]; + } + } +} + +namespace { + +constexpr uint8_t kNumExtraBits[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 6, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 7, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 11, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 13, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 14, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, // +}; + +void BuildHuffmanCodeTable(const JHUFF_TBL& table, HuffmanCodeTable* code) { + int huff_code[kJpegHuffmanAlphabetSize]; + // +1 for a sentinel element. + uint32_t huff_size[kJpegHuffmanAlphabetSize + 1]; + int p = 0; + for (size_t l = 1; l <= kJpegHuffmanMaxBitLength; ++l) { + int i = table.bits[l]; + while (i--) huff_size[p++] = l; + } + + // Reuse sentinel element. + int last_p = p; + huff_size[last_p] = 0; + + int next_code = 0; + uint32_t si = huff_size[0]; + p = 0; + while (huff_size[p]) { + while ((huff_size[p]) == si) { + huff_code[p++] = next_code; + next_code++; + } + next_code <<= 1; + si++; + } + for (p = 0; p < last_p; p++) { + int i = table.huffval[p]; + int nbits = kNumExtraBits[i]; + code->depth[i] = huff_size[p] + nbits; + code->code[i] = huff_code[p] << nbits; + } +} + +} // namespace + +void InitEntropyCoder(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + m->coding_tables = + Allocate(cinfo, m->num_huffman_tables, JPOOL_IMAGE); + for (size_t i = 0; i < m->num_huffman_tables; ++i) { + BuildHuffmanCodeTable(m->huffman_tables[i], &m->coding_tables[i]); + } +} + +} // namespace jpegli +#endif // HWY_ONCE diff --git a/media/libjxl/src/lib/jpegli/entropy_coding.h b/media/libjxl/src/lib/jpegli/entropy_coding.h new file mode 100644 index 0000000000..a552219ec3 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/entropy_coding.h @@ -0,0 +1,28 @@ +// 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_JPEGLI_ENTROPY_CODING_H_ +#define LIB_JPEGLI_ENTROPY_CODING_H_ + +#include "lib/jpegli/common.h" + +namespace jpegli { + +size_t MaxNumTokensPerMCURow(j_compress_ptr cinfo); + +size_t EstimateNumTokens(j_compress_ptr cinfo, size_t mcu_y, size_t ysize_mcus, + size_t num_tokens, size_t max_per_row); + +void TokenizeJpeg(j_compress_ptr cinfo); + +void CopyHuffmanTables(j_compress_ptr cinfo); + +void OptimizeHuffmanCodes(j_compress_ptr cinfo); + +void InitEntropyCoder(j_compress_ptr cinfo); + +} // namespace jpegli + +#endif // LIB_JPEGLI_ENTROPY_CODING_H_ diff --git a/media/libjxl/src/lib/jpegli/error.cc b/media/libjxl/src/lib/jpegli/error.cc new file mode 100644 index 0000000000..b3ccab0452 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/error.cc @@ -0,0 +1,102 @@ +// 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/jpegli/error.h" + +#include +#include +#include + +#include + +#include "lib/jpegli/common.h" + +namespace jpegli { + +const char* const kErrorMessageTable[] = { + "Message codes are not supported, error message is in msg_parm.s string", +}; + +bool FormatString(char* buffer, const char* format, ...) { + va_list args; + va_start(args, format); + vsnprintf(buffer, JMSG_STR_PARM_MAX, format, args); // notypo + va_end(args); + return false; +} + +void ExitWithAbort(j_common_ptr cinfo) { + (*cinfo->err->output_message)(cinfo); + jpegli_destroy(cinfo); + exit(EXIT_FAILURE); +} + +void EmitMessage(j_common_ptr cinfo, int msg_level) { + if (msg_level < 0) { + if (cinfo->err->num_warnings <= 5 || cinfo->err->trace_level >= 3) { + (*cinfo->err->output_message)(cinfo); + } + ++cinfo->err->num_warnings; + } else if (cinfo->err->trace_level >= msg_level) { + (*cinfo->err->output_message)(cinfo); + } +} + +void OutputMessage(j_common_ptr cinfo) { + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + fprintf(stderr, "%s\n", buffer); +} + +void FormatMessage(j_common_ptr cinfo, char* buffer) { + jpeg_error_mgr* err = cinfo->err; + int code = err->msg_code; + if (code == 0) { + memcpy(buffer, cinfo->err->msg_parm.s, JMSG_STR_PARM_MAX); + } else if (err->addon_message_table != nullptr && + code >= err->first_addon_message && + code <= err->last_addon_message) { + std::string msg(err->addon_message_table[code - err->first_addon_message]); + if (msg.find("%s") != std::string::npos) { + snprintf(buffer, JMSG_LENGTH_MAX, msg.data(), err->msg_parm.s); + } else { + snprintf(buffer, JMSG_LENGTH_MAX, msg.data(), err->msg_parm.i[0], + err->msg_parm.i[1], err->msg_parm.i[2], err->msg_parm.i[3], + err->msg_parm.i[4], err->msg_parm.i[5], err->msg_parm.i[6], + err->msg_parm.i[7]); + } + } else { + snprintf(buffer, JMSG_LENGTH_MAX, "%s", kErrorMessageTable[0]); + } +} + +void ResetErrorManager(j_common_ptr cinfo) { + memset(cinfo->err->msg_parm.s, 0, JMSG_STR_PARM_MAX); + cinfo->err->msg_code = 0; + cinfo->err->num_warnings = 0; +} + +} // namespace jpegli + +struct jpeg_error_mgr* jpegli_std_error(struct jpeg_error_mgr* err) { + err->error_exit = jpegli::ExitWithAbort; + err->emit_message = jpegli::EmitMessage; + err->output_message = jpegli::OutputMessage; + err->format_message = jpegli::FormatMessage; + err->reset_error_mgr = jpegli::ResetErrorManager; + memset(err->msg_parm.s, 0, JMSG_STR_PARM_MAX); + err->trace_level = 0; + err->num_warnings = 0; + // We don't support message codes and message table, but we define one here + // in case the application has a custom format_message and tries to access + // these fields there. + err->msg_code = 0; + err->jpeg_message_table = jpegli::kErrorMessageTable; + err->last_jpeg_message = 0; + err->addon_message_table = nullptr; + err->first_addon_message = 0; + err->last_addon_message = 0; + return err; +} diff --git a/media/libjxl/src/lib/jpegli/error.h b/media/libjxl/src/lib/jpegli/error.h new file mode 100644 index 0000000000..fb5dc411a8 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/error.h @@ -0,0 +1,47 @@ +// 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_JPEGLI_ERROR_H_ +#define LIB_JPEGLI_ERROR_H_ + +#include +#include + +#include "lib/jpegli/common.h" +#include "lib/jxl/base/compiler_specific.h" + +namespace jpegli { + +bool FormatString(char* buffer, const char* format, ...); + +} // namespace jpegli + +// `error_exit` should be no-return; but let's add some guarantees on our side. +#define JPEGLI_ERROR(format, ...) \ + jpegli::FormatString(cinfo->err->msg_parm.s, ("%s:%d: " format), __FILE__, \ + __LINE__, ##__VA_ARGS__), \ + (*cinfo->err->error_exit)(reinterpret_cast(cinfo)), \ + JXL_CRASH() + +#define JPEGLI_WARN(format, ...) \ + jpegli::FormatString(cinfo->err->msg_parm.s, ("%s:%d: " format), __FILE__, \ + __LINE__, ##__VA_ARGS__), \ + (*cinfo->err->emit_message)(reinterpret_cast(cinfo), -1) + +#define JPEGLI_TRACE(level, format, ...) \ + if (cinfo->err->trace_level >= (level)) \ + jpegli::FormatString(cinfo->err->msg_parm.s, ("%s:%d: " format), __FILE__, \ + __LINE__, ##__VA_ARGS__), \ + (*cinfo->err->emit_message)(reinterpret_cast(cinfo), \ + (level)) + +#define JPEGLI_CHECK(condition) \ + do { \ + if (!(condition)) { \ + JPEGLI_ERROR("JPEGLI_CHECK: %s", #condition); \ + } \ + } while (0) + +#endif // LIB_JPEGLI_ERROR_H_ diff --git a/media/libjxl/src/lib/jpegli/error_handling_test.cc b/media/libjxl/src/lib/jpegli/error_handling_test.cc new file mode 100644 index 0000000000..da85dbcb0b --- /dev/null +++ b/media/libjxl/src/lib/jpegli/error_handling_test.cc @@ -0,0 +1,1284 @@ +// 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/jpegli/common.h" +#include "lib/jpegli/decode.h" +#include "lib/jpegli/encode.h" +#include "lib/jpegli/libjpeg_test_util.h" +#include "lib/jpegli/test_params.h" +#include "lib/jpegli/test_utils.h" +#include "lib/jpegli/testing.h" + +namespace jpegli { +namespace { + +TEST(EncoderErrorHandlingTest, MinimalSuccess) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + { + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + jpegli_start_compress(&cinfo, TRUE); + JSAMPLE image[1] = {0}; + JSAMPROW row[] = {image}; + jpegli_write_scanlines(&cinfo, row, 1); + jpegli_finish_compress(&cinfo); + return true; + }; + EXPECT_TRUE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + } + TestImage output; + DecodeWithLibjpeg(CompressParams(), DecompressParams(), nullptr, 0, buffer, + buffer_size, &output); + EXPECT_EQ(1, output.xsize); + EXPECT_EQ(1, output.ysize); + EXPECT_EQ(1, output.components); + EXPECT_EQ(0, output.pixels[0]); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, NoDestination) { + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); +} + +TEST(EncoderErrorHandlingTest, NoImageDimensions) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, ImageTooBig) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 100000; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, NoInputComponents) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + jpegli_set_defaults(&cinfo); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, TooManyInputComponents) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1000; + jpegli_set_defaults(&cinfo); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, NoSetDefaults) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_start_compress(&cinfo, TRUE); + JSAMPLE image[1] = {0}; + JSAMPROW row[] = {image}; + jpegli_write_scanlines(&cinfo, row, 1); + jpegli_finish_compress(&cinfo); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, NoStartCompress) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + JSAMPLE image[1] = {0}; + JSAMPROW row[] = {image}; + jpegli_write_scanlines(&cinfo, row, 1); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, NoWriteScanlines) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + jpegli_start_compress(&cinfo, TRUE); + jpegli_finish_compress(&cinfo); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, NoWriteAllScanlines) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 2; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + jpegli_start_compress(&cinfo, TRUE); + JSAMPLE image[1] = {0}; + JSAMPROW row[] = {image}; + jpegli_write_scanlines(&cinfo, row, 1); + jpegli_finish_compress(&cinfo); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidQuantValue) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + cinfo.quant_tbl_ptrs[0] = + jpegli_alloc_quant_table(reinterpret_cast(&cinfo)); + for (UINT16& q : cinfo.quant_tbl_ptrs[0]->quantval) { + q = 0; + } + jpegli_start_compress(&cinfo, TRUE); + JSAMPLE image[1] = {0}; + JSAMPROW row[] = {image}; + jpegli_write_scanlines(&cinfo, row, 1); + jpegli_finish_compress(&cinfo); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidQuantTableIndex) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + cinfo.comp_info[0].quant_tbl_no = 3; + jpegli_start_compress(&cinfo, TRUE); + JSAMPLE image[1] = {0}; + JSAMPROW row[] = {image}; + jpegli_write_scanlines(&cinfo, row, 1); + jpegli_finish_compress(&cinfo); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch1) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + cinfo.num_components = 100; + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch2) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + cinfo.num_components = 2; + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch3) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + cinfo.num_components = 2; + cinfo.comp_info[1].h_samp_factor = cinfo.comp_info[1].v_samp_factor = 1; + jpegli_start_compress(&cinfo, TRUE); + JSAMPLE image[1] = {0}; + JSAMPROW row[] = {image}; + jpegli_write_scanlines(&cinfo, row, 1); + jpegli_finish_compress(&cinfo); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch4) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + cinfo.in_color_space = JCS_RGB; + jpegli_set_defaults(&cinfo); + jpegli_start_compress(&cinfo, TRUE); + JSAMPLE image[1] = {0}; + JSAMPROW row[] = {image}; + jpegli_write_scanlines(&cinfo, row, 1); + jpegli_finish_compress(&cinfo); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch5) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_GRAYSCALE; + jpegli_set_defaults(&cinfo); + jpegli_start_compress(&cinfo, TRUE); + JSAMPLE image[3] = {0}; + JSAMPROW row[] = {image}; + jpegli_write_scanlines(&cinfo, row, 1); + jpegli_finish_compress(&cinfo); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, NumberOfComponentsMismatch6) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + jpegli_set_defaults(&cinfo); + cinfo.num_components = 2; + jpegli_start_compress(&cinfo, TRUE); + JSAMPLE image[3] = {0}; + JSAMPROW row[] = {image}; + jpegli_write_scanlines(&cinfo, row, 1); + jpegli_finish_compress(&cinfo); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidColorTransform) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_YCbCr; + jpegli_set_defaults(&cinfo); + cinfo.jpeg_color_space = JCS_RGB; + jpegli_start_compress(&cinfo, TRUE); + JSAMPLE image[3] = {0}; + JSAMPROW row[] = {image}; + jpegli_write_scanlines(&cinfo, row, 1); + jpegli_finish_compress(&cinfo); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, DuplicateComponentIds) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 3; + jpegli_set_defaults(&cinfo); + cinfo.comp_info[0].component_id = 0; + cinfo.comp_info[1].component_id = 0; + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidComponentIndex) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 3; + jpegli_set_defaults(&cinfo); + cinfo.comp_info[0].component_index = 17; + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, ArithmeticCoding) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 3; + jpegli_set_defaults(&cinfo); + cinfo.arith_code = TRUE; + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, CCIR601Sampling) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 3; + jpegli_set_defaults(&cinfo); + cinfo.CCIR601_sampling = TRUE; + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidScanScript1) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + static constexpr jpeg_scan_info kScript[] = {{1, {0}, 0, 63, 0, 0}}; // + cinfo.scan_info = kScript; + cinfo.num_scans = 0; + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidScanScript2) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + static constexpr jpeg_scan_info kScript[] = {{2, {0, 1}, 0, 63, 0, 0}}; // + cinfo.scan_info = kScript; + cinfo.num_scans = ARRAY_SIZE(kScript); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidScanScript3) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + static constexpr jpeg_scan_info kScript[] = {{5, {0}, 0, 63, 0, 0}}; // + cinfo.scan_info = kScript; + cinfo.num_scans = ARRAY_SIZE(kScript); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidScanScript4) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 2; + jpegli_set_defaults(&cinfo); + static constexpr jpeg_scan_info kScript[] = {{2, {0, 0}, 0, 63, 0, 0}}; // + cinfo.scan_info = kScript; + cinfo.num_scans = ARRAY_SIZE(kScript); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidScanScript5) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 2; + jpegli_set_defaults(&cinfo); + static constexpr jpeg_scan_info kScript[] = {{2, {1, 0}, 0, 63, 0, 0}}; // + cinfo.scan_info = kScript; + cinfo.num_scans = ARRAY_SIZE(kScript); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidScanScript6) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + static constexpr jpeg_scan_info kScript[] = {{1, {0}, 0, 64, 0, 0}}; // + cinfo.scan_info = kScript; + cinfo.num_scans = ARRAY_SIZE(kScript); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidScanScript7) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + static constexpr jpeg_scan_info kScript[] = {{1, {0}, 2, 1, 0, 0}}; // + cinfo.scan_info = kScript; + cinfo.num_scans = ARRAY_SIZE(kScript); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidScanScript8) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 2; + jpegli_set_defaults(&cinfo); + static constexpr jpeg_scan_info kScript[] = { + {1, {0}, 0, 63, 0, 0}, {1, {1}, 0, 0, 0, 0}, {1, {1}, 1, 63, 0, 0} // + }; + cinfo.scan_info = kScript; + cinfo.num_scans = ARRAY_SIZE(kScript); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidScanScript9) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + static constexpr jpeg_scan_info kScript[] = { + {1, {0}, 0, 1, 0, 0}, {1, {0}, 2, 63, 0, 0}, // + }; + cinfo.scan_info = kScript; + cinfo.num_scans = ARRAY_SIZE(kScript); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidScanScript10) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 2; + jpegli_set_defaults(&cinfo); + static constexpr jpeg_scan_info kScript[] = { + {2, {0, 1}, 0, 0, 0, 0}, {2, {0, 1}, 1, 63, 0, 0} // + }; + cinfo.scan_info = kScript; + cinfo.num_scans = ARRAY_SIZE(kScript); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidScanScript11) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + static constexpr jpeg_scan_info kScript[] = { + {1, {0}, 1, 63, 0, 0}, {1, {0}, 0, 0, 0, 0} // + }; + cinfo.scan_info = kScript; + cinfo.num_scans = ARRAY_SIZE(kScript); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidScanScript12) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + static constexpr jpeg_scan_info kScript[] = { + {1, {0}, 0, 0, 10, 1}, {1, {0}, 0, 0, 1, 0}, {1, {0}, 1, 63, 0, 0} // + }; + cinfo.scan_info = kScript; + cinfo.num_scans = ARRAY_SIZE(kScript); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, InvalidScanScript13) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + static constexpr jpeg_scan_info kScript[] = { + {1, {0}, 0, 0, 0, 2}, + {1, {0}, 0, 0, 1, 0}, + {1, {0}, 0, 0, 2, 1}, // + {1, {0}, 1, 63, 0, 0} // + }; + cinfo.scan_info = kScript; + cinfo.num_scans = ARRAY_SIZE(kScript); + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, MCUSizeTooBig) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 3; + jpegli_set_defaults(&cinfo); + jpegli_set_progressive_level(&cinfo, 0); + cinfo.comp_info[0].h_samp_factor = 3; + cinfo.comp_info[0].v_samp_factor = 3; + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, RestartIntervalTooBig) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 1; + jpegli_set_defaults(&cinfo); + cinfo.restart_interval = 1000000; + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, SamplingFactorTooBig) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 3; + jpegli_set_defaults(&cinfo); + cinfo.comp_info[0].h_samp_factor = 5; + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +TEST(EncoderErrorHandlingTest, NonIntegralSamplingRatio) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + cinfo.image_width = 1; + cinfo.image_height = 1; + cinfo.input_components = 3; + jpegli_set_defaults(&cinfo); + cinfo.comp_info[0].h_samp_factor = 3; + cinfo.comp_info[1].h_samp_factor = 2; + jpegli_start_compress(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + if (buffer) free(buffer); +} + +constexpr const char* kAddOnTable[] = {"First message", + "Second message with int param %d", + "Third message with string param %s"}; + +TEST(EncoderErrorHandlingTest, AddOnTableNoParam) { + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + cinfo.err->addon_message_table = kAddOnTable; + cinfo.err->first_addon_message = 10000; + cinfo.err->last_addon_message = 10002; + cinfo.err->msg_code = 10000; + (*cinfo.err->error_exit)(reinterpret_cast(&cinfo)); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); +} + +TEST(EncoderErrorHandlingTest, AddOnTableIntParam) { + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + cinfo.err->addon_message_table = kAddOnTable; + cinfo.err->first_addon_message = 10000; + cinfo.err->last_addon_message = 10002; + cinfo.err->msg_code = 10001; + cinfo.err->msg_parm.i[0] = 17; + (*cinfo.err->error_exit)(reinterpret_cast(&cinfo)); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); +} + +TEST(EncoderErrorHandlingTest, AddOnTableNoStringParam) { + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + cinfo.err->addon_message_table = kAddOnTable; + cinfo.err->first_addon_message = 10000; + cinfo.err->last_addon_message = 10002; + cinfo.err->msg_code = 10002; + memcpy(cinfo.err->msg_parm.s, "MESSAGE PARAM", 14); + (*cinfo.err->error_exit)(reinterpret_cast(&cinfo)); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_compress(&cinfo); +} + +const uint8_t kCompressed0[] = { + // SOI + 0xff, 0xd8, // + // SOF + 0xff, 0xc0, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, // + 0x01, 0x11, 0x00, // + // DQT + 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x03, 0x02, // + 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05, // + 0x08, 0x05, 0x05, 0x04, 0x04, 0x05, 0x0a, 0x07, 0x07, 0x06, // + 0x08, 0x0c, 0x0a, 0x0c, 0x0c, 0x0b, 0x0a, 0x0b, 0x0b, 0x0d, // + 0x0e, 0x12, 0x10, 0x0d, 0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10, // + 0x16, 0x10, 0x11, 0x13, 0x14, 0x15, 0x15, 0x15, 0x0c, 0x0f, // + 0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15, 0x14, // + // DHT + 0xff, 0xc4, 0x00, 0xd2, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, // + 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // + 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // + 0x09, 0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, // + 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, // + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, // + 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, // + 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, // + 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, // + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, // + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, // + 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, // + 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, // + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, // + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, // + 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, // + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, // + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, // + 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, // + 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, // + 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, // + 0xf9, 0xfa, // + // SOS + 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, // + // entropy coded data + 0xfc, 0xaa, 0xaf, // + // EOI + 0xff, 0xd9, // +}; +const size_t kLen0 = sizeof(kCompressed0); + +const size_t kSOFOffset = 2; +const size_t kDQTOffset = 15; +const size_t kDHTOffset = 84; +const size_t kSOSOffset = 296; + +TEST(DecoderErrorHandlingTest, MinimalSuccess) { + ASSERT_TRUE(kCompressed0[kDQTOffset] == 0xff); + ASSERT_TRUE(kCompressed0[kSOFOffset] == 0xff); + ASSERT_TRUE(kCompressed0[kDHTOffset] == 0xff); + ASSERT_TRUE(kCompressed0[kSOSOffset] == 0xff); + jpeg_decompress_struct cinfo = {}; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + jpegli_mem_src(&cinfo, kCompressed0, kLen0); + jpegli_read_header(&cinfo, TRUE); + EXPECT_EQ(1, cinfo.image_width); + EXPECT_EQ(1, cinfo.image_height); + jpegli_start_decompress(&cinfo); + JSAMPLE image[1]; + JSAMPROW row[] = {image}; + jpegli_read_scanlines(&cinfo, row, 1); + EXPECT_EQ(0, image[0]); + jpegli_finish_decompress(&cinfo); + return true; + }; + EXPECT_TRUE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); +} + +TEST(DecoderErrorHandlingTest, NoSource) { + jpeg_decompress_struct cinfo = {}; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + jpegli_read_header(&cinfo, TRUE); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); +} + +TEST(DecoderErrorHandlingTest, NoReadHeader) { + jpeg_decompress_struct cinfo = {}; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + jpegli_mem_src(&cinfo, kCompressed0, kLen0); + jpegli_start_decompress(&cinfo); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); +} + +TEST(DecoderErrorHandlingTest, NoStartDecompress) { + jpeg_decompress_struct cinfo = {}; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + jpegli_mem_src(&cinfo, kCompressed0, kLen0); + jpegli_read_header(&cinfo, TRUE); + EXPECT_EQ(1, cinfo.image_width); + EXPECT_EQ(1, cinfo.image_height); + JSAMPLE image[1]; + JSAMPROW row[] = {image}; + jpegli_read_scanlines(&cinfo, row, 1); + EXPECT_EQ(0, image[0]); + jpegli_finish_decompress(&cinfo); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); +} + +TEST(DecoderErrorHandlingTest, NoReadScanlines) { + jpeg_decompress_struct cinfo = {}; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + jpegli_mem_src(&cinfo, kCompressed0, kLen0); + jpegli_read_header(&cinfo, TRUE); + EXPECT_EQ(1, cinfo.image_width); + EXPECT_EQ(1, cinfo.image_height); + jpegli_start_decompress(&cinfo); + jpegli_finish_decompress(&cinfo); + return true; + }; + EXPECT_FALSE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); +} + +const size_t kMaxImageWidth = 0xffff; +JSAMPLE kOutputBuffer[MAX_COMPONENTS * kMaxImageWidth]; + +bool ParseCompressed(const std::vector& compressed) { + jpeg_decompress_struct cinfo = {}; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + jpegli_mem_src(&cinfo, compressed.data(), compressed.size()); + jpegli_read_header(&cinfo, TRUE); + jpegli_start_decompress(&cinfo); + for (JDIMENSION i = 0; i < cinfo.output_height; ++i) { + JSAMPROW row[] = {kOutputBuffer}; + jpegli_read_scanlines(&cinfo, row, 1); + } + jpegli_finish_decompress(&cinfo); + return true; + }; + bool retval = try_catch_block(); + jpegli_destroy_decompress(&cinfo); + return retval; +} + +TEST(DecoderErrorHandlingTest, NoSOI) { + for (int pos : {0, 1}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[pos] = 0; + EXPECT_FALSE(ParseCompressed(compressed)); + } +} + +TEST(DecoderErrorHandlingTest, InvalidDQT) { + // Bad marker length + for (int diff : {-2, -1, 1, 2}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kDQTOffset + 3] += diff; + EXPECT_FALSE(ParseCompressed(compressed)); + } + // invalid table index / precision + for (int val : {0x20, 0x05}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kDQTOffset + 4] = val; + EXPECT_FALSE(ParseCompressed(compressed)); + } + // zero quant value + for (int k : {0, 1, 17, 63}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kDQTOffset + 5 + k] = 0; + EXPECT_FALSE(ParseCompressed(compressed)); + } +} + +TEST(DecoderErrorHandlingTest, InvalidSOF) { + // Bad marker length + for (int diff : {-2, -1, 1, 2}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kSOFOffset + 3] += diff; + EXPECT_FALSE(ParseCompressed(compressed)); + } + // zero width, height or num_components + for (int pos : {6, 8, 9}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kSOFOffset + pos] = 0; + EXPECT_FALSE(ParseCompressed(compressed)); + } + // invalid data precision + for (int val : {0, 1, 127}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kSOFOffset + 4] = val; + EXPECT_FALSE(ParseCompressed(compressed)); + } + // too many num_components + for (int val : {5, 255}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kSOFOffset + 9] = val; + EXPECT_FALSE(ParseCompressed(compressed)); + } + // invalid sampling factors + for (int val : {0x00, 0x01, 0x10, 0x15, 0x51}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kSOFOffset + 11] = val; + EXPECT_FALSE(ParseCompressed(compressed)); + } + // invalid quant table index + for (int val : {5, 17}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kSOFOffset + 12] = val; + EXPECT_FALSE(ParseCompressed(compressed)); + } +} + +TEST(DecoderErrorHandlingTest, InvalidDHT) { + // Bad marker length + for (int diff : {-2, -1, 1, 2}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kDHTOffset + 3] += diff; + EXPECT_FALSE(ParseCompressed(compressed)); + } + { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kDHTOffset + 2] += 17; + EXPECT_FALSE(ParseCompressed(compressed)); + } + // invalid table slot_id + for (int val : {0x05, 0x15, 0x20}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kDHTOffset + 4] = val; + EXPECT_FALSE(ParseCompressed(compressed)); + } +} + +TEST(DecoderErrorHandlingTest, InvalidSOS) { + // Invalid comps_in_scan + for (int val : {2, 5, 17}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kSOSOffset + 4] = val; + EXPECT_FALSE(ParseCompressed(compressed)); + } + // invalid Huffman table indexes + for (int val : {0x05, 0x50, 0x15, 0x51}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kSOSOffset + 6] = val; + EXPECT_FALSE(ParseCompressed(compressed)); + } + // invalid Ss/Se + for (int pos : {7, 8}) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + compressed[kSOSOffset + pos] = 64; + EXPECT_FALSE(ParseCompressed(compressed)); + } +} + +TEST(DecoderErrorHandlingTest, MutateSingleBytes) { + for (size_t pos = 0; pos < kLen0; ++pos) { + std::vector compressed(kCompressed0, kCompressed0 + kLen0); + for (int val : {0x00, 0x0f, 0xf0, 0xff}) { + compressed[pos] = val; + ParseCompressed(compressed); + } + } +} + +} // namespace +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/fuzztest.h b/media/libjxl/src/lib/jpegli/fuzztest.h new file mode 100644 index 0000000000..d5cde97713 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/fuzztest.h @@ -0,0 +1,22 @@ +// 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_JPEGLI_FUZZTEST_H_ +#define LIB_JPEGLI_FUZZTEST_H_ + +#include "lib/jxl/base/compiler_specific.h" + +#if !defined(FUZZ_TEST) +struct FuzzTestSink { + template + FuzzTestSink WithSeeds(F /*f*/) { + return *this; + } +}; +#define FUZZ_TEST(A, B) \ + const JXL_MAYBE_UNUSED FuzzTestSink unused##A##B = FuzzTestSink() +#endif + +#endif // LIB_JPEGLI_FUZZTEST_H_ diff --git a/media/libjxl/src/lib/jpegli/huffman.cc b/media/libjxl/src/lib/jpegli/huffman.cc new file mode 100644 index 0000000000..6b068ab64f --- /dev/null +++ b/media/libjxl/src/lib/jpegli/huffman.cc @@ -0,0 +1,323 @@ +// 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/jpegli/huffman.h" + +#include +#include + +#include "lib/jpegli/common.h" +#include "lib/jpegli/error.h" +#include "lib/jxl/base/status.h" + +namespace jpegli { + +// Returns the table width of the next 2nd level table, count is the histogram +// of bit lengths for the remaining symbols, len is the code length of the next +// processed symbol. +static inline int NextTableBitSize(const int* count, int len) { + int left = 1 << (len - kJpegHuffmanRootTableBits); + while (len < static_cast(kJpegHuffmanMaxBitLength)) { + left -= count[len]; + if (left <= 0) break; + ++len; + left <<= 1; + } + return len - kJpegHuffmanRootTableBits; +} + +void BuildJpegHuffmanTable(const uint32_t* count, const uint32_t* symbols, + HuffmanTableEntry* lut) { + HuffmanTableEntry code; // current table entry + HuffmanTableEntry* table; // next available space in table + int len; // current code length + int idx; // symbol index + int key; // prefix code + int reps; // number of replicate key values in current table + int low; // low bits for current root entry + int table_bits; // key length of current table + int table_size; // size of current table + + // Make a local copy of the input bit length histogram. + int tmp_count[kJpegHuffmanMaxBitLength + 1] = {0}; + int total_count = 0; + for (len = 1; len <= static_cast(kJpegHuffmanMaxBitLength); ++len) { + tmp_count[len] = count[len]; + total_count += tmp_count[len]; + } + + table = lut; + table_bits = kJpegHuffmanRootTableBits; + table_size = 1 << table_bits; + + // Special case code with only one value. + if (total_count == 1) { + code.bits = 0; + code.value = symbols[0]; + for (key = 0; key < table_size; ++key) { + table[key] = code; + } + return; + } + + // Fill in root table. + key = 0; + idx = 0; + for (len = 1; len <= kJpegHuffmanRootTableBits; ++len) { + for (; tmp_count[len] > 0; --tmp_count[len]) { + code.bits = len; + code.value = symbols[idx++]; + reps = 1 << (kJpegHuffmanRootTableBits - len); + while (reps--) { + table[key++] = code; + } + } + } + + // Fill in 2nd level tables and add pointers to root table. + table += table_size; + table_size = 0; + low = 0; + for (len = kJpegHuffmanRootTableBits + 1; + len <= static_cast(kJpegHuffmanMaxBitLength); ++len) { + for (; tmp_count[len] > 0; --tmp_count[len]) { + // Start a new sub-table if the previous one is full. + if (low >= table_size) { + table += table_size; + table_bits = NextTableBitSize(tmp_count, len); + table_size = 1 << table_bits; + low = 0; + lut[key].bits = table_bits + kJpegHuffmanRootTableBits; + lut[key].value = (table - lut) - key; + ++key; + } + code.bits = len - kJpegHuffmanRootTableBits; + code.value = symbols[idx++]; + reps = 1 << (table_bits - code.bits); + while (reps--) { + table[low++] = code; + } + } + } +} + +// A node of a Huffman tree. +struct HuffmanTree { + HuffmanTree(uint32_t count, int16_t left, int16_t right) + : total_count(count), index_left(left), index_right_or_value(right) {} + uint32_t total_count; + int16_t index_left; + int16_t index_right_or_value; +}; + +void SetDepth(const HuffmanTree& p, HuffmanTree* pool, uint8_t* depth, + uint8_t level) { + if (p.index_left >= 0) { + ++level; + SetDepth(pool[p.index_left], pool, depth, level); + SetDepth(pool[p.index_right_or_value], pool, depth, level); + } else { + depth[p.index_right_or_value] = level; + } +} + +// Sort the root nodes, least popular first. +static JXL_INLINE bool Compare(const HuffmanTree& v0, const HuffmanTree& v1) { + return v0.total_count < v1.total_count; +} + +// This function will create a Huffman tree. +// +// The catch here is that the tree cannot be arbitrarily deep. +// Brotli specifies a maximum depth of 15 bits for "code trees" +// and 7 bits for "code length code trees." +// +// count_limit is the value that is to be faked as the minimum value +// and this minimum value is raised until the tree matches the +// maximum length requirement. +// +// This algorithm is not of excellent performance for very long data blocks, +// especially when population counts are longer than 2**tree_limit, but +// we are not planning to use this with extremely long blocks. +// +// See http://en.wikipedia.org/wiki/Huffman_coding +void CreateHuffmanTree(const uint32_t* data, const size_t length, + const int tree_limit, uint8_t* depth) { + // For block sizes below 64 kB, we never need to do a second iteration + // of this loop. Probably all of our block sizes will be smaller than + // that, so this loop is mostly of academic interest. If we actually + // would need this, we would be better off with the Katajainen algorithm. + for (uint32_t count_limit = 1;; count_limit *= 2) { + std::vector tree; + tree.reserve(2 * length + 1); + + for (size_t i = length; i != 0;) { + --i; + if (data[i]) { + const uint32_t count = std::max(data[i], count_limit - 1); + tree.emplace_back(count, -1, static_cast(i)); + } + } + + const size_t n = tree.size(); + if (n == 1) { + // Fake value; will be fixed on upper level. + depth[tree[0].index_right_or_value] = 1; + break; + } + + std::stable_sort(tree.begin(), tree.end(), Compare); + + // The nodes are: + // [0, n): the sorted leaf nodes that we start with. + // [n]: we add a sentinel here. + // [n + 1, 2n): new parent nodes are added here, starting from + // (n+1). These are naturally in ascending order. + // [2n]: we add a sentinel at the end as well. + // There will be (2n+1) elements at the end. + const HuffmanTree sentinel(std::numeric_limits::max(), -1, -1); + tree.push_back(sentinel); + tree.push_back(sentinel); + + size_t i = 0; // Points to the next leaf node. + size_t j = n + 1; // Points to the next non-leaf node. + for (size_t k = n - 1; k != 0; --k) { + size_t left; + size_t right; + if (tree[i].total_count <= tree[j].total_count) { + left = i; + ++i; + } else { + left = j; + ++j; + } + if (tree[i].total_count <= tree[j].total_count) { + right = i; + ++i; + } else { + right = j; + ++j; + } + + // The sentinel node becomes the parent node. + size_t j_end = tree.size() - 1; + tree[j_end].total_count = + tree[left].total_count + tree[right].total_count; + tree[j_end].index_left = static_cast(left); + tree[j_end].index_right_or_value = static_cast(right); + + // Add back the last sentinel node. + tree.push_back(sentinel); + } + JXL_DASSERT(tree.size() == 2 * n + 1); + SetDepth(tree[2 * n - 1], tree.data(), depth, 0); + + // We need to pack the Huffman tree in tree_limit bits. + // If this was not successful, add fake entities to the lowest values + // and retry. + if (*std::max_element(&depth[0], &depth[length]) <= tree_limit) { + break; + } + } +} + +void ValidateHuffmanTable(j_common_ptr cinfo, const JHUFF_TBL* table, + bool is_dc) { + size_t total_symbols = 0; + size_t total_p = 0; + size_t max_depth = 0; + for (size_t d = 1; d <= kJpegHuffmanMaxBitLength; ++d) { + uint8_t count = table->bits[d]; + if (count) { + total_symbols += count; + total_p += (1u << (kJpegHuffmanMaxBitLength - d)) * count; + max_depth = d; + } + } + total_p += 1u << (kJpegHuffmanMaxBitLength - max_depth); // sentinel symbol + if (total_symbols == 0) { + JPEGLI_ERROR("Empty Huffman table"); + } + if (total_symbols > kJpegHuffmanAlphabetSize) { + JPEGLI_ERROR("Too many symbols in Huffman table"); + } + if (total_p != (1u << kJpegHuffmanMaxBitLength)) { + JPEGLI_ERROR("Invalid bit length distribution"); + } + uint8_t symbol_seen[kJpegHuffmanAlphabetSize] = {}; + for (size_t i = 0; i < total_symbols; ++i) { + uint8_t symbol = table->huffval[i]; + if (symbol_seen[symbol]) { + JPEGLI_ERROR("Duplicate symbol %d in Huffman table", symbol); + } + symbol_seen[symbol] = 1; + } +} + +void AddStandardHuffmanTables(j_common_ptr cinfo, bool is_dc) { + // Huffman tables from the JPEG standard. + static constexpr JHUFF_TBL kStandardDCTables[2] = { + // DC luma + {{0, 0, 1, 5, 1, 1, 1, 1, 1, 1}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, + FALSE}, + // DC chroma + {{0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, + FALSE}}; + static constexpr JHUFF_TBL kStandardACTables[2] = { + // AC luma + {{0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125}, + {0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, + 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, + 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, + 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa}, + FALSE}, + // AC chroma + {{0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119}, + {0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, + 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, + 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, + 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, + 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa}, + FALSE}}; + const JHUFF_TBL* std_tables = is_dc ? kStandardDCTables : kStandardACTables; + JHUFF_TBL** tables; + if (cinfo->is_decompressor) { + j_decompress_ptr cinfo_d = reinterpret_cast(cinfo); + tables = is_dc ? cinfo_d->dc_huff_tbl_ptrs : cinfo_d->ac_huff_tbl_ptrs; + } else { + j_compress_ptr cinfo_c = reinterpret_cast(cinfo); + tables = is_dc ? cinfo_c->dc_huff_tbl_ptrs : cinfo_c->ac_huff_tbl_ptrs; + } + for (int i = 0; i < 2; ++i) { + if (tables[i] == nullptr) { + tables[i] = jpegli_alloc_huff_table(cinfo); + memcpy(tables[i], &std_tables[i], sizeof(JHUFF_TBL)); + ValidateHuffmanTable(cinfo, tables[i], is_dc); + } + } +} + +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/huffman.h b/media/libjxl/src/lib/jpegli/huffman.h new file mode 100644 index 0000000000..99549668cf --- /dev/null +++ b/media/libjxl/src/lib/jpegli/huffman.h @@ -0,0 +1,58 @@ +// 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_JPEGLI_HUFFMAN_H_ +#define LIB_JPEGLI_HUFFMAN_H_ + +#include +#include + +#include "lib/jpegli/common_internal.h" + +namespace jpegli { + +constexpr int kJpegHuffmanRootTableBits = 8; +// Maximum huffman lookup table size. +// Requirements: alphabet of 257 symbols (256 + 1 special symbol for the all 1s +// code) and max bit length 16, the root table has 8 bits. +// zlib/examples/enough.c works with an assumption that Huffman code is +// "complete". Input JPEGs might have this assumption broken, hence the +// following sum is used as estimate: +// + number of 1-st level cells +// + number of symbols +// + asymptotic amount of repeated 2nd level cells +// The third number is 1 + 3 + ... + 255 i.e. it is assumed that sub-table of +// each "size" might be almost completely be filled with repetitions. +// Total sum is slightly less than 1024,... +constexpr int kJpegHuffmanLutSize = 1024; + +struct HuffmanTableEntry { + uint8_t bits; // number of bits used for this symbol + uint16_t value; // symbol value or table offset +}; + +void BuildJpegHuffmanTable(const uint32_t* count, const uint32_t* symbols, + HuffmanTableEntry* lut); + +// This function will create a Huffman tree. +// +// The (data,length) contains the population counts. +// The tree_limit is the maximum bit depth of the Huffman codes. +// +// The depth contains the tree, i.e., how many bits are used for +// the symbol. +// +// See http://en.wikipedia.org/wiki/Huffman_coding +void CreateHuffmanTree(const uint32_t* data, size_t length, int tree_limit, + uint8_t* depth); + +void ValidateHuffmanTable(j_common_ptr cinfo, const JHUFF_TBL* table, + bool is_dc); + +void AddStandardHuffmanTables(j_common_ptr cinfo, bool is_dc); + +} // namespace jpegli + +#endif // LIB_JPEGLI_HUFFMAN_H_ diff --git a/media/libjxl/src/lib/jpegli/idct.cc b/media/libjxl/src/lib/jpegli/idct.cc new file mode 100644 index 0000000000..665a00d826 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/idct.cc @@ -0,0 +1,703 @@ +// 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/jpegli/idct.h" + +#include + +#include "lib/jpegli/decode_internal.h" +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/status.h" + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jpegli/idct.cc" +#include +#include + +#include "lib/jpegli/transpose-inl.h" + +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { + +// These templates are not found via ADL. +using hwy::HWY_NAMESPACE::Abs; +using hwy::HWY_NAMESPACE::Add; +using hwy::HWY_NAMESPACE::Gt; +using hwy::HWY_NAMESPACE::IfThenElseZero; +using hwy::HWY_NAMESPACE::Mul; +using hwy::HWY_NAMESPACE::MulAdd; +using hwy::HWY_NAMESPACE::NegMulAdd; +using hwy::HWY_NAMESPACE::Rebind; +using hwy::HWY_NAMESPACE::Sub; +using hwy::HWY_NAMESPACE::Vec; +using hwy::HWY_NAMESPACE::Xor; + +using D = HWY_FULL(float); +using DI = HWY_FULL(int32_t); +constexpr D d; +constexpr DI di; + +using D8 = HWY_CAPPED(float, 8); +constexpr D8 d8; + +void DequantBlock(const int16_t* JXL_RESTRICT qblock, + const float* JXL_RESTRICT dequant, + const float* JXL_RESTRICT biases, float* JXL_RESTRICT block) { + for (size_t k = 0; k < 64; k += Lanes(d)) { + const auto mul = Load(d, dequant + k); + const auto bias = Load(d, biases + k); + const Rebind di16; + const Vec quant_i = PromoteTo(di, Load(di16, qblock + k)); + const Rebind df; + const auto quant = ConvertTo(df, quant_i); + const auto abs_quant = Abs(quant); + const auto not_0 = Gt(abs_quant, Zero(df)); + const auto sign_quant = Xor(quant, abs_quant); + const auto biased_quant = Sub(quant, Xor(bias, sign_quant)); + const auto dequant = IfThenElseZero(not_0, Mul(biased_quant, mul)); + Store(dequant, d, block + k); + } +} + +template +void ForwardEvenOdd(const float* JXL_RESTRICT a_in, size_t a_in_stride, + float* JXL_RESTRICT a_out) { + for (size_t i = 0; i < N / 2; i++) { + auto in1 = LoadU(d8, a_in + 2 * i * a_in_stride); + Store(in1, d8, a_out + i * 8); + } + for (size_t i = N / 2; i < N; i++) { + auto in1 = LoadU(d8, a_in + (2 * (i - N / 2) + 1) * a_in_stride); + Store(in1, d8, a_out + i * 8); + } +} + +template +void BTranspose(float* JXL_RESTRICT coeff) { + for (size_t i = N - 1; i > 0; i--) { + auto in1 = Load(d8, coeff + i * 8); + auto in2 = Load(d8, coeff + (i - 1) * 8); + Store(Add(in1, in2), d8, coeff + i * 8); + } + constexpr float kSqrt2 = 1.41421356237f; + auto sqrt2 = Set(d8, kSqrt2); + auto in1 = Load(d8, coeff); + Store(Mul(in1, sqrt2), d8, coeff); +} + +// Constants for DCT implementation. Generated by the following snippet: +// for i in range(N // 2): +// print(1.0 / (2 * math.cos((i + 0.5) * math.pi / N)), end=", ") +template +struct WcMultipliers; + +template <> +struct WcMultipliers<4> { + static constexpr float kMultipliers[] = { + 0.541196100146197, + 1.3065629648763764, + }; +}; + +template <> +struct WcMultipliers<8> { + static constexpr float kMultipliers[] = { + 0.5097955791041592, + 0.6013448869350453, + 0.8999762231364156, + 2.5629154477415055, + }; +}; + +#if JXL_CXX_LANG < JXL_CXX_17 +constexpr float WcMultipliers<4>::kMultipliers[]; +constexpr float WcMultipliers<8>::kMultipliers[]; +#endif + +template +void MultiplyAndAdd(const float* JXL_RESTRICT coeff, float* JXL_RESTRICT out, + size_t out_stride) { + for (size_t i = 0; i < N / 2; i++) { + auto mul = Set(d8, WcMultipliers::kMultipliers[i]); + auto in1 = Load(d8, coeff + i * 8); + auto in2 = Load(d8, coeff + (N / 2 + i) * 8); + auto out1 = MulAdd(mul, in2, in1); + auto out2 = NegMulAdd(mul, in2, in1); + StoreU(out1, d8, out + i * out_stride); + StoreU(out2, d8, out + (N - i - 1) * out_stride); + } +} + +template +struct IDCT1DImpl; + +template <> +struct IDCT1DImpl<1> { + JXL_INLINE void operator()(const float* from, size_t from_stride, float* to, + size_t to_stride) { + StoreU(LoadU(d8, from), d8, to); + } +}; + +template <> +struct IDCT1DImpl<2> { + JXL_INLINE void operator()(const float* from, size_t from_stride, float* to, + size_t to_stride) { + JXL_DASSERT(from_stride >= 8); + JXL_DASSERT(to_stride >= 8); + auto in1 = LoadU(d8, from); + auto in2 = LoadU(d8, from + from_stride); + StoreU(Add(in1, in2), d8, to); + StoreU(Sub(in1, in2), d8, to + to_stride); + } +}; + +template +struct IDCT1DImpl { + void operator()(const float* from, size_t from_stride, float* to, + size_t to_stride) { + JXL_DASSERT(from_stride >= 8); + JXL_DASSERT(to_stride >= 8); + HWY_ALIGN float tmp[64]; + ForwardEvenOdd(from, from_stride, tmp); + IDCT1DImpl()(tmp, 8, tmp, 8); + BTranspose(tmp + N * 4); + IDCT1DImpl()(tmp + N * 4, 8, tmp + N * 4, 8); + MultiplyAndAdd(tmp, to, to_stride); + } +}; + +template +void IDCT1D(float* JXL_RESTRICT from, float* JXL_RESTRICT output, + size_t output_stride) { + for (size_t i = 0; i < 8; i += Lanes(d8)) { + IDCT1DImpl()(from + i, 8, output + i, output_stride); + } +} + +void ComputeScaledIDCT(float* JXL_RESTRICT block0, float* JXL_RESTRICT block1, + float* JXL_RESTRICT output, size_t output_stride) { + Transpose8x8Block(block0, block1); + IDCT1D<8>(block1, block0, 8); + Transpose8x8Block(block0, block1); + IDCT1D<8>(block1, output, output_stride); +} + +void InverseTransformBlock8x8(const int16_t* JXL_RESTRICT qblock, + const float* JXL_RESTRICT dequant, + const float* JXL_RESTRICT biases, + float* JXL_RESTRICT scratch_space, + float* JXL_RESTRICT output, size_t output_stride, + size_t dctsize) { + float* JXL_RESTRICT block0 = scratch_space; + float* JXL_RESTRICT block1 = scratch_space + DCTSIZE2; + DequantBlock(qblock, dequant, biases, block0); + ComputeScaledIDCT(block0, block1, output, output_stride); +} + +// Computes the N-point IDCT of in[], and stores the result in out[]. The in[] +// array is at most 8 values long, values in[8:N-1] are assumed to be 0. +void Compute1dIDCT(const float* in, float* out, size_t N) { + switch (N) { + case 3: { + static constexpr float kC3[3] = { + 1.414213562373, + 1.224744871392, + 0.707106781187, + }; + float even0 = in[0] + kC3[2] * in[2]; + float even1 = in[0] - kC3[0] * in[2]; + float odd0 = kC3[1] * in[1]; + out[0] = even0 + odd0; + out[2] = even0 - odd0; + out[1] = even1; + break; + } + case 5: { + static constexpr float kC5[5] = { + 1.414213562373, 1.344997023928, 1.144122805635, + 0.831253875555, 0.437016024449, + }; + float even0 = in[0] + kC5[2] * in[2] + kC5[4] * in[4]; + float even1 = in[0] - kC5[4] * in[2] - kC5[2] * in[4]; + float even2 = in[0] - kC5[0] * in[2] + kC5[0] * in[4]; + float odd0 = kC5[1] * in[1] + kC5[3] * in[3]; + float odd1 = kC5[3] * in[1] - kC5[1] * in[3]; + out[0] = even0 + odd0; + out[4] = even0 - odd0; + out[1] = even1 + odd1; + out[3] = even1 - odd1; + out[2] = even2; + break; + } + case 6: { + static constexpr float kC6[6] = { + 1.414213562373, 1.366025403784, 1.224744871392, + 1.000000000000, 0.707106781187, 0.366025403784, + }; + float even0 = in[0] + kC6[2] * in[2] + kC6[4] * in[4]; + float even1 = in[0] - kC6[0] * in[4]; + float even2 = in[0] - kC6[2] * in[2] + kC6[4] * in[4]; + float odd0 = kC6[1] * in[1] + kC6[3] * in[3] + kC6[5] * in[5]; + float odd1 = kC6[3] * in[1] - kC6[3] * in[3] - kC6[3] * in[5]; + float odd2 = kC6[5] * in[1] - kC6[3] * in[3] + kC6[1] * in[5]; + out[0] = even0 + odd0; + out[5] = even0 - odd0; + out[1] = even1 + odd1; + out[4] = even1 - odd1; + out[2] = even2 + odd2; + out[3] = even2 - odd2; + break; + } + case 7: { + static constexpr float kC7[7] = { + 1.414213562373, 1.378756275744, 1.274162392264, 1.105676685997, + 0.881747733790, 0.613604268353, 0.314692122713, + }; + float even0 = in[0] + kC7[2] * in[2] + kC7[4] * in[4] + kC7[6] * in[6]; + float even1 = in[0] + kC7[6] * in[2] - kC7[2] * in[4] - kC7[4] * in[6]; + float even2 = in[0] - kC7[4] * in[2] - kC7[6] * in[4] + kC7[2] * in[6]; + float even3 = in[0] - kC7[0] * in[2] + kC7[0] * in[4] - kC7[0] * in[6]; + float odd0 = kC7[1] * in[1] + kC7[3] * in[3] + kC7[5] * in[5]; + float odd1 = kC7[3] * in[1] - kC7[5] * in[3] - kC7[1] * in[5]; + float odd2 = kC7[5] * in[1] - kC7[1] * in[3] + kC7[3] * in[5]; + out[0] = even0 + odd0; + out[6] = even0 - odd0; + out[1] = even1 + odd1; + out[5] = even1 - odd1; + out[2] = even2 + odd2; + out[4] = even2 - odd2; + out[3] = even3; + break; + } + case 9: { + static constexpr float kC9[9] = { + 1.414213562373, 1.392728480640, 1.328926048777, + 1.224744871392, 1.083350440839, 0.909038955344, + 0.707106781187, 0.483689525296, 0.245575607938, + }; + float even0 = in[0] + kC9[2] * in[2] + kC9[4] * in[4] + kC9[6] * in[6]; + float even1 = in[0] + kC9[6] * in[2] - kC9[6] * in[4] - kC9[0] * in[6]; + float even2 = in[0] - kC9[8] * in[2] - kC9[2] * in[4] + kC9[6] * in[6]; + float even3 = in[0] - kC9[4] * in[2] + kC9[8] * in[4] + kC9[6] * in[6]; + float even4 = in[0] - kC9[0] * in[2] + kC9[0] * in[4] - kC9[0] * in[6]; + float odd0 = + kC9[1] * in[1] + kC9[3] * in[3] + kC9[5] * in[5] + kC9[7] * in[7]; + float odd1 = kC9[3] * in[1] - kC9[3] * in[5] - kC9[3] * in[7]; + float odd2 = + kC9[5] * in[1] - kC9[3] * in[3] - kC9[7] * in[5] + kC9[1] * in[7]; + float odd3 = + kC9[7] * in[1] - kC9[3] * in[3] + kC9[1] * in[5] - kC9[5] * in[7]; + out[0] = even0 + odd0; + out[8] = even0 - odd0; + out[1] = even1 + odd1; + out[7] = even1 - odd1; + out[2] = even2 + odd2; + out[6] = even2 - odd2; + out[3] = even3 + odd3; + out[5] = even3 - odd3; + out[4] = even4; + break; + } + case 10: { + static constexpr float kC10[10] = { + 1.414213562373, 1.396802246667, 1.344997023928, 1.260073510670, + 1.144122805635, 1.000000000000, 0.831253875555, 0.642039521920, + 0.437016024449, 0.221231742082, + }; + float even0 = in[0] + kC10[2] * in[2] + kC10[4] * in[4] + kC10[6] * in[6]; + float even1 = in[0] + kC10[6] * in[2] - kC10[8] * in[4] - kC10[2] * in[6]; + float even2 = in[0] - kC10[0] * in[4]; + float even3 = in[0] - kC10[6] * in[2] - kC10[8] * in[4] + kC10[2] * in[6]; + float even4 = in[0] - kC10[2] * in[2] + kC10[4] * in[4] - kC10[6] * in[6]; + float odd0 = + kC10[1] * in[1] + kC10[3] * in[3] + kC10[5] * in[5] + kC10[7] * in[7]; + float odd1 = + kC10[3] * in[1] + kC10[9] * in[3] - kC10[5] * in[5] - kC10[1] * in[7]; + float odd2 = + kC10[5] * in[1] - kC10[5] * in[3] - kC10[5] * in[5] + kC10[5] * in[7]; + float odd3 = + kC10[7] * in[1] - kC10[1] * in[3] + kC10[5] * in[5] + kC10[9] * in[7]; + float odd4 = + kC10[9] * in[1] - kC10[7] * in[3] + kC10[5] * in[5] - kC10[3] * in[7]; + out[0] = even0 + odd0; + out[9] = even0 - odd0; + out[1] = even1 + odd1; + out[8] = even1 - odd1; + out[2] = even2 + odd2; + out[7] = even2 - odd2; + out[3] = even3 + odd3; + out[6] = even3 - odd3; + out[4] = even4 + odd4; + out[5] = even4 - odd4; + break; + } + case 11: { + static constexpr float kC11[11] = { + 1.414213562373, 1.399818907436, 1.356927976287, 1.286413904599, + 1.189712155524, 1.068791297809, 0.926112931411, 0.764581576418, + 0.587485545401, 0.398430002847, 0.201263574413, + }; + float even0 = in[0] + kC11[2] * in[2] + kC11[4] * in[4] + kC11[6] * in[6]; + float even1 = + in[0] + kC11[6] * in[2] - kC11[10] * in[4] - kC11[4] * in[6]; + float even2 = + in[0] + kC11[10] * in[2] - kC11[2] * in[4] - kC11[8] * in[6]; + float even3 = in[0] - kC11[8] * in[2] - kC11[6] * in[4] + kC11[2] * in[6]; + float even4 = + in[0] - kC11[4] * in[2] + kC11[8] * in[4] + kC11[10] * in[6]; + float even5 = in[0] - kC11[0] * in[2] + kC11[0] * in[4] - kC11[0] * in[6]; + float odd0 = + kC11[1] * in[1] + kC11[3] * in[3] + kC11[5] * in[5] + kC11[7] * in[7]; + float odd1 = + kC11[3] * in[1] + kC11[9] * in[3] - kC11[7] * in[5] - kC11[1] * in[7]; + float odd2 = + kC11[5] * in[1] - kC11[7] * in[3] - kC11[3] * in[5] + kC11[9] * in[7]; + float odd3 = + kC11[7] * in[1] - kC11[1] * in[3] + kC11[9] * in[5] + kC11[5] * in[7]; + float odd4 = + kC11[9] * in[1] - kC11[5] * in[3] + kC11[1] * in[5] - kC11[3] * in[7]; + out[0] = even0 + odd0; + out[10] = even0 - odd0; + out[1] = even1 + odd1; + out[9] = even1 - odd1; + out[2] = even2 + odd2; + out[8] = even2 - odd2; + out[3] = even3 + odd3; + out[7] = even3 - odd3; + out[4] = even4 + odd4; + out[6] = even4 - odd4; + out[5] = even5; + break; + } + case 12: { + static constexpr float kC12[12] = { + 1.414213562373, 1.402114769300, 1.366025403784, 1.306562964876, + 1.224744871392, 1.121971053594, 1.000000000000, 0.860918669154, + 0.707106781187, 0.541196100146, 0.366025403784, 0.184591911283, + }; + float even0 = in[0] + kC12[2] * in[2] + kC12[4] * in[4] + kC12[6] * in[6]; + float even1 = in[0] + kC12[6] * in[2] - kC12[6] * in[6]; + float even2 = + in[0] + kC12[10] * in[2] - kC12[4] * in[4] - kC12[6] * in[6]; + float even3 = + in[0] - kC12[10] * in[2] - kC12[4] * in[4] + kC12[6] * in[6]; + float even4 = in[0] - kC12[6] * in[2] + kC12[6] * in[6]; + float even5 = in[0] - kC12[2] * in[2] + kC12[4] * in[4] - kC12[6] * in[6]; + float odd0 = + kC12[1] * in[1] + kC12[3] * in[3] + kC12[5] * in[5] + kC12[7] * in[7]; + float odd1 = + kC12[3] * in[1] + kC12[9] * in[3] - kC12[9] * in[5] - kC12[3] * in[7]; + float odd2 = kC12[5] * in[1] - kC12[9] * in[3] - kC12[1] * in[5] - + kC12[11] * in[7]; + float odd3 = kC12[7] * in[1] - kC12[3] * in[3] - kC12[11] * in[5] + + kC12[1] * in[7]; + float odd4 = + kC12[9] * in[1] - kC12[3] * in[3] + kC12[3] * in[5] - kC12[9] * in[7]; + float odd5 = kC12[11] * in[1] - kC12[9] * in[3] + kC12[7] * in[5] - + kC12[5] * in[7]; + out[0] = even0 + odd0; + out[11] = even0 - odd0; + out[1] = even1 + odd1; + out[10] = even1 - odd1; + out[2] = even2 + odd2; + out[9] = even2 - odd2; + out[3] = even3 + odd3; + out[8] = even3 - odd3; + out[4] = even4 + odd4; + out[7] = even4 - odd4; + out[5] = even5 + odd5; + out[6] = even5 - odd5; + break; + } + case 13: { + static constexpr float kC13[13] = { + 1.414213562373, 1.403902353238, 1.373119086479, 1.322312651445, + 1.252223920364, 1.163874944761, 1.058554051646, 0.937797056801, + 0.803364869133, 0.657217812653, 0.501487040539, 0.338443458124, + 0.170464607981, + }; + float even0 = in[0] + kC13[2] * in[2] + kC13[4] * in[4] + kC13[6] * in[6]; + float even1 = + in[0] + kC13[6] * in[2] + kC13[12] * in[4] - kC13[8] * in[6]; + float even2 = + in[0] + kC13[10] * in[2] - kC13[6] * in[4] - kC13[4] * in[6]; + float even3 = + in[0] - kC13[12] * in[2] - kC13[2] * in[4] + kC13[10] * in[6]; + float even4 = + in[0] - kC13[8] * in[2] - kC13[10] * in[4] + kC13[2] * in[6]; + float even5 = + in[0] - kC13[4] * in[2] + kC13[8] * in[4] - kC13[12] * in[6]; + float even6 = in[0] - kC13[0] * in[2] + kC13[0] * in[4] - kC13[0] * in[6]; + float odd0 = + kC13[1] * in[1] + kC13[3] * in[3] + kC13[5] * in[5] + kC13[7] * in[7]; + float odd1 = kC13[3] * in[1] + kC13[9] * in[3] - kC13[11] * in[5] - + kC13[5] * in[7]; + float odd2 = kC13[5] * in[1] - kC13[11] * in[3] - kC13[1] * in[5] - + kC13[9] * in[7]; + float odd3 = + kC13[7] * in[1] - kC13[5] * in[3] - kC13[9] * in[5] + kC13[3] * in[7]; + float odd4 = kC13[9] * in[1] - kC13[1] * in[3] + kC13[7] * in[5] + + kC13[11] * in[7]; + float odd5 = kC13[11] * in[1] - kC13[7] * in[3] + kC13[3] * in[5] - + kC13[1] * in[7]; + out[0] = even0 + odd0; + out[12] = even0 - odd0; + out[1] = even1 + odd1; + out[11] = even1 - odd1; + out[2] = even2 + odd2; + out[10] = even2 - odd2; + out[3] = even3 + odd3; + out[9] = even3 - odd3; + out[4] = even4 + odd4; + out[8] = even4 - odd4; + out[5] = even5 + odd5; + out[7] = even5 - odd5; + out[6] = even6; + break; + } + case 14: { + static constexpr float kC14[14] = { + 1.414213562373, 1.405321284327, 1.378756275744, 1.334852607020, + 1.274162392264, 1.197448846138, 1.105676685997, 1.000000000000, + 0.881747733790, 0.752406978226, 0.613604268353, 0.467085128785, + 0.314692122713, 0.158341680609, + }; + float even0 = in[0] + kC14[2] * in[2] + kC14[4] * in[4] + kC14[6] * in[6]; + float even1 = + in[0] + kC14[6] * in[2] + kC14[12] * in[4] - kC14[10] * in[6]; + float even2 = + in[0] + kC14[10] * in[2] - kC14[8] * in[4] - kC14[2] * in[6]; + float even3 = in[0] - kC14[0] * in[4]; + float even4 = + in[0] - kC14[10] * in[2] - kC14[8] * in[4] + kC14[2] * in[6]; + float even5 = + in[0] - kC14[6] * in[2] + kC14[12] * in[4] + kC14[10] * in[6]; + float even6 = in[0] - kC14[2] * in[2] + kC14[4] * in[4] - kC14[6] * in[6]; + float odd0 = + kC14[1] * in[1] + kC14[3] * in[3] + kC14[5] * in[5] + kC14[7] * in[7]; + float odd1 = kC14[3] * in[1] + kC14[9] * in[3] - kC14[13] * in[5] - + kC14[7] * in[7]; + float odd2 = kC14[5] * in[1] - kC14[13] * in[3] - kC14[3] * in[5] - + kC14[7] * in[7]; + float odd3 = + kC14[7] * in[1] - kC14[7] * in[3] - kC14[7] * in[5] + kC14[7] * in[7]; + float odd4 = kC14[9] * in[1] - kC14[1] * in[3] + kC14[11] * in[5] + + kC14[7] * in[7]; + float odd5 = kC14[11] * in[1] - kC14[5] * in[3] + kC14[1] * in[5] - + kC14[7] * in[7]; + float odd6 = kC14[13] * in[1] - kC14[11] * in[3] + kC14[9] * in[5] - + kC14[7] * in[7]; + out[0] = even0 + odd0; + out[13] = even0 - odd0; + out[1] = even1 + odd1; + out[12] = even1 - odd1; + out[2] = even2 + odd2; + out[11] = even2 - odd2; + out[3] = even3 + odd3; + out[10] = even3 - odd3; + out[4] = even4 + odd4; + out[9] = even4 - odd4; + out[5] = even5 + odd5; + out[8] = even5 - odd5; + out[6] = even6 + odd6; + out[7] = even6 - odd6; + break; + } + case 15: { + static constexpr float kC15[15] = { + 1.414213562373, 1.406466352507, 1.383309602960, 1.344997023928, + 1.291948376043, 1.224744871392, 1.144122805635, 1.050965490998, + 0.946293578512, 0.831253875555, 0.707106781187, 0.575212476952, + 0.437016024449, 0.294031532930, 0.147825570407, + }; + float even0 = in[0] + kC15[2] * in[2] + kC15[4] * in[4] + kC15[6] * in[6]; + float even1 = + in[0] + kC15[6] * in[2] + kC15[12] * in[4] - kC15[12] * in[6]; + float even2 = + in[0] + kC15[10] * in[2] - kC15[10] * in[4] - kC15[0] * in[6]; + float even3 = + in[0] + kC15[14] * in[2] - kC15[2] * in[4] - kC15[12] * in[6]; + float even4 = + in[0] - kC15[12] * in[2] - kC15[6] * in[4] + kC15[6] * in[6]; + float even5 = + in[0] - kC15[8] * in[2] - kC15[14] * in[4] + kC15[6] * in[6]; + float even6 = + in[0] - kC15[4] * in[2] + kC15[8] * in[4] - kC15[12] * in[6]; + float even7 = in[0] - kC15[0] * in[2] + kC15[0] * in[4] - kC15[0] * in[6]; + float odd0 = + kC15[1] * in[1] + kC15[3] * in[3] + kC15[5] * in[5] + kC15[7] * in[7]; + float odd1 = kC15[3] * in[1] + kC15[9] * in[3] - kC15[9] * in[7]; + float odd2 = kC15[5] * in[1] - kC15[5] * in[5] - kC15[5] * in[7]; + float odd3 = kC15[7] * in[1] - kC15[9] * in[3] - kC15[5] * in[5] + + kC15[11] * in[7]; + float odd4 = kC15[9] * in[1] - kC15[3] * in[3] + kC15[3] * in[7]; + float odd5 = kC15[11] * in[1] - kC15[3] * in[3] + kC15[5] * in[5] - + kC15[13] * in[7]; + float odd6 = kC15[13] * in[1] - kC15[9] * in[3] + kC15[5] * in[5] - + kC15[1] * in[7]; + out[0] = even0 + odd0; + out[14] = even0 - odd0; + out[1] = even1 + odd1; + out[13] = even1 - odd1; + out[2] = even2 + odd2; + out[12] = even2 - odd2; + out[3] = even3 + odd3; + out[11] = even3 - odd3; + out[4] = even4 + odd4; + out[10] = even4 - odd4; + out[5] = even5 + odd5; + out[9] = even5 - odd5; + out[6] = even6 + odd6; + out[8] = even6 - odd6; + out[7] = even7; + break; + } + case 16: { + static constexpr float kC16[16] = { + 1.414213562373, 1.407403737526, 1.387039845322, 1.353318001174, + 1.306562964876, 1.247225012987, 1.175875602419, 1.093201867002, + 1.000000000000, 0.897167586343, 0.785694958387, 0.666655658478, + 0.541196100146, 0.410524527522, 0.275899379283, 0.138617169199, + }; + float even0 = in[0] + kC16[2] * in[2] + kC16[4] * in[4] + kC16[6] * in[6]; + float even1 = + in[0] + kC16[6] * in[2] + kC16[12] * in[4] - kC16[14] * in[6]; + float even2 = + in[0] + kC16[10] * in[2] - kC16[12] * in[4] - kC16[2] * in[6]; + float even3 = + in[0] + kC16[14] * in[2] - kC16[4] * in[4] - kC16[10] * in[6]; + float even4 = + in[0] - kC16[14] * in[2] - kC16[4] * in[4] + kC16[10] * in[6]; + float even5 = + in[0] - kC16[10] * in[2] - kC16[12] * in[4] + kC16[2] * in[6]; + float even6 = + in[0] - kC16[6] * in[2] + kC16[12] * in[4] + kC16[14] * in[6]; + float even7 = in[0] - kC16[2] * in[2] + kC16[4] * in[4] - kC16[6] * in[6]; + float odd0 = (kC16[1] * in[1] + kC16[3] * in[3] + kC16[5] * in[5] + + kC16[7] * in[7]); + float odd1 = (kC16[3] * in[1] + kC16[9] * in[3] + kC16[15] * in[5] - + kC16[11] * in[7]); + float odd2 = (kC16[5] * in[1] + kC16[15] * in[3] - kC16[7] * in[5] - + kC16[3] * in[7]); + float odd3 = (kC16[7] * in[1] - kC16[11] * in[3] - kC16[3] * in[5] + + kC16[15] * in[7]); + float odd4 = (kC16[9] * in[1] - kC16[5] * in[3] - kC16[13] * in[5] + + kC16[1] * in[7]); + float odd5 = (kC16[11] * in[1] - kC16[1] * in[3] + kC16[9] * in[5] + + kC16[13] * in[7]); + float odd6 = (kC16[13] * in[1] - kC16[7] * in[3] + kC16[1] * in[5] - + kC16[5] * in[7]); + float odd7 = (kC16[15] * in[1] - kC16[13] * in[3] + kC16[11] * in[5] - + kC16[9] * in[7]); + out[0] = even0 + odd0; + out[15] = even0 - odd0; + out[1] = even1 + odd1; + out[14] = even1 - odd1; + out[2] = even2 + odd2; + out[13] = even2 - odd2; + out[3] = even3 + odd3; + out[12] = even3 - odd3; + out[4] = even4 + odd4; + out[11] = even4 - odd4; + out[5] = even5 + odd5; + out[10] = even5 - odd5; + out[6] = even6 + odd6; + out[9] = even6 - odd6; + out[7] = even7 + odd7; + out[8] = even7 - odd7; + break; + } + default: + JXL_DEBUG_ABORT("Unreachable"); + break; + } +} + +void InverseTransformBlockGeneric(const int16_t* JXL_RESTRICT qblock, + const float* JXL_RESTRICT dequant, + const float* JXL_RESTRICT biases, + float* JXL_RESTRICT scratch_space, + float* JXL_RESTRICT output, + size_t output_stride, size_t dctsize) { + float* JXL_RESTRICT block0 = scratch_space; + float* JXL_RESTRICT block1 = scratch_space + DCTSIZE2; + DequantBlock(qblock, dequant, biases, block0); + if (dctsize == 1) { + *output = *block0; + } else if (dctsize == 2 || dctsize == 4) { + float* JXL_RESTRICT block2 = scratch_space + 2 * DCTSIZE2; + ComputeScaledIDCT(block0, block1, block2, 8); + if (dctsize == 4) { + for (size_t iy = 0; iy < 4; ++iy) { + for (size_t ix = 0; ix < 4; ++ix) { + float* block = &block2[16 * iy + 2 * ix]; + output[iy * output_stride + ix] = + 0.25f * (block[0] + block[1] + block[8] + block[9]); + } + } + } else { + for (size_t iy = 0; iy < 2; ++iy) { + for (size_t ix = 0; ix < 2; ++ix) { + float* block = &block2[32 * iy + 4 * ix]; + output[iy * output_stride + ix] = + 0.0625f * + (block[0] + block[1] + block[2] + block[3] + block[8] + block[9] + + block[10] + block[11] + block[16] + block[17] + block[18] + + block[19] + block[24] + block[25] + block[26] + block[27]); + } + } + } + } else { + float dctin[DCTSIZE]; + float dctout[DCTSIZE * 2]; + size_t insize = std::min(dctsize, DCTSIZE); + for (size_t ix = 0; ix < insize; ++ix) { + for (size_t iy = 0; iy < insize; ++iy) { + dctin[iy] = block0[iy * DCTSIZE + ix]; + } + Compute1dIDCT(dctin, dctout, dctsize); + for (size_t iy = 0; iy < dctsize; ++iy) { + block1[iy * dctsize + ix] = dctout[iy]; + } + } + for (size_t iy = 0; iy < dctsize; ++iy) { + Compute1dIDCT(block1 + iy * dctsize, output + iy * output_stride, + dctsize); + } + } +} + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace jpegli { + +HWY_EXPORT(InverseTransformBlock8x8); +HWY_EXPORT(InverseTransformBlockGeneric); + +jxl::Status ChooseInverseTransform(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + for (int c = 0; c < cinfo->num_components; ++c) { + int dct_size = m->scaled_dct_size[c]; + if (dct_size < 1 || dct_size > 16) { + return JXL_FAILURE("Compute1dIDCT does not support N=%d", dct_size); + } + if (dct_size == DCTSIZE) { + m->inverse_transform[c] = HWY_DYNAMIC_DISPATCH(InverseTransformBlock8x8); + } else { + m->inverse_transform[c] = + HWY_DYNAMIC_DISPATCH(InverseTransformBlockGeneric); + } + } + return true; +} + +} // namespace jpegli +#endif // HWY_ONCE diff --git a/media/libjxl/src/lib/jpegli/idct.h b/media/libjxl/src/lib/jpegli/idct.h new file mode 100644 index 0000000000..746b1562c1 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/idct.h @@ -0,0 +1,18 @@ +// 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_JPEGLI_IDCT_H_ +#define LIB_JPEGLI_IDCT_H_ + +#include "lib/jpegli/common.h" +#include "lib/jxl/base/status.h" + +namespace jpegli { + +jxl::Status ChooseInverseTransform(j_decompress_ptr cinfo); + +} // namespace jpegli + +#endif // LIB_JPEGLI_IDCT_H_ diff --git a/media/libjxl/src/lib/jpegli/input.cc b/media/libjxl/src/lib/jpegli/input.cc new file mode 100644 index 0000000000..16299477f7 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/input.cc @@ -0,0 +1,414 @@ +// 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/jpegli/input.h" + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jpegli/input.cc" +#include +#include + +#include "lib/jpegli/encode_internal.h" +#include "lib/jpegli/error.h" +#include "lib/jxl/base/byte_order.h" +#include "lib/jxl/base/compiler_specific.h" + +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { + +using hwy::HWY_NAMESPACE::Mul; +using hwy::HWY_NAMESPACE::Rebind; +using hwy::HWY_NAMESPACE::Vec; + +using D = HWY_FULL(float); +using DU = HWY_FULL(uint32_t); +using DU8 = Rebind; +using DU16 = Rebind; + +constexpr D d; +constexpr DU du; +constexpr DU8 du8; +constexpr DU16 du16; + +static constexpr double kMul16 = 1.0 / 257.0; +static constexpr double kMulFloat = 255.0; + +template +void ReadUint8Row(const uint8_t* row_in, size_t x0, size_t len, + float* row_out[kMaxComponents]) { + for (size_t x = x0; x < len; ++x) { + for (size_t c = 0; c < C; ++c) { + row_out[c][x] = row_in[C * x + c]; + } + } +} + +template +void ReadUint16Row(const uint8_t* row_in, size_t x0, size_t len, + float* row_out[kMaxComponents]) { + const uint16_t* row16 = reinterpret_cast(row_in); + for (size_t x = x0; x < len; ++x) { + for (size_t c = 0; c < C; ++c) { + uint16_t val = row16[C * x + c]; + if (swap_endianness) val = JXL_BSWAP16(val); + row_out[c][x] = val * kMul16; + } + } +} + +template +void ReadFloatRow(const uint8_t* row_in, size_t x0, size_t len, + float* row_out[kMaxComponents]) { + const float* rowf = reinterpret_cast(row_in); + for (size_t x = x0; x < len; ++x) { + for (size_t c = 0; c < C; ++c) { + float val = rowf[C * x + c]; + if (swap_endianness) val = BSwapFloat(val); + row_out[c][x] = val * kMulFloat; + } + } +} + +void ReadUint8RowSingle(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + const size_t N = Lanes(d); + const size_t simd_len = len & (~(N - 1)); + float* JXL_RESTRICT const row0 = row_out[0]; + for (size_t x = 0; x < simd_len; x += N) { + Store(ConvertTo(d, PromoteTo(du, LoadU(du8, row_in + x))), d, row0 + x); + } + ReadUint8Row<1>(row_in, simd_len, len, row_out); +} + +void ReadUint8RowInterleaved2(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + const size_t N = Lanes(d); + const size_t simd_len = len & (~(N - 1)); + float* JXL_RESTRICT const row0 = row_out[0]; + float* JXL_RESTRICT const row1 = row_out[1]; + Vec out0, out1; // NOLINT + for (size_t x = 0; x < simd_len; x += N) { + LoadInterleaved2(du8, row_in + 2 * x, out0, out1); + Store(ConvertTo(d, PromoteTo(du, out0)), d, row0 + x); + Store(ConvertTo(d, PromoteTo(du, out1)), d, row1 + x); + } + ReadUint8Row<2>(row_in, simd_len, len, row_out); +} + +void ReadUint8RowInterleaved3(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + const size_t N = Lanes(d); + const size_t simd_len = len & (~(N - 1)); + float* JXL_RESTRICT const row0 = row_out[0]; + float* JXL_RESTRICT const row1 = row_out[1]; + float* JXL_RESTRICT const row2 = row_out[2]; + Vec out0, out1, out2; // NOLINT + for (size_t x = 0; x < simd_len; x += N) { + LoadInterleaved3(du8, row_in + 3 * x, out0, out1, out2); + Store(ConvertTo(d, PromoteTo(du, out0)), d, row0 + x); + Store(ConvertTo(d, PromoteTo(du, out1)), d, row1 + x); + Store(ConvertTo(d, PromoteTo(du, out2)), d, row2 + x); + } + ReadUint8Row<3>(row_in, simd_len, len, row_out); +} + +void ReadUint8RowInterleaved4(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + const size_t N = Lanes(d); + const size_t simd_len = len & (~(N - 1)); + float* JXL_RESTRICT const row0 = row_out[0]; + float* JXL_RESTRICT const row1 = row_out[1]; + float* JXL_RESTRICT const row2 = row_out[2]; + float* JXL_RESTRICT const row3 = row_out[3]; + Vec out0, out1, out2, out3; // NOLINT + for (size_t x = 0; x < simd_len; x += N) { + LoadInterleaved4(du8, row_in + 4 * x, out0, out1, out2, out3); + Store(ConvertTo(d, PromoteTo(du, out0)), d, row0 + x); + Store(ConvertTo(d, PromoteTo(du, out1)), d, row1 + x); + Store(ConvertTo(d, PromoteTo(du, out2)), d, row2 + x); + Store(ConvertTo(d, PromoteTo(du, out3)), d, row3 + x); + } + ReadUint8Row<4>(row_in, simd_len, len, row_out); +} + +void ReadUint16RowSingle(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + const size_t N = Lanes(d); + const size_t simd_len = len & (~(N - 1)); + const auto mul = Set(d, kMul16); + const uint16_t* JXL_RESTRICT const row = + reinterpret_cast(row_in); + float* JXL_RESTRICT const row0 = row_out[0]; + for (size_t x = 0; x < simd_len; x += N) { + Store(Mul(mul, ConvertTo(d, PromoteTo(du, LoadU(du16, row + x)))), d, + row0 + x); + } + ReadUint16Row<1>(row_in, simd_len, len, row_out); +} + +void ReadUint16RowInterleaved2(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + const size_t N = Lanes(d); + const size_t simd_len = len & (~(N - 1)); + const auto mul = Set(d, kMul16); + const uint16_t* JXL_RESTRICT const row = + reinterpret_cast(row_in); + float* JXL_RESTRICT const row0 = row_out[0]; + float* JXL_RESTRICT const row1 = row_out[1]; + Vec out0, out1; // NOLINT + for (size_t x = 0; x < simd_len; x += N) { + LoadInterleaved2(du16, row + 2 * x, out0, out1); + Store(Mul(mul, ConvertTo(d, PromoteTo(du, out0))), d, row0 + x); + Store(Mul(mul, ConvertTo(d, PromoteTo(du, out1))), d, row1 + x); + } + ReadUint16Row<2>(row_in, simd_len, len, row_out); +} + +void ReadUint16RowInterleaved3(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + const size_t N = Lanes(d); + const size_t simd_len = len & (~(N - 1)); + const auto mul = Set(d, kMul16); + const uint16_t* JXL_RESTRICT const row = + reinterpret_cast(row_in); + float* JXL_RESTRICT const row0 = row_out[0]; + float* JXL_RESTRICT const row1 = row_out[1]; + float* JXL_RESTRICT const row2 = row_out[2]; + Vec out0, out1, out2; // NOLINT + for (size_t x = 0; x < simd_len; x += N) { + LoadInterleaved3(du16, row + 3 * x, out0, out1, out2); + Store(Mul(mul, ConvertTo(d, PromoteTo(du, out0))), d, row0 + x); + Store(Mul(mul, ConvertTo(d, PromoteTo(du, out1))), d, row1 + x); + Store(Mul(mul, ConvertTo(d, PromoteTo(du, out2))), d, row2 + x); + } + ReadUint16Row<3>(row_in, simd_len, len, row_out); +} + +void ReadUint16RowInterleaved4(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + const size_t N = Lanes(d); + const size_t simd_len = len & (~(N - 1)); + const auto mul = Set(d, kMul16); + const uint16_t* JXL_RESTRICT const row = + reinterpret_cast(row_in); + float* JXL_RESTRICT const row0 = row_out[0]; + float* JXL_RESTRICT const row1 = row_out[1]; + float* JXL_RESTRICT const row2 = row_out[2]; + float* JXL_RESTRICT const row3 = row_out[3]; + Vec out0, out1, out2, out3; // NOLINT + for (size_t x = 0; x < simd_len; x += N) { + LoadInterleaved4(du16, row + 4 * x, out0, out1, out2, out3); + Store(Mul(mul, ConvertTo(d, PromoteTo(du, out0))), d, row0 + x); + Store(Mul(mul, ConvertTo(d, PromoteTo(du, out1))), d, row1 + x); + Store(Mul(mul, ConvertTo(d, PromoteTo(du, out2))), d, row2 + x); + Store(Mul(mul, ConvertTo(d, PromoteTo(du, out3))), d, row3 + x); + } + ReadUint16Row<4>(row_in, simd_len, len, row_out); +} + +void ReadUint16RowSingleSwap(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + ReadUint16Row<1, true>(row_in, 0, len, row_out); +} + +void ReadUint16RowInterleaved2Swap(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + ReadUint16Row<2, true>(row_in, 0, len, row_out); +} + +void ReadUint16RowInterleaved3Swap(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + ReadUint16Row<3, true>(row_in, 0, len, row_out); +} + +void ReadUint16RowInterleaved4Swap(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + ReadUint16Row<4, true>(row_in, 0, len, row_out); +} + +void ReadFloatRowSingle(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + const size_t N = Lanes(d); + const size_t simd_len = len & (~(N - 1)); + const auto mul = Set(d, kMulFloat); + const float* JXL_RESTRICT const row = reinterpret_cast(row_in); + float* JXL_RESTRICT const row0 = row_out[0]; + for (size_t x = 0; x < simd_len; x += N) { + Store(Mul(mul, LoadU(d, row + x)), d, row0 + x); + } + ReadFloatRow<1>(row_in, simd_len, len, row_out); +} + +void ReadFloatRowInterleaved2(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + const size_t N = Lanes(d); + const size_t simd_len = len & (~(N - 1)); + const auto mul = Set(d, kMulFloat); + const float* JXL_RESTRICT const row = reinterpret_cast(row_in); + float* JXL_RESTRICT const row0 = row_out[0]; + float* JXL_RESTRICT const row1 = row_out[1]; + Vec out0, out1; // NOLINT + for (size_t x = 0; x < simd_len; x += N) { + LoadInterleaved2(d, row + 2 * x, out0, out1); + Store(Mul(mul, out0), d, row0 + x); + Store(Mul(mul, out1), d, row1 + x); + } + ReadFloatRow<2>(row_in, simd_len, len, row_out); +} + +void ReadFloatRowInterleaved3(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + const size_t N = Lanes(d); + const size_t simd_len = len & (~(N - 1)); + const auto mul = Set(d, kMulFloat); + const float* JXL_RESTRICT const row = reinterpret_cast(row_in); + float* JXL_RESTRICT const row0 = row_out[0]; + float* JXL_RESTRICT const row1 = row_out[1]; + float* JXL_RESTRICT const row2 = row_out[2]; + Vec out0, out1, out2; // NOLINT + for (size_t x = 0; x < simd_len; x += N) { + LoadInterleaved3(d, row + 3 * x, out0, out1, out2); + Store(Mul(mul, out0), d, row0 + x); + Store(Mul(mul, out1), d, row1 + x); + Store(Mul(mul, out2), d, row2 + x); + } + ReadFloatRow<3>(row_in, simd_len, len, row_out); +} + +void ReadFloatRowInterleaved4(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + const size_t N = Lanes(d); + const size_t simd_len = len & (~(N - 1)); + const auto mul = Set(d, kMulFloat); + const float* JXL_RESTRICT const row = reinterpret_cast(row_in); + float* JXL_RESTRICT const row0 = row_out[0]; + float* JXL_RESTRICT const row1 = row_out[1]; + float* JXL_RESTRICT const row2 = row_out[2]; + float* JXL_RESTRICT const row3 = row_out[3]; + Vec out0, out1, out2, out3; // NOLINT + for (size_t x = 0; x < simd_len; x += N) { + LoadInterleaved4(d, row + 4 * x, out0, out1, out2, out3); + Store(Mul(mul, out0), d, row0 + x); + Store(Mul(mul, out1), d, row1 + x); + Store(Mul(mul, out2), d, row2 + x); + Store(Mul(mul, out3), d, row3 + x); + } + ReadFloatRow<4>(row_in, simd_len, len, row_out); +} + +void ReadFloatRowSingleSwap(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + ReadFloatRow<1, true>(row_in, 0, len, row_out); +} + +void ReadFloatRowInterleaved2Swap(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + ReadFloatRow<2, true>(row_in, 0, len, row_out); +} + +void ReadFloatRowInterleaved3Swap(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + ReadFloatRow<3, true>(row_in, 0, len, row_out); +} + +void ReadFloatRowInterleaved4Swap(const uint8_t* row_in, size_t len, + float* row_out[kMaxComponents]) { + ReadFloatRow<4, true>(row_in, 0, len, row_out); +} + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace jpegli { + +HWY_EXPORT(ReadUint8RowSingle); +HWY_EXPORT(ReadUint8RowInterleaved2); +HWY_EXPORT(ReadUint8RowInterleaved3); +HWY_EXPORT(ReadUint8RowInterleaved4); +HWY_EXPORT(ReadUint16RowSingle); +HWY_EXPORT(ReadUint16RowInterleaved2); +HWY_EXPORT(ReadUint16RowInterleaved3); +HWY_EXPORT(ReadUint16RowInterleaved4); +HWY_EXPORT(ReadUint16RowSingleSwap); +HWY_EXPORT(ReadUint16RowInterleaved2Swap); +HWY_EXPORT(ReadUint16RowInterleaved3Swap); +HWY_EXPORT(ReadUint16RowInterleaved4Swap); +HWY_EXPORT(ReadFloatRowSingle); +HWY_EXPORT(ReadFloatRowInterleaved2); +HWY_EXPORT(ReadFloatRowInterleaved3); +HWY_EXPORT(ReadFloatRowInterleaved4); +HWY_EXPORT(ReadFloatRowSingleSwap); +HWY_EXPORT(ReadFloatRowInterleaved2Swap); +HWY_EXPORT(ReadFloatRowInterleaved3Swap); +HWY_EXPORT(ReadFloatRowInterleaved4Swap); + +void ChooseInputMethod(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + bool swap_endianness = + (m->endianness == JPEGLI_LITTLE_ENDIAN && !IsLittleEndian()) || + (m->endianness == JPEGLI_BIG_ENDIAN && IsLittleEndian()); + m->input_method = nullptr; + if (m->data_type == JPEGLI_TYPE_UINT8) { + if (cinfo->raw_data_in || cinfo->input_components == 1) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint8RowSingle); + } else if (cinfo->input_components == 2) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint8RowInterleaved2); + } else if (cinfo->input_components == 3) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint8RowInterleaved3); + } else if (cinfo->input_components == 4) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint8RowInterleaved4); + } + } else if (m->data_type == JPEGLI_TYPE_UINT16 && !swap_endianness) { + if (cinfo->raw_data_in || cinfo->input_components == 1) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowSingle); + } else if (cinfo->input_components == 2) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowInterleaved2); + } else if (cinfo->input_components == 3) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowInterleaved3); + } else if (cinfo->input_components == 4) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowInterleaved4); + } + } else if (m->data_type == JPEGLI_TYPE_UINT16 && swap_endianness) { + if (cinfo->raw_data_in || cinfo->input_components == 1) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowSingleSwap); + } else if (cinfo->input_components == 2) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowInterleaved2Swap); + } else if (cinfo->input_components == 3) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowInterleaved3Swap); + } else if (cinfo->input_components == 4) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowInterleaved4Swap); + } + } else if (m->data_type == JPEGLI_TYPE_FLOAT && !swap_endianness) { + if (cinfo->raw_data_in || cinfo->input_components == 1) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowSingle); + } else if (cinfo->input_components == 2) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowInterleaved2); + } else if (cinfo->input_components == 3) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowInterleaved3); + } else if (cinfo->input_components == 4) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowInterleaved4); + } + } else if (m->data_type == JPEGLI_TYPE_FLOAT && swap_endianness) { + if (cinfo->raw_data_in || cinfo->input_components == 1) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowSingleSwap); + } else if (cinfo->input_components == 2) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowInterleaved2Swap); + } else if (cinfo->input_components == 3) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowInterleaved3Swap); + } else if (cinfo->input_components == 4) { + m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowInterleaved4Swap); + } + } + if (m->input_method == nullptr) { + JPEGLI_ERROR("Could not find input method."); + } +} + +} // namespace jpegli +#endif // HWY_ONCE diff --git a/media/libjxl/src/lib/jpegli/input.h b/media/libjxl/src/lib/jpegli/input.h new file mode 100644 index 0000000000..f54d0bee43 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/input.h @@ -0,0 +1,17 @@ +// 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_JPEGLI_INPUT_H_ +#define LIB_JPEGLI_INPUT_H_ + +#include "lib/jpegli/common.h" + +namespace jpegli { + +void ChooseInputMethod(j_compress_ptr cinfo); + +} // namespace jpegli + +#endif // LIB_JPEGLI_INPUT_H_ diff --git a/media/libjxl/src/lib/jpegli/input_suspension_test.cc b/media/libjxl/src/lib/jpegli/input_suspension_test.cc new file mode 100644 index 0000000000..6fa1416213 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/input_suspension_test.cc @@ -0,0 +1,631 @@ +// 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 "lib/jpegli/decode.h" +#include "lib/jpegli/libjpeg_test_util.h" +#include "lib/jpegli/test_params.h" +#include "lib/jpegli/test_utils.h" +#include "lib/jpegli/testing.h" +#include "lib/jxl/base/status.h" + +namespace jpegli { +namespace { + +constexpr uint8_t kFakeEoiMarker[2] = {0xff, 0xd9}; + +struct SourceManager { + SourceManager(const uint8_t* data, size_t len, size_t max_chunk_size, + bool is_partial_file) + : data_(data), + len_(len), + pos_(0), + max_chunk_size_(max_chunk_size), + is_partial_file_(is_partial_file) { + pub_.init_source = init_source; + pub_.fill_input_buffer = fill_input_buffer; + pub_.next_input_byte = nullptr; + pub_.bytes_in_buffer = 0; + pub_.skip_input_data = skip_input_data; + pub_.resync_to_restart = jpegli_resync_to_restart; + pub_.term_source = term_source; + if (max_chunk_size_ == 0) max_chunk_size_ = len; + } + + ~SourceManager() { + EXPECT_EQ(0, pub_.bytes_in_buffer); + if (!is_partial_file_) { + EXPECT_EQ(len_, pos_); + } + } + + bool LoadNextChunk() { + if (pos_ >= len_ && !is_partial_file_) { + return false; + } + if (pub_.bytes_in_buffer > 0) { + EXPECT_LE(pub_.bytes_in_buffer, buffer_.size()); + memmove(buffer_.data(), pub_.next_input_byte, pub_.bytes_in_buffer); + } + size_t chunk_size = + pos_ < len_ ? std::min(len_ - pos_, max_chunk_size_) : 2; + buffer_.resize(pub_.bytes_in_buffer + chunk_size); + memcpy(&buffer_[pub_.bytes_in_buffer], + pos_ < len_ ? data_ + pos_ : kFakeEoiMarker, chunk_size); + pub_.next_input_byte = buffer_.data(); + pub_.bytes_in_buffer += chunk_size; + pos_ += chunk_size; + return true; + } + + private: + jpeg_source_mgr pub_; + std::vector buffer_; + const uint8_t* data_; + size_t len_; + size_t pos_; + size_t max_chunk_size_; + bool is_partial_file_; + + static void init_source(j_decompress_ptr cinfo) { + auto* src = reinterpret_cast(cinfo->src); + src->pub_.next_input_byte = nullptr; + src->pub_.bytes_in_buffer = 0; + } + + static boolean fill_input_buffer(j_decompress_ptr cinfo) { return FALSE; } + + static void skip_input_data(j_decompress_ptr cinfo, + long num_bytes /* NOLINT*/) { + auto* src = reinterpret_cast(cinfo->src); + if (num_bytes <= 0) { + return; + } + if (src->pub_.bytes_in_buffer >= static_cast(num_bytes)) { + src->pub_.bytes_in_buffer -= num_bytes; + src->pub_.next_input_byte += num_bytes; + } else { + src->pos_ += num_bytes - src->pub_.bytes_in_buffer; + src->pub_.bytes_in_buffer = 0; + } + } + + static void term_source(j_decompress_ptr cinfo) {} +}; + +uint8_t markers_seen[kMarkerSequenceLen]; +size_t num_markers_seen = 0; + +uint8_t get_next_byte(j_decompress_ptr cinfo) { + cinfo->src->bytes_in_buffer--; + return *cinfo->src->next_input_byte++; +} + +boolean test_marker_processor(j_decompress_ptr cinfo) { + markers_seen[num_markers_seen] = cinfo->unread_marker; + if (cinfo->src->bytes_in_buffer < 2) { + return FALSE; + } + size_t marker_len = (get_next_byte(cinfo) << 8) + get_next_byte(cinfo); + EXPECT_EQ(2 + ((num_markers_seen + 2) % sizeof(kMarkerData)), marker_len); + if (marker_len > 2) { + (*cinfo->src->skip_input_data)(cinfo, marker_len - 2); + } + ++num_markers_seen; + return TRUE; +} + +jxl::Status ReadOutputImage(const DecompressParams& dparams, + j_decompress_ptr cinfo, SourceManager* src, + TestImage* output) { + output->ysize = cinfo->output_height; + output->xsize = cinfo->output_width; + output->components = cinfo->num_components; + if (cinfo->raw_data_out) { + output->color_space = cinfo->jpeg_color_space; + for (int c = 0; c < cinfo->num_components; ++c) { + size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE; + size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE; + std::vector plane(ysize * xsize); + output->raw_data.emplace_back(std::move(plane)); + } + } else { + output->color_space = cinfo->out_color_space; + output->AllocatePixels(); + } + size_t total_output_lines = 0; + while (cinfo->output_scanline < cinfo->output_height) { + size_t max_lines; + size_t num_output_lines; + if (cinfo->raw_data_out) { + size_t iMCU_height = cinfo->max_v_samp_factor * DCTSIZE; + EXPECT_EQ(cinfo->output_scanline, cinfo->output_iMCU_row * iMCU_height); + max_lines = iMCU_height; + std::vector> rowdata(cinfo->num_components); + std::vector data(cinfo->num_components); + for (int c = 0; c < cinfo->num_components; ++c) { + size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE; + size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE; + size_t num_lines = cinfo->comp_info[c].v_samp_factor * DCTSIZE; + rowdata[c].resize(num_lines); + size_t y0 = cinfo->output_iMCU_row * num_lines; + for (size_t i = 0; i < num_lines; ++i) { + rowdata[c][i] = + y0 + i < ysize ? &output->raw_data[c][(y0 + i) * xsize] : nullptr; + } + data[c] = rowdata[c].data(); + } + while ((num_output_lines = + jpegli_read_raw_data(cinfo, data.data(), max_lines)) == 0) { + JXL_ENSURE(src && src->LoadNextChunk()); + } + } else { + size_t max_output_lines = dparams.max_output_lines; + if (max_output_lines == 0) max_output_lines = cinfo->output_height; + size_t lines_left = cinfo->output_height - cinfo->output_scanline; + max_lines = std::min(max_output_lines, lines_left); + size_t stride = cinfo->output_width * cinfo->num_components; + std::vector scanlines(max_lines); + for (size_t i = 0; i < max_lines; ++i) { + size_t yidx = cinfo->output_scanline + i; + scanlines[i] = &output->pixels[yidx * stride]; + } + while ((num_output_lines = jpegli_read_scanlines(cinfo, scanlines.data(), + max_lines)) == 0) { + JXL_ENSURE(src && src->LoadNextChunk()); + } + } + total_output_lines += num_output_lines; + EXPECT_EQ(total_output_lines, cinfo->output_scanline); + if (num_output_lines < max_lines) { + JXL_ENSURE(src && src->LoadNextChunk()); + } + } + return true; +} + +struct TestConfig { + std::string fn; + std::string fn_desc; + TestImage input; + CompressParams jparams; + DecompressParams dparams; + float max_rms_dist = 1.0f; +}; + +jxl::StatusOr> GetTestJpegData(TestConfig& config) { + std::vector compressed; + if (!config.fn.empty()) { + JXL_ASSIGN_OR_RETURN(compressed, ReadTestData(config.fn)); + } else { + GeneratePixels(&config.input); + JXL_RETURN_IF_ERROR( + EncodeWithJpegli(config.input, config.jparams, &compressed)); + } + return compressed; +} + +bool IsSequential(const TestConfig& config) { + if (!config.fn.empty()) { + return config.fn_desc.find("PROGR") == std::string::npos; + } + return config.jparams.progressive_mode <= 0; +} + +class InputSuspensionTestParam : public ::testing::TestWithParam {}; + +TEST_P(InputSuspensionTestParam, InputOutputLockStepNonBuffered) { + TestConfig config = GetParam(); + const DecompressParams& dparams = config.dparams; + JXL_ASSIGN_OR_QUIT(std::vector compressed, GetTestJpegData(config), + "Failed to create test data."); + bool is_partial = config.dparams.size_factor < 1.0f; + if (is_partial) { + compressed.resize(compressed.size() * config.dparams.size_factor); + } + SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size, + is_partial); + TestImage output0; + jpeg_decompress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + cinfo.src = reinterpret_cast(&src); + + if (config.jparams.add_marker) { + jpegli_save_markers(&cinfo, kSpecialMarker0, 0xffff); + jpegli_save_markers(&cinfo, kSpecialMarker1, 0xffff); + num_markers_seen = 0; + jpegli_set_marker_processor(&cinfo, 0xe6, test_marker_processor); + jpegli_set_marker_processor(&cinfo, 0xe7, test_marker_processor); + jpegli_set_marker_processor(&cinfo, 0xe8, test_marker_processor); + } + while (jpegli_read_header(&cinfo, TRUE) == JPEG_SUSPENDED) { + JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk()); + } + SetDecompressParams(dparams, &cinfo); + jpegli_set_output_format(&cinfo, dparams.data_type, dparams.endianness); + if (config.jparams.add_marker) { + EXPECT_EQ(num_markers_seen, kMarkerSequenceLen); + EXPECT_EQ(0, memcmp(markers_seen, kMarkerSequence, num_markers_seen)); + } + VerifyHeader(config.jparams, &cinfo); + cinfo.raw_data_out = TO_JXL_BOOL(dparams.output_mode == RAW_DATA); + + if (dparams.output_mode == COEFFICIENTS) { + jvirt_barray_ptr* coef_arrays; + while ((coef_arrays = jpegli_read_coefficients(&cinfo)) == nullptr) { + JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk()); + } + CopyCoefficients(&cinfo, coef_arrays, &output0); + } else { + while (!jpegli_start_decompress(&cinfo)) { + JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk()); + } + JPEGLI_TEST_ENSURE_TRUE(ReadOutputImage(dparams, &cinfo, &src, &output0)); + } + + while (!jpegli_finish_decompress(&cinfo)) { + JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk()); + } + return true; + }; + ASSERT_TRUE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); + + TestImage output1; + DecodeWithLibjpeg(config.jparams, dparams, compressed, &output1); + VerifyOutputImage(output1, output0, config.max_rms_dist); +} + +TEST_P(InputSuspensionTestParam, InputOutputLockStepBuffered) { + TestConfig config = GetParam(); + if (config.jparams.add_marker) return; + const DecompressParams& dparams = config.dparams; + JXL_ASSIGN_OR_QUIT(std::vector compressed, GetTestJpegData(config), + "Failed to create test data."); + bool is_partial = config.dparams.size_factor < 1.0f; + if (is_partial) { + compressed.resize(compressed.size() * config.dparams.size_factor); + } + SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size, + is_partial); + std::vector output_progression0; + jpeg_decompress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + + cinfo.src = reinterpret_cast(&src); + + while (jpegli_read_header(&cinfo, TRUE) == JPEG_SUSPENDED) { + JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk()); + } + SetDecompressParams(dparams, &cinfo); + jpegli_set_output_format(&cinfo, dparams.data_type, dparams.endianness); + + cinfo.buffered_image = TRUE; + cinfo.raw_data_out = TO_JXL_BOOL(dparams.output_mode == RAW_DATA); + + EXPECT_TRUE(jpegli_start_decompress(&cinfo)); + EXPECT_FALSE(jpegli_input_complete(&cinfo)); + EXPECT_EQ(0, cinfo.output_scan_number); + + int sos_marker_cnt = 1; // read_header reads the first SOS marker + while (!jpegli_input_complete(&cinfo)) { + EXPECT_EQ(cinfo.input_scan_number, sos_marker_cnt); + EXPECT_TRUE(jpegli_start_output(&cinfo, cinfo.input_scan_number)); + // start output sets output_scan_number, but does not change + // input_scan_number + EXPECT_EQ(cinfo.output_scan_number, cinfo.input_scan_number); + EXPECT_EQ(cinfo.input_scan_number, sos_marker_cnt); + TestImage output; + JPEGLI_TEST_ENSURE_TRUE(ReadOutputImage(dparams, &cinfo, &src, &output)); + output_progression0.emplace_back(std::move(output)); + // read scanlines/read raw data does not change input/output scan number + EXPECT_EQ(cinfo.input_scan_number, sos_marker_cnt); + EXPECT_EQ(cinfo.output_scan_number, cinfo.input_scan_number); + while (!jpegli_finish_output(&cinfo)) { + JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk()); + } + ++sos_marker_cnt; // finish output reads the next SOS marker or EOI + if (dparams.output_mode == COEFFICIENTS) { + jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(&cinfo); + JPEGLI_TEST_ENSURE_TRUE(coef_arrays != nullptr); + CopyCoefficients(&cinfo, coef_arrays, &output_progression0.back()); + } + } + + EXPECT_TRUE(jpegli_finish_decompress(&cinfo)); + return true; + }; + ASSERT_TRUE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); + + std::vector output_progression1; + DecodeAllScansWithLibjpeg(config.jparams, dparams, compressed, + &output_progression1); + ASSERT_EQ(output_progression0.size(), output_progression1.size()); + for (size_t i = 0; i < output_progression0.size(); ++i) { + const TestImage& output = output_progression0[i]; + const TestImage& expected = output_progression1[i]; + VerifyOutputImage(expected, output, config.max_rms_dist); + } +} + +TEST_P(InputSuspensionTestParam, PreConsumeInputBuffered) { + TestConfig config = GetParam(); + if (config.jparams.add_marker) return; + const DecompressParams& dparams = config.dparams; + JXL_ASSIGN_OR_QUIT(std::vector compressed, GetTestJpegData(config), + "Failed to create test data."); + bool is_partial = config.dparams.size_factor < 1.0f; + if (is_partial) { + compressed.resize(compressed.size() * config.dparams.size_factor); + } + std::vector output_progression1; + DecodeAllScansWithLibjpeg(config.jparams, dparams, compressed, + &output_progression1); + SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size, + is_partial); + TestImage output0; + jpeg_decompress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + cinfo.src = reinterpret_cast(&src); + + int status; + while ((status = jpegli_consume_input(&cinfo)) != JPEG_REACHED_SOS) { + if (status == JPEG_SUSPENDED) { + JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk()); + } + } + EXPECT_EQ(JPEG_REACHED_SOS, jpegli_consume_input(&cinfo)); + cinfo.buffered_image = TRUE; + cinfo.raw_data_out = TO_JXL_BOOL(dparams.output_mode == RAW_DATA); + cinfo.do_block_smoothing = TO_JXL_BOOL(dparams.do_block_smoothing); + + EXPECT_TRUE(jpegli_start_decompress(&cinfo)); + EXPECT_FALSE(jpegli_input_complete(&cinfo)); + EXPECT_EQ(1, cinfo.input_scan_number); + EXPECT_EQ(0, cinfo.output_scan_number); + + while ((status = jpegli_consume_input(&cinfo)) != JPEG_REACHED_EOI) { + if (status == JPEG_SUSPENDED) { + JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk()); + } + } + + EXPECT_TRUE(jpegli_input_complete(&cinfo)); + EXPECT_EQ(output_progression1.size(), cinfo.input_scan_number); + EXPECT_EQ(0, cinfo.output_scan_number); + + EXPECT_TRUE(jpegli_start_output(&cinfo, cinfo.input_scan_number)); + EXPECT_EQ(output_progression1.size(), cinfo.input_scan_number); + EXPECT_EQ(cinfo.output_scan_number, cinfo.input_scan_number); + + JPEGLI_TEST_ENSURE_TRUE( + ReadOutputImage(dparams, &cinfo, nullptr, &output0)); + EXPECT_EQ(output_progression1.size(), cinfo.input_scan_number); + EXPECT_EQ(cinfo.output_scan_number, cinfo.input_scan_number); + + EXPECT_TRUE(jpegli_finish_output(&cinfo)); + if (dparams.output_mode == COEFFICIENTS) { + jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(&cinfo); + JPEGLI_TEST_ENSURE_TRUE(coef_arrays != nullptr); + CopyCoefficients(&cinfo, coef_arrays, &output0); + } + EXPECT_TRUE(jpegli_finish_decompress(&cinfo)); + return true; + }; + ASSERT_TRUE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); + + VerifyOutputImage(output_progression1.back(), output0, config.max_rms_dist); +} + +TEST_P(InputSuspensionTestParam, PreConsumeInputNonBuffered) { + TestConfig config = GetParam(); + if (config.jparams.add_marker || IsSequential(config)) return; + const DecompressParams& dparams = config.dparams; + JXL_ASSIGN_OR_QUIT(std::vector compressed, GetTestJpegData(config), + "Failed to create test data."); + bool is_partial = config.dparams.size_factor < 1.0f; + if (is_partial) { + compressed.resize(compressed.size() * config.dparams.size_factor); + } + SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size, + is_partial); + TestImage output0; + jpeg_decompress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + cinfo.src = reinterpret_cast(&src); + + int status; + while ((status = jpegli_consume_input(&cinfo)) != JPEG_REACHED_SOS) { + if (status == JPEG_SUSPENDED) { + JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk()); + } + } + EXPECT_EQ(JPEG_REACHED_SOS, jpegli_consume_input(&cinfo)); + cinfo.raw_data_out = TO_JXL_BOOL(dparams.output_mode == RAW_DATA); + cinfo.do_block_smoothing = TO_JXL_BOOL(dparams.do_block_smoothing); + + if (dparams.output_mode == COEFFICIENTS) { + jpegli_read_coefficients(&cinfo); + } else { + while (!jpegli_start_decompress(&cinfo)) { + JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk()); + } + } + + while ((status = jpegli_consume_input(&cinfo)) != JPEG_REACHED_EOI) { + if (status == JPEG_SUSPENDED) { + JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk()); + } + } + + if (dparams.output_mode == COEFFICIENTS) { + jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(&cinfo); + JPEGLI_TEST_ENSURE_TRUE(coef_arrays != nullptr); + CopyCoefficients(&cinfo, coef_arrays, &output0); + } else { + JPEGLI_TEST_ENSURE_TRUE( + ReadOutputImage(dparams, &cinfo, nullptr, &output0)); + } + + EXPECT_TRUE(jpegli_finish_decompress(&cinfo)); + return true; + }; + ASSERT_TRUE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); + + TestImage output1; + DecodeWithLibjpeg(config.jparams, dparams, compressed, &output1); + VerifyOutputImage(output1, output0, config.max_rms_dist); +} + +std::vector GenerateTests() { + std::vector all_tests; + std::vector> testfiles({ + {"jxl/flower/flower.png.im_q85_444.jpg", "Q85YUV444"}, + {"jxl/flower/flower.png.im_q85_420_R13B.jpg", "Q85YUV420R13B"}, + {"jxl/flower/flower.png.im_q85_420_progr.jpg", "Q85YUV420PROGR"}, + }); + for (const auto& it : testfiles) { + for (size_t chunk_size : {1, 64, 65536}) { + for (size_t max_output_lines : {0, 1, 8, 16}) { + TestConfig config; + config.fn = it.first; + config.fn_desc = it.second; + config.dparams.chunk_size = chunk_size; + config.dparams.max_output_lines = max_output_lines; + all_tests.push_back(config); + if (max_output_lines == 16) { + config.dparams.output_mode = RAW_DATA; + all_tests.push_back(config); + config.dparams.output_mode = COEFFICIENTS; + all_tests.push_back(config); + } + } + } + } + for (size_t r : {1, 17, 1024}) { + for (size_t chunk_size : {1, 65536}) { + TestConfig config; + config.dparams.chunk_size = chunk_size; + config.jparams.progressive_mode = 2; + config.jparams.restart_interval = r; + all_tests.push_back(config); + } + } + for (size_t chunk_size : {1, 4, 1024}) { + TestConfig config; + config.input.xsize = 256; + config.input.ysize = 256; + config.dparams.chunk_size = chunk_size; + config.jparams.add_marker = true; + all_tests.push_back(config); + } + // Tests for partial input. + for (float size_factor : {0.1f, 0.33f, 0.5f, 0.75f}) { + for (int progr : {0, 1, 3}) { + for (int samp : {1, 2}) { + for (JpegIOMode output_mode : {PIXELS, RAW_DATA}) { + TestConfig config; + config.input.xsize = 517; + config.input.ysize = 523; + config.jparams.h_sampling = {samp, 1, 1}; + config.jparams.v_sampling = {samp, 1, 1}; + config.jparams.progressive_mode = progr; + config.dparams.size_factor = size_factor; + config.dparams.output_mode = output_mode; + // The last partially available block can behave differently. + // TODO(szabadka) Figure out if we can make the behaviour more + // similar. + config.max_rms_dist = samp == 1 ? 1.75f : 3.0f; + all_tests.push_back(config); + } + } + } + } + // Tests for block smoothing. + for (float size_factor : {0.1f, 0.33f, 0.5f, 0.75f, 1.0f}) { + for (int samp : {1, 2}) { + TestConfig config; + config.input.xsize = 517; + config.input.ysize = 523; + config.jparams.h_sampling = {samp, 1, 1}; + config.jparams.v_sampling = {samp, 1, 1}; + config.jparams.progressive_mode = 2; + config.dparams.size_factor = size_factor; + config.dparams.do_block_smoothing = true; + // libjpeg does smoothing for incomplete scans differently at + // the border between current and previous scans. + config.max_rms_dist = 8.0f; + all_tests.push_back(config); + } + } + return all_tests; +} + +std::ostream& operator<<(std::ostream& os, const TestConfig& c) { + if (!c.fn.empty()) { + os << c.fn_desc; + } else { + os << c.input; + } + os << c.jparams; + if (c.dparams.chunk_size == 0) { + os << "CompleteInput"; + } else { + os << "InputChunks" << c.dparams.chunk_size; + } + if (c.dparams.size_factor < 1.0f) { + os << "Partial" << static_cast(c.dparams.size_factor * 100) << "p"; + } + if (c.dparams.max_output_lines == 0) { + os << "CompleteOutput"; + } else { + os << "OutputLines" << c.dparams.max_output_lines; + } + if (c.dparams.output_mode == RAW_DATA) { + os << "RawDataOut"; + } else if (c.dparams.output_mode == COEFFICIENTS) { + os << "CoeffsOut"; + } + if (c.dparams.do_block_smoothing) { + os << "BlockSmoothing"; + } + return os; +} + +std::string TestDescription( + const testing::TestParamInfo& info) { + std::stringstream name; + name << info.param; + return name.str(); +} + +JPEGLI_INSTANTIATE_TEST_SUITE_P(InputSuspensionTest, InputSuspensionTestParam, + testing::ValuesIn(GenerateTests()), + TestDescription); + +} // namespace +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/jpeg.version.62 b/media/libjxl/src/lib/jpegli/jpeg.version.62 new file mode 100644 index 0000000000..3a8d1f5ec5 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/jpeg.version.62 @@ -0,0 +1,11 @@ +LIBJPEG_6.2 { + global: + jpeg*; +}; + +LIBJPEGTURBO_6.2 { + global: + jpeg_mem_src*; + jpeg_mem_dest*; + tj*; +}; \ No newline at end of file diff --git a/media/libjxl/src/lib/jpegli/jpeg.version.8 b/media/libjxl/src/lib/jpegli/jpeg.version.8 new file mode 100644 index 0000000000..aa891f8571 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/jpeg.version.8 @@ -0,0 +1,9 @@ +LIBJPEG_8.0 { + global: + jpeg*; +}; + +LIBJPEGTURBO_8.0 { + global: + tj*; +}; diff --git a/media/libjxl/src/lib/jpegli/libjpeg_test_util.cc b/media/libjxl/src/lib/jpegli/libjpeg_test_util.cc new file mode 100644 index 0000000000..76746a8fdd --- /dev/null +++ b/media/libjxl/src/lib/jpegli/libjpeg_test_util.cc @@ -0,0 +1,269 @@ +// 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/jpegli/libjpeg_test_util.h" + +#include +#include + +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/include_jpeglib.h" // NOLINT +#include "lib/jxl/base/sanitizers.h" + +namespace jpegli { + +namespace { + +void Check(bool ok) { + if (!ok) { + JXL_CRASH(); + } +} + +#define JPEG_API_FN(name) jpeg_##name +#include "lib/jpegli/test_utils-inl.h" +#undef JPEG_API_FN + +void ReadOutputPass(j_decompress_ptr cinfo, const DecompressParams& dparams, + TestImage* output) { + JDIMENSION xoffset = 0; + JDIMENSION yoffset = 0; + JDIMENSION xsize_cropped = cinfo->output_width; + JDIMENSION ysize_cropped = cinfo->output_height; + if (dparams.crop_output) { + xoffset = xsize_cropped = cinfo->output_width / 3; + yoffset = ysize_cropped = cinfo->output_height / 3; + jpeg_crop_scanline(cinfo, &xoffset, &xsize_cropped); + Check(xsize_cropped == cinfo->output_width); + } + output->xsize = xsize_cropped; + output->ysize = ysize_cropped; + output->components = cinfo->out_color_components; + if (cinfo->quantize_colors) { + JSAMPLE** colormap = cinfo->colormap; + jxl::msan::UnpoisonMemory(reinterpret_cast(colormap), + cinfo->out_color_components * sizeof(JSAMPLE*)); + for (int c = 0; c < cinfo->out_color_components; ++c) { + jxl::msan::UnpoisonMemory( + reinterpret_cast(colormap[c]), + cinfo->actual_number_of_colors * sizeof(JSAMPLE)); + } + } + if (!cinfo->raw_data_out) { + size_t stride = output->xsize * output->components; + output->pixels.resize(output->ysize * stride); + output->color_space = cinfo->out_color_space; + if (yoffset > 0) { + jpeg_skip_scanlines(cinfo, yoffset); + } + for (size_t y = 0; y < output->ysize; ++y) { + JSAMPROW rows[] = { + reinterpret_cast(&output->pixels[y * stride])}; + Check(1 == jpeg_read_scanlines(cinfo, rows, 1)); + jxl::msan::UnpoisonMemory( + rows[0], sizeof(JSAMPLE) * cinfo->output_components * output->xsize); + if (cinfo->quantize_colors) { + UnmapColors(rows[0], cinfo->output_width, cinfo->out_color_components, + cinfo->colormap, cinfo->actual_number_of_colors); + } + } + if (cinfo->output_scanline < cinfo->output_height) { + jpeg_skip_scanlines(cinfo, cinfo->output_height - cinfo->output_scanline); + } + } else { + output->color_space = cinfo->jpeg_color_space; + for (int c = 0; c < cinfo->num_components; ++c) { + size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE; + size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE; + std::vector plane(ysize * xsize); + output->raw_data.emplace_back(std::move(plane)); + } + while (cinfo->output_scanline < cinfo->output_height) { + size_t iMCU_height = cinfo->max_v_samp_factor * DCTSIZE; + Check(cinfo->output_scanline == cinfo->output_iMCU_row * iMCU_height); + std::vector> rowdata(cinfo->num_components); + std::vector data(cinfo->num_components); + for (int c = 0; c < cinfo->num_components; ++c) { + size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE; + size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE; + size_t num_lines = cinfo->comp_info[c].v_samp_factor * DCTSIZE; + rowdata[c].resize(num_lines); + size_t y0 = cinfo->output_iMCU_row * num_lines; + for (size_t i = 0; i < num_lines; ++i) { + rowdata[c][i] = + y0 + i < ysize ? &output->raw_data[c][(y0 + i) * xsize] : nullptr; + } + data[c] = rowdata[c].data(); + } + Check(iMCU_height == jpeg_read_raw_data(cinfo, data.data(), iMCU_height)); + } + } + Check(cinfo->total_iMCU_rows == + DivCeil(cinfo->image_height, cinfo->max_v_samp_factor * DCTSIZE)); +} + +void DecodeWithLibjpeg(const CompressParams& jparams, + const DecompressParams& dparams, j_decompress_ptr cinfo, + TestImage* output) { + if (jparams.add_marker) { + jpeg_save_markers(cinfo, kSpecialMarker0, 0xffff); + jpeg_save_markers(cinfo, kSpecialMarker1, 0xffff); + } + if (!jparams.icc.empty()) { + jpeg_save_markers(cinfo, JPEG_APP0 + 2, 0xffff); + } + Check(JPEG_REACHED_SOS == jpeg_read_header(cinfo, /*require_image=*/TRUE)); + if (!jparams.icc.empty()) { + uint8_t* icc_data = nullptr; + unsigned int icc_len = 0; // "unpoison" via initialization + Check(jpeg_read_icc_profile(cinfo, &icc_data, &icc_len)); + Check(icc_data); + jxl::msan::UnpoisonMemory(icc_data, icc_len); + Check(0 == memcmp(jparams.icc.data(), icc_data, icc_len)); + free(icc_data); + } + SetDecompressParams(dparams, cinfo); + VerifyHeader(jparams, cinfo); + if (dparams.output_mode == COEFFICIENTS) { + jvirt_barray_ptr* coef_arrays = jpeg_read_coefficients(cinfo); + Check(coef_arrays != nullptr); + jxl::msan::UnpoisonMemory(coef_arrays, + cinfo->num_components * sizeof(jvirt_barray_ptr)); + CopyCoefficients(cinfo, coef_arrays, output); + } else { + Check(jpeg_start_decompress(cinfo)); + VerifyScanHeader(jparams, cinfo); + ReadOutputPass(cinfo, dparams, output); + } + Check(jpeg_finish_decompress(cinfo)); +} + +} // namespace + +// Verifies that an image encoded with libjpegli can be decoded with libjpeg, +// and checks that the jpeg coding metadata matches jparams. +void DecodeAllScansWithLibjpeg(const CompressParams& jparams, + const DecompressParams& dparams, + const std::vector& compressed, + std::vector* output_progression) { + jpeg_decompress_struct cinfo = {}; + const auto try_catch_block = [&]() { + jpeg_error_mgr jerr; + jmp_buf env; + cinfo.err = jpeg_std_error(&jerr); + if (setjmp(env)) { + return false; + } + cinfo.client_data = reinterpret_cast(&env); + cinfo.err->error_exit = [](j_common_ptr cinfo) { + (*cinfo->err->output_message)(cinfo); + jmp_buf* env = reinterpret_cast(cinfo->client_data); + jpeg_destroy(cinfo); + longjmp(*env, 1); + }; + jpeg_create_decompress(&cinfo); + jpeg_mem_src(&cinfo, compressed.data(), compressed.size()); + if (jparams.add_marker) { + jpeg_save_markers(&cinfo, kSpecialMarker0, 0xffff); + jpeg_save_markers(&cinfo, kSpecialMarker1, 0xffff); + } + Check(JPEG_REACHED_SOS == jpeg_read_header(&cinfo, /*require_image=*/TRUE)); + cinfo.buffered_image = TRUE; + SetDecompressParams(dparams, &cinfo); + VerifyHeader(jparams, &cinfo); + Check(jpeg_start_decompress(&cinfo)); + // start decompress should not read the whole input in buffered image mode + Check(!jpeg_input_complete(&cinfo)); + Check(cinfo.output_scan_number == 0); + int sos_marker_cnt = 1; // read header reads the first SOS marker + while (!jpeg_input_complete(&cinfo)) { + Check(cinfo.input_scan_number == sos_marker_cnt); + if (dparams.skip_scans && (cinfo.input_scan_number % 2) != 1) { + int result = JPEG_SUSPENDED; + while (result != JPEG_REACHED_SOS && result != JPEG_REACHED_EOI) { + result = jpeg_consume_input(&cinfo); + } + if (result == JPEG_REACHED_SOS) ++sos_marker_cnt; + continue; + } + SetScanDecompressParams(dparams, &cinfo, cinfo.input_scan_number); + Check(jpeg_start_output(&cinfo, cinfo.input_scan_number)); + // start output sets output_scan_number, but does not change + // input_scan_number + Check(cinfo.output_scan_number == cinfo.input_scan_number); + Check(cinfo.input_scan_number == sos_marker_cnt); + VerifyScanHeader(jparams, &cinfo); + TestImage output; + ReadOutputPass(&cinfo, dparams, &output); + output_progression->emplace_back(std::move(output)); + // read scanlines/read raw data does not change input/output scan number + if (!cinfo.progressive_mode) { + Check(cinfo.input_scan_number == sos_marker_cnt); + Check(cinfo.output_scan_number == cinfo.input_scan_number); + } + Check(jpeg_finish_output(&cinfo)); + ++sos_marker_cnt; // finish output reads the next SOS marker or EOI + if (dparams.output_mode == COEFFICIENTS) { + jvirt_barray_ptr* coef_arrays = jpeg_read_coefficients(&cinfo); + Check(coef_arrays != nullptr); + jxl::msan::UnpoisonMemory( + coef_arrays, cinfo.num_components * sizeof(jvirt_barray_ptr)); + CopyCoefficients(&cinfo, coef_arrays, &output_progression->back()); + } + } + Check(jpeg_finish_decompress(&cinfo)); + return true; + }; + Check(try_catch_block()); + jpeg_destroy_decompress(&cinfo); +} + +// Returns the number of bytes read from compressed. +size_t DecodeWithLibjpeg(const CompressParams& jparams, + const DecompressParams& dparams, + const uint8_t* table_stream, size_t table_stream_size, + const uint8_t* compressed, size_t len, + TestImage* output) { + jpeg_decompress_struct cinfo = {}; + size_t bytes_read; + const auto try_catch_block = [&]() { + jpeg_error_mgr jerr; + jmp_buf env; + cinfo.err = jpeg_std_error(&jerr); + if (setjmp(env)) { + return false; + } + cinfo.client_data = reinterpret_cast(&env); + cinfo.err->error_exit = [](j_common_ptr cinfo) { + (*cinfo->err->output_message)(cinfo); + jmp_buf* env = reinterpret_cast(cinfo->client_data); + jpeg_destroy(cinfo); + longjmp(*env, 1); + }; + jpeg_create_decompress(&cinfo); + if (table_stream != nullptr) { + jpeg_mem_src(&cinfo, table_stream, table_stream_size); + jpeg_read_header(&cinfo, FALSE); + } + jpeg_mem_src(&cinfo, compressed, len); + DecodeWithLibjpeg(jparams, dparams, &cinfo, output); + jxl::msan::UnpoisonMemory(cinfo.src, sizeof(jpeg_source_mgr)); + bytes_read = len - cinfo.src->bytes_in_buffer; + return true; + }; + Check(try_catch_block()); + jpeg_destroy_decompress(&cinfo); + return bytes_read; +} + +void DecodeWithLibjpeg(const CompressParams& jparams, + const DecompressParams& dparams, + const std::vector& compressed, + TestImage* output) { + DecodeWithLibjpeg(jparams, dparams, nullptr, 0, compressed.data(), + compressed.size(), output); +} + +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/libjpeg_test_util.h b/media/libjxl/src/lib/jpegli/libjpeg_test_util.h new file mode 100644 index 0000000000..18cc1e57b5 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/libjpeg_test_util.h @@ -0,0 +1,37 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_JPEGLI_LIBJPEG_TEST_UTIL_H_ +#define LIB_JPEGLI_LIBJPEG_TEST_UTIL_H_ + +#include +#include + +#include + +#include "lib/jpegli/test_params.h" + +namespace jpegli { + +// Verifies that an image encoded with libjpegli can be decoded with libjpeg, +// and checks that the jpeg coding metadata matches jparams. +void DecodeAllScansWithLibjpeg(const CompressParams& jparams, + const DecompressParams& dparams, + const std::vector& compressed, + std::vector* output_progression); +// Returns the number of bytes read from compressed. +size_t DecodeWithLibjpeg(const CompressParams& jparams, + const DecompressParams& dparams, + const uint8_t* table_stream, size_t table_stream_size, + const uint8_t* compressed, size_t len, + TestImage* output); +void DecodeWithLibjpeg(const CompressParams& jparams, + const DecompressParams& dparams, + const std::vector& compressed, + TestImage* output); + +} // namespace jpegli + +#endif // LIB_JPEGLI_LIBJPEG_TEST_UTIL_H_ diff --git a/media/libjxl/src/lib/jpegli/libjpeg_wrapper.cc b/media/libjxl/src/lib/jpegli/libjpeg_wrapper.cc new file mode 100644 index 0000000000..a2b333afef --- /dev/null +++ b/media/libjxl/src/lib/jpegli/libjpeg_wrapper.cc @@ -0,0 +1,255 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// This file contains wrapper-functions that are used to build the libjpeg.so +// shared library that is API- and ABI-compatible with libjpeg-turbo's version +// of libjpeg.so. + +#include "lib/jpegli/common.h" +#include "lib/jpegli/decode.h" +#include "lib/jpegli/encode.h" +#include "lib/jpegli/error.h" + +struct jpeg_error_mgr *jpeg_std_error(struct jpeg_error_mgr *err) { + return jpegli_std_error(err); +} + +void jpeg_abort(j_common_ptr cinfo) { jpegli_abort(cinfo); } + +void jpeg_destroy(j_common_ptr cinfo) { jpegli_destroy(cinfo); } + +JQUANT_TBL *jpeg_alloc_quant_table(j_common_ptr cinfo) { + return jpegli_alloc_quant_table(cinfo); +} + +JHUFF_TBL *jpeg_alloc_huff_table(j_common_ptr cinfo) { + return jpegli_alloc_huff_table(cinfo); +} + +void jpeg_CreateDecompress(j_decompress_ptr cinfo, int version, + size_t structsize) { + jpegli_CreateDecompress(cinfo, version, structsize); +} + +void jpeg_stdio_src(j_decompress_ptr cinfo, FILE *infile) { + jpegli_stdio_src(cinfo, infile); +} + +void jpeg_mem_src(j_decompress_ptr cinfo, const unsigned char *inbuffer, + unsigned long insize /* NOLINT */) { + jpegli_mem_src(cinfo, inbuffer, insize); +} + +int jpeg_read_header(j_decompress_ptr cinfo, boolean require_image) { + return jpegli_read_header(cinfo, require_image); +} + +boolean jpeg_start_decompress(j_decompress_ptr cinfo) { + return jpegli_start_decompress(cinfo); +} + +JDIMENSION jpeg_read_scanlines(j_decompress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION max_lines) { + return jpegli_read_scanlines(cinfo, scanlines, max_lines); +} + +JDIMENSION jpeg_skip_scanlines(j_decompress_ptr cinfo, JDIMENSION num_lines) { + return jpegli_skip_scanlines(cinfo, num_lines); +} + +void jpeg_crop_scanline(j_decompress_ptr cinfo, JDIMENSION *xoffset, + JDIMENSION *width) { + jpegli_crop_scanline(cinfo, xoffset, width); +} + +boolean jpeg_finish_decompress(j_decompress_ptr cinfo) { + return jpegli_finish_decompress(cinfo); +} + +JDIMENSION jpeg_read_raw_data(j_decompress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION max_lines) { + return jpegli_read_raw_data(cinfo, data, max_lines); +} + +jvirt_barray_ptr *jpeg_read_coefficients(j_decompress_ptr cinfo) { + return jpegli_read_coefficients(cinfo); +} + +boolean jpeg_has_multiple_scans(j_decompress_ptr cinfo) { + return jpegli_has_multiple_scans(cinfo); +} + +boolean jpeg_start_output(j_decompress_ptr cinfo, int scan_number) { + return jpegli_start_output(cinfo, scan_number); +} + +boolean jpeg_finish_output(j_decompress_ptr cinfo) { + return jpegli_finish_output(cinfo); +} + +boolean jpeg_input_complete(j_decompress_ptr cinfo) { + return jpegli_input_complete(cinfo); +} + +int jpeg_consume_input(j_decompress_ptr cinfo) { + return jpegli_consume_input(cinfo); +} + +#if JPEG_LIB_VERSION >= 80 +void jpeg_core_output_dimensions(j_decompress_ptr cinfo) { + jpegli_core_output_dimensions(cinfo); +} +#endif +void jpeg_calc_output_dimensions(j_decompress_ptr cinfo) { + jpegli_calc_output_dimensions(cinfo); +} + +void jpeg_save_markers(j_decompress_ptr cinfo, int marker_code, + unsigned int length_limit) { + jpegli_save_markers(cinfo, marker_code, length_limit); +} + +void jpeg_set_marker_processor(j_decompress_ptr cinfo, int marker_code, + jpeg_marker_parser_method routine) { + jpegli_set_marker_processor(cinfo, marker_code, routine); +} + +boolean jpeg_read_icc_profile(j_decompress_ptr cinfo, JOCTET **icc_data_ptr, + unsigned int *icc_data_len) { + return jpegli_read_icc_profile(cinfo, icc_data_ptr, icc_data_len); +} + +void jpeg_abort_decompress(j_decompress_ptr cinfo) { + jpegli_abort_decompress(cinfo); +} + +void jpeg_destroy_decompress(j_decompress_ptr cinfo) { + jpegli_destroy_decompress(cinfo); +} + +void jpeg_CreateCompress(j_compress_ptr cinfo, int version, size_t structsize) { + jpegli_CreateCompress(cinfo, version, structsize); +} + +void jpeg_stdio_dest(j_compress_ptr cinfo, FILE *outfile) { + jpegli_stdio_dest(cinfo, outfile); +} + +void jpeg_mem_dest(j_compress_ptr cinfo, unsigned char **outbuffer, + unsigned long *outsize /* NOLINT */) { + jpegli_mem_dest(cinfo, outbuffer, outsize); +} + +void jpeg_set_defaults(j_compress_ptr cinfo) { jpegli_set_defaults(cinfo); } + +void jpeg_default_colorspace(j_compress_ptr cinfo) { + jpegli_default_colorspace(cinfo); +} + +void jpeg_set_colorspace(j_compress_ptr cinfo, J_COLOR_SPACE colorspace) { + jpegli_set_colorspace(cinfo, colorspace); +} + +void jpeg_set_quality(j_compress_ptr cinfo, int quality, + boolean force_baseline) { + jpegli_set_quality(cinfo, quality, force_baseline); +} + +void jpeg_set_linear_quality(j_compress_ptr cinfo, int scale_factor, + boolean force_baseline) { + jpegli_set_linear_quality(cinfo, scale_factor, force_baseline); +} + +#if JPEG_LIB_VERSION >= 70 +void jpeg_default_qtables(j_compress_ptr cinfo, boolean force_baseline) { + jpegli_default_qtables(cinfo, force_baseline); +} +#endif + +int jpeg_quality_scaling(int quality) { + return jpegli_quality_scaling(quality); +} + +void jpeg_add_quant_table(j_compress_ptr cinfo, int which_tbl, + const unsigned int *basic_table, int scale_factor, + boolean force_baseline) { + jpegli_add_quant_table(cinfo, which_tbl, basic_table, scale_factor, + force_baseline); +} + +void jpeg_simple_progression(j_compress_ptr cinfo) { + jpegli_simple_progression(cinfo); +} + +void jpeg_suppress_tables(j_compress_ptr cinfo, boolean suppress) { + jpegli_suppress_tables(cinfo, suppress); +} + +#if JPEG_LIB_VERSION >= 70 +void jpeg_calc_jpeg_dimensions(j_compress_ptr cinfo) { + jpegli_calc_jpeg_dimensions(cinfo); +} +#endif + +void jpeg_copy_critical_parameters(j_decompress_ptr srcinfo, + j_compress_ptr dstinfo) { + jpegli_copy_critical_parameters(srcinfo, dstinfo); +} + +void jpeg_write_m_header(j_compress_ptr cinfo, int marker, + unsigned int datalen) { + jpegli_write_m_header(cinfo, marker, datalen); +} + +void jpeg_write_m_byte(j_compress_ptr cinfo, int val) { + jpegli_write_m_byte(cinfo, val); +} + +void jpeg_write_marker(j_compress_ptr cinfo, int marker, const JOCTET *dataptr, + unsigned int datalen) { + jpegli_write_marker(cinfo, marker, dataptr, datalen); +} + +void jpeg_write_icc_profile(j_compress_ptr cinfo, const JOCTET *icc_data_ptr, + unsigned int icc_data_len) { + jpegli_write_icc_profile(cinfo, icc_data_ptr, icc_data_len); +} + +void jpeg_start_compress(j_compress_ptr cinfo, boolean write_all_tables) { + jpegli_start_compress(cinfo, write_all_tables); +} + +void jpeg_write_tables(j_compress_ptr cinfo) { jpegli_write_tables(cinfo); } + +JDIMENSION jpeg_write_scanlines(j_compress_ptr cinfo, JSAMPARRAY scanlines, + JDIMENSION num_lines) { + return jpegli_write_scanlines(cinfo, scanlines, num_lines); +} + +JDIMENSION jpeg_write_raw_data(j_compress_ptr cinfo, JSAMPIMAGE data, + JDIMENSION num_lines) { + return jpegli_write_raw_data(cinfo, data, num_lines); +} + +void jpeg_write_coefficients(j_compress_ptr cinfo, + jvirt_barray_ptr *coef_arrays) { + jpegli_write_coefficients(cinfo, coef_arrays); +} + +void jpeg_finish_compress(j_compress_ptr cinfo) { + jpegli_finish_compress(cinfo); +} + +void jpeg_abort_compress(j_compress_ptr cinfo) { jpegli_abort_compress(cinfo); } + +void jpeg_destroy_compress(j_compress_ptr cinfo) { + jpegli_destroy_compress(cinfo); +} + +boolean jpeg_resync_to_restart(j_decompress_ptr cinfo, int desired) { + return jpegli_resync_to_restart(cinfo, desired); +} + +void jpeg_new_colormap(j_decompress_ptr cinfo) { jpegli_new_colormap(cinfo); } diff --git a/media/libjxl/src/lib/jpegli/memory_manager.cc b/media/libjxl/src/lib/jpegli/memory_manager.cc new file mode 100644 index 0000000000..3a8f230e63 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/memory_manager.cc @@ -0,0 +1,186 @@ +// 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/jpegli/memory_manager.h" + +#include + +#include +#include + +#include "lib/jpegli/common_internal.h" +#include "lib/jpegli/error.h" + +struct jvirt_sarray_control { + JSAMPARRAY full_buffer; + size_t numrows; + JDIMENSION maxaccess; +}; + +struct jvirt_barray_control { + JBLOCKARRAY full_buffer; + size_t numrows; + JDIMENSION maxaccess; +}; + +namespace jpegli { + +namespace { + +struct MemoryManager { + struct jpeg_memory_mgr pub; + std::vector owned_ptrs[2 * JPOOL_NUMPOOLS]; + uint64_t pool_memory_usage[2 * JPOOL_NUMPOOLS]; + uint64_t total_memory_usage; + uint64_t peak_memory_usage; +}; + +void* Alloc(j_common_ptr cinfo, int pool_id, size_t sizeofobject) { + MemoryManager* mem = reinterpret_cast(cinfo->mem); + if (pool_id < 0 || pool_id >= 2 * JPOOL_NUMPOOLS) { + JPEGLI_ERROR("Invalid pool id %d", pool_id); + } + if (mem->pub.max_memory_to_use > 0 && + mem->total_memory_usage + static_cast(sizeofobject) > + static_cast(mem->pub.max_memory_to_use)) { + JPEGLI_ERROR("Total memory usage exceeding %ld", + mem->pub.max_memory_to_use); + } + void* p; + if (pool_id < JPOOL_NUMPOOLS) { + p = malloc(sizeofobject); + } else { + p = hwy::AllocateAlignedBytes(sizeofobject, nullptr, nullptr); + } + if (p == nullptr) { + JPEGLI_ERROR("Out of memory"); + } + mem->owned_ptrs[pool_id].push_back(p); + mem->pool_memory_usage[pool_id] += sizeofobject; + mem->total_memory_usage += sizeofobject; + mem->peak_memory_usage = + std::max(mem->peak_memory_usage, mem->total_memory_usage); + return p; +} + +constexpr size_t gcd(size_t a, size_t b) { return b == 0 ? a : gcd(b, a % b); } +constexpr size_t lcm(size_t a, size_t b) { return (a * b) / gcd(a, b); } + +template +T** Alloc2dArray(j_common_ptr cinfo, int pool_id, JDIMENSION samplesperrow, + JDIMENSION numrows) { + T** array = Allocate(cinfo, numrows, pool_id); + // Always use aligned allocator for large 2d arrays. + if (pool_id < JPOOL_NUMPOOLS) { + pool_id += JPOOL_NUMPOOLS; + } + size_t alignment = lcm(sizeof(T), HWY_ALIGNMENT); + size_t memstride = RoundUpTo(samplesperrow * sizeof(T), alignment); + size_t stride = memstride / sizeof(T); + T* buffer = Allocate(cinfo, numrows * stride, pool_id); + for (size_t i = 0; i < numrows; ++i) { + array[i] = &buffer[i * stride]; + } + return array; +} + +template +Control* RequestVirtualArray(j_common_ptr cinfo, int pool_id, boolean pre_zero, + JDIMENSION samplesperrow, JDIMENSION numrows, + JDIMENSION maxaccess) { + if (pool_id != JPOOL_IMAGE) { + JPEGLI_ERROR("Only image lifetime virtual arrays are supported."); + } + Control* p = Allocate(cinfo, 1, pool_id); + p->full_buffer = Alloc2dArray(cinfo, pool_id, samplesperrow, numrows); + p->numrows = numrows; + p->maxaccess = maxaccess; + if (pre_zero) { + for (size_t i = 0; i < numrows; ++i) { + memset(p->full_buffer[i], 0, samplesperrow * sizeof(T)); + } + } + return p; +} + +void RealizeVirtualArrays(j_common_ptr cinfo) { + // Nothing to do, the full arrays were realized at request time already. +} + +template +T** AccessVirtualArray(j_common_ptr cinfo, Control* ptr, JDIMENSION start_row, + JDIMENSION num_rows, boolean writable) { + if (num_rows > ptr->maxaccess) { + JPEGLI_ERROR("Invalid virtual array access, num rows %u vs max rows %u", + num_rows, ptr->maxaccess); + } + if (start_row + num_rows > ptr->numrows) { + JPEGLI_ERROR("Invalid virtual array access, %u vs %u total rows", + start_row + num_rows, ptr->numrows); + } + if (ptr->full_buffer == nullptr) { + JPEGLI_ERROR("Invalid virtual array access, array not realized."); + } + return ptr->full_buffer + start_row; +} + +void ClearPool(j_common_ptr cinfo, int pool_id) { + MemoryManager* mem = reinterpret_cast(cinfo->mem); + mem->owned_ptrs[pool_id].clear(); + mem->total_memory_usage -= mem->pool_memory_usage[pool_id]; + mem->pool_memory_usage[pool_id] = 0; +} + +void FreePool(j_common_ptr cinfo, int pool_id) { + MemoryManager* mem = reinterpret_cast(cinfo->mem); + if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) { + JPEGLI_ERROR("Invalid pool id %d", pool_id); + } + for (void* ptr : mem->owned_ptrs[pool_id]) { + free(ptr); + } + ClearPool(cinfo, pool_id); + for (void* ptr : mem->owned_ptrs[JPOOL_NUMPOOLS + pool_id]) { + hwy::FreeAlignedBytes(ptr, nullptr, nullptr); + } + ClearPool(cinfo, JPOOL_NUMPOOLS + pool_id); +} + +void SelfDestruct(j_common_ptr cinfo) { + MemoryManager* mem = reinterpret_cast(cinfo->mem); + for (int pool_id = 0; pool_id < JPOOL_NUMPOOLS; ++pool_id) { + FreePool(cinfo, pool_id); + } + delete mem; + cinfo->mem = nullptr; +} + +} // namespace + +void InitMemoryManager(j_common_ptr cinfo) { + MemoryManager* mem = new MemoryManager; + mem->pub.alloc_small = jpegli::Alloc; + mem->pub.alloc_large = jpegli::Alloc; + mem->pub.alloc_sarray = jpegli::Alloc2dArray; + mem->pub.alloc_barray = jpegli::Alloc2dArray; + mem->pub.request_virt_sarray = + jpegli::RequestVirtualArray; + mem->pub.request_virt_barray = + jpegli::RequestVirtualArray; + mem->pub.realize_virt_arrays = jpegli::RealizeVirtualArrays; + mem->pub.access_virt_sarray = + jpegli::AccessVirtualArray; + mem->pub.access_virt_barray = + jpegli::AccessVirtualArray; + mem->pub.free_pool = jpegli::FreePool; + mem->pub.self_destruct = jpegli::SelfDestruct; + mem->pub.max_memory_to_use = 0; + mem->total_memory_usage = 0; + mem->peak_memory_usage = 0; + memset(mem->pool_memory_usage, 0, sizeof(mem->pool_memory_usage)); + cinfo->mem = reinterpret_cast(mem); +} + +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/memory_manager.h b/media/libjxl/src/lib/jpegli/memory_manager.h new file mode 100644 index 0000000000..c650caad49 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/memory_manager.h @@ -0,0 +1,46 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_JPEGLI_MEMORY_MANAGER_H_ +#define LIB_JPEGLI_MEMORY_MANAGER_H_ + +#include + +#include "lib/jpegli/common.h" + +#define JPOOL_PERMANENT_ALIGNED (JPOOL_NUMPOOLS + JPOOL_PERMANENT) +#define JPOOL_IMAGE_ALIGNED (JPOOL_NUMPOOLS + JPOOL_IMAGE) + +namespace jpegli { + +void InitMemoryManager(j_common_ptr cinfo); + +template +T* Allocate(j_common_ptr cinfo, size_t len, int pool_id = JPOOL_PERMANENT) { + const size_t size = len * sizeof(T); // NOLINT + void* p = (*cinfo->mem->alloc_small)(cinfo, pool_id, size); + return reinterpret_cast(p); +} + +template +T* Allocate(j_decompress_ptr cinfo, size_t len, int pool_id = JPOOL_PERMANENT) { + return Allocate(reinterpret_cast(cinfo), len, pool_id); +} + +template +T* Allocate(j_compress_ptr cinfo, size_t len, int pool_id = JPOOL_PERMANENT) { + return Allocate(reinterpret_cast(cinfo), len, pool_id); +} + +template +JBLOCKARRAY GetBlockRow(T cinfo, int c, JDIMENSION by) { + return (*cinfo->mem->access_virt_barray)( + reinterpret_cast(cinfo), cinfo->master->coeff_buffers[c], + by, 1, true); +} + +} // namespace jpegli + +#endif // LIB_JPEGLI_MEMORY_MANAGER_H_ diff --git a/media/libjxl/src/lib/jpegli/output_suspension_test.cc b/media/libjxl/src/lib/jpegli/output_suspension_test.cc new file mode 100644 index 0000000000..44d63fdcbb --- /dev/null +++ b/media/libjxl/src/lib/jpegli/output_suspension_test.cc @@ -0,0 +1,231 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/jpegli/encode.h" +#include "lib/jpegli/libjpeg_test_util.h" +#include "lib/jpegli/test_params.h" +#include "lib/jpegli/test_utils.h" +#include "lib/jpegli/testing.h" + +namespace jpegli { +namespace { + +constexpr size_t kInitialBufferSize = 1024; +constexpr size_t kFinalBufferSize = 18; + +struct DestinationManager { + jpeg_destination_mgr pub; + std::vector buffer; + + DestinationManager() { + pub.init_destination = init_destination; + pub.empty_output_buffer = empty_output_buffer; + pub.term_destination = term_destination; + } + + void Rewind() { + pub.next_output_byte = buffer.data(); + pub.free_in_buffer = buffer.size(); + } + + void EmptyTo(std::vector* output, size_t new_size = 0) { + output->insert(output->end(), buffer.data(), pub.next_output_byte); + if (new_size > 0) { + buffer.resize(new_size); + } + Rewind(); + } + + static void init_destination(j_compress_ptr cinfo) { + auto* us = reinterpret_cast(cinfo->dest); + us->buffer.resize(kInitialBufferSize); + us->Rewind(); + } + + static boolean empty_output_buffer(j_compress_ptr cinfo) { return FALSE; } + + static void term_destination(j_compress_ptr cinfo) {} +}; + +struct TestConfig { + TestImage input; + CompressParams jparams; + size_t buffer_size; + size_t lines_batch_size; +}; + +class OutputSuspensionTestParam : public ::testing::TestWithParam { +}; + +TEST_P(OutputSuspensionTestParam, PixelData) { + jpeg_compress_struct cinfo = {}; + TestConfig config = GetParam(); + TestImage& input = config.input; + GeneratePixels(&input); + DestinationManager dest; + std::vector compressed; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + cinfo.dest = reinterpret_cast(&dest); + + cinfo.image_width = input.xsize; + cinfo.image_height = input.ysize; + cinfo.input_components = input.components; + cinfo.in_color_space = JCS_RGB; + jpegli_set_defaults(&cinfo); + cinfo.comp_info[0].v_samp_factor = config.jparams.v_sampling[0]; + jpegli_set_progressive_level(&cinfo, 0); + cinfo.optimize_coding = FALSE; + jpegli_start_compress(&cinfo, TRUE); + + size_t stride = cinfo.image_width * cinfo.input_components; + std::vector row_bytes(config.lines_batch_size * stride); + while (cinfo.next_scanline < cinfo.image_height) { + size_t lines_left = cinfo.image_height - cinfo.next_scanline; + size_t num_lines = std::min(config.lines_batch_size, lines_left); + memcpy(row_bytes.data(), &input.pixels[cinfo.next_scanline * stride], + num_lines * stride); + std::vector rows(num_lines); + for (size_t i = 0; i < num_lines; ++i) { + rows[i] = &row_bytes[i * stride]; + } + size_t lines_done = 0; + while (lines_done < num_lines) { + lines_done += jpegli_write_scanlines(&cinfo, &rows[lines_done], + num_lines - lines_done); + if (lines_done < num_lines) { + dest.EmptyTo(&compressed, config.buffer_size); + } + } + } + dest.EmptyTo(&compressed, kFinalBufferSize); + jpegli_finish_compress(&cinfo); + dest.EmptyTo(&compressed); + return true; + }; + ASSERT_TRUE(try_catch_block()); + jpegli_destroy_compress(&cinfo); + TestImage output; + DecodeWithLibjpeg(CompressParams(), DecompressParams(), compressed, &output); + VerifyOutputImage(input, output, 2.5); +} + +TEST_P(OutputSuspensionTestParam, RawData) { + jpeg_compress_struct cinfo = {}; + TestConfig config = GetParam(); + if (config.lines_batch_size != 1) return; + TestImage& input = config.input; + input.color_space = JCS_YCbCr; + GeneratePixels(&input); + GenerateRawData(config.jparams, &input); + DestinationManager dest; + std::vector compressed; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + cinfo.dest = reinterpret_cast(&dest); + cinfo.image_width = input.xsize; + cinfo.image_height = input.ysize; + cinfo.input_components = input.components; + cinfo.in_color_space = JCS_YCbCr; + jpegli_set_defaults(&cinfo); + cinfo.comp_info[0].h_samp_factor = config.jparams.h_sampling[0]; + cinfo.comp_info[0].v_samp_factor = config.jparams.v_sampling[0]; + jpegli_set_progressive_level(&cinfo, 0); + cinfo.optimize_coding = FALSE; + cinfo.raw_data_in = TRUE; + jpegli_start_compress(&cinfo, TRUE); + + std::vector> raw_data = input.raw_data; + size_t max_lines = config.jparams.max_v_sample() * DCTSIZE; + std::vector> rowdata(cinfo.num_components); + std::vector data(cinfo.num_components); + for (int c = 0; c < cinfo.num_components; ++c) { + rowdata[c].resize(config.jparams.v_samp(c) * DCTSIZE); + data[c] = rowdata[c].data(); + } + while (cinfo.next_scanline < cinfo.image_height) { + for (int c = 0; c < cinfo.num_components; ++c) { + size_t cwidth = cinfo.comp_info[c].width_in_blocks * DCTSIZE; + size_t cheight = cinfo.comp_info[c].height_in_blocks * DCTSIZE; + size_t num_lines = config.jparams.v_samp(c) * DCTSIZE; + size_t y0 = (cinfo.next_scanline / max_lines) * num_lines; + for (size_t i = 0; i < num_lines; ++i) { + rowdata[c][i] = + (y0 + i < cheight ? &raw_data[c][(y0 + i) * cwidth] : nullptr); + } + } + while (jpegli_write_raw_data(&cinfo, data.data(), max_lines) == 0) { + dest.EmptyTo(&compressed, config.buffer_size); + } + } + dest.EmptyTo(&compressed, kFinalBufferSize); + jpegli_finish_compress(&cinfo); + dest.EmptyTo(&compressed); + return true; + }; + try_catch_block(); + jpegli_destroy_compress(&cinfo); + DecompressParams dparams; + dparams.output_mode = RAW_DATA; + TestImage output; + DecodeWithLibjpeg(CompressParams(), dparams, compressed, &output); + VerifyOutputImage(input, output, 3.5); +} + +std::vector GenerateTests() { + std::vector all_tests; + const size_t xsize0 = 1920; + const size_t ysize0 = 1080; + for (int dysize : {0, 1, 8, 9}) { + for (int v_sampling : {1, 2}) { + for (int nlines : {1, 8, 117}) { + for (int bufsize : {1, 16, 16 << 10}) { + TestConfig config; + config.lines_batch_size = nlines; + config.buffer_size = bufsize; + config.input.xsize = xsize0; + config.input.ysize = ysize0 + dysize; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {v_sampling, 1, 1}; + all_tests.push_back(config); + } + } + } + } + return all_tests; +} + +std::ostream& operator<<(std::ostream& os, const TestConfig& c) { + os << c.input; + os << c.jparams; + os << "Lines" << c.lines_batch_size; + os << "BufSize" << c.buffer_size; + return os; +} + +std::string TestDescription( + const testing::TestParamInfo& info) { + std::stringstream name; + name << info.param; + return name.str(); +} + +JPEGLI_INSTANTIATE_TEST_SUITE_P(OutputSuspensionTest, OutputSuspensionTestParam, + testing::ValuesIn(GenerateTests()), + TestDescription); + +} // namespace +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/quant.cc b/media/libjxl/src/lib/jpegli/quant.cc new file mode 100644 index 0000000000..14db6701b2 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/quant.cc @@ -0,0 +1,768 @@ +// 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/jpegli/quant.h" + +#include +#include +#include + +#include "lib/jpegli/adaptive_quantization.h" +#include "lib/jpegli/common.h" +#include "lib/jpegli/encode_internal.h" +#include "lib/jpegli/error.h" +#include "lib/jpegli/memory_manager.h" +#include "lib/jxl/base/byte_order.h" +#include "lib/jxl/base/status.h" + +namespace jpegli { + +namespace { + +// Global scale is chosen in a way that butteraugli 3-norm matches libjpeg +// with the same quality setting. Fitted for quality 90 on jyrki31 corpus. +constexpr float kGlobalScaleXYB = 1.43951668f; +constexpr float kGlobalScaleYCbCr = 1.73966010f; + +constexpr float kBaseQuantMatrixXYB[] = { + // c = 0 + 7.5629935265f, + 19.8247814178f, + 22.5724945068f, + 20.6706695557f, + 22.6864585876f, + 23.5696277618f, + 25.8129081726f, + 36.3307571411f, + 19.8247814178f, + 21.5503177643f, + 19.9372234344f, + 20.5424213409f, + 21.8645496368f, + 23.9041385651f, + 28.2844066620f, + 32.6609764099f, + 22.5724945068f, + 19.9372234344f, + 21.9017257690f, + 19.1223449707f, + 21.7515811920f, + 24.6724700928f, + 25.4249649048f, + 32.6653823853f, + 20.6706695557f, + 20.5424213409f, + 19.1223449707f, + 20.1610221863f, + 25.3719692230f, + 25.9668903351f, + 30.9804954529f, + 31.3406009674f, + 22.6864585876f, + 21.8645496368f, + 21.7515811920f, + 25.3719692230f, + 26.2431850433f, + 40.5992202759f, + 43.2624626160f, + 63.3010940552f, + 23.5696277618f, + 23.9041385651f, + 24.6724700928f, + 25.9668903351f, + 40.5992202759f, + 48.3026771545f, + 34.0964355469f, + 61.9852142334f, + 25.8129081726f, + 28.2844066620f, + 25.4249649048f, + 30.9804954529f, + 43.2624626160f, + 34.0964355469f, + 34.4937438965f, + 66.9702758789f, + 36.3307571411f, + 32.6609764099f, + 32.6653823853f, + 31.3406009674f, + 63.3010940552f, + 61.9852142334f, + 66.9702758789f, + 39.9652709961f, + // c = 1 + 1.6262000799f, + 3.2199242115f, + 3.4903779030f, + 3.9148359299f, + 4.8337211609f, + 4.9108843803f, + 5.3137121201f, + 6.1676793098f, + 3.2199242115f, + 3.4547898769f, + 3.6036829948f, + 4.2652835846f, + 4.8368387222f, + 4.8226222992f, + 5.6120514870f, + 6.3431472778f, + 3.4903779030f, + 3.6036829948f, + 3.9044559002f, + 4.3374395370f, + 4.8435096741f, + 5.4057979584f, + 5.6066360474f, + 6.1075134277f, + 3.9148359299f, + 4.2652835846f, + 4.3374395370f, + 4.6064834595f, + 5.1751475334f, + 5.4013924599f, + 6.0399808884f, + 6.7825231552f, + 4.8337211609f, + 4.8368387222f, + 4.8435096741f, + 5.1751475334f, + 5.3748049736f, + 6.1410837173f, + 7.6529307365f, + 7.5235214233f, + 4.9108843803f, + 4.8226222992f, + 5.4057979584f, + 5.4013924599f, + 6.1410837173f, + 6.3431472778f, + 7.1083049774f, + 7.6008300781f, + 5.3137121201f, + 5.6120514870f, + 5.6066360474f, + 6.0399808884f, + 7.6529307365f, + 7.1083049774f, + 7.0943155289f, + 7.0478363037f, + 6.1676793098f, + 6.3431472778f, + 6.1075134277f, + 6.7825231552f, + 7.5235214233f, + 7.6008300781f, + 7.0478363037f, + 6.9186143875f, + // c = 2 + 3.3038473129f, + 10.0689258575f, + 12.2785224915f, + 14.6041173935f, + 16.2107315063f, + 19.2314529419f, + 28.0129547119f, + 55.6682891846f, + 10.0689258575f, + 11.4085016251f, + 11.3871345520f, + 15.4934167862f, + 16.5364933014f, + 14.9153423309f, + 26.3748722076f, + 40.8614425659f, + 12.2785224915f, + 11.3871345520f, + 17.0886878967f, + 13.9500350952f, + 16.0003223419f, + 28.5660629272f, + 26.2124195099f, + 30.1260128021f, + 14.6041173935f, + 15.4934167862f, + 13.9500350952f, + 21.1235027313f, + 26.1579780579f, + 25.5579223633f, + 40.6859359741f, + 33.8056335449f, + 16.2107315063f, + 16.5364933014f, + 16.0003223419f, + 26.1579780579f, + 26.8042831421f, + 26.1587715149f, + 35.7343978882f, + 43.6857032776f, + 19.2314529419f, + 14.9153423309f, + 28.5660629272f, + 25.5579223633f, + 26.1587715149f, + 34.5418128967f, + 41.3197937012f, + 48.7867660522f, + 28.0129547119f, + 26.3748722076f, + 26.2124195099f, + 40.6859359741f, + 35.7343978882f, + 41.3197937012f, + 47.6329460144f, + 55.3498458862f, + 55.6682891846f, + 40.8614425659f, + 30.1260128021f, + 33.8056335449f, + 43.6857032776f, + 48.7867660522f, + 55.3498458862f, + 63.6065597534f, +}; + +const float kBaseQuantMatrixYCbCr[] = { + // c = 0 + 1.2397409345866273f, // + 1.7227115097630963f, // + 2.9212167156636855f, // + 2.812737435286529f, // + 3.339819711906184f, // + 3.463603762596166f, // + 3.840915217993518f, // + 3.86956f, // + 1.7227115097630963f, // + 2.0928894413636874f, // + 2.8456760904429297f, // + 2.704506820909662f, // + 3.4407673520905337f, // + 3.166232352090534f, // + 4.025208741558432f, // + 4.035324490952577f, // + 2.9212167156636855f, // + 2.8456760904429297f, // + 2.9587403520905338f, // + 3.3862948970669273f, // + 3.619523781336757f, // + 3.9046279999999998f, // + 3.757835838431854f, // + 4.237447515714274f, // + 2.812737435286529f, // + 2.704506820909662f, // + 3.3862948970669273f, // + 3.380058821812233f, // + 4.1679867415584315f, // + 4.805510627261856f, // + 4.784259f, // + 4.605934f, // + 3.339819711906184f, // + 3.4407673520905337f, // + 3.619523781336757f, // + 4.1679867415584315f, // + 4.579851258441568f, // + 4.923237f, // + 5.574107f, // + 5.48533336146308f, // + 3.463603762596166f, // + 3.166232352090534f, // + 3.9046279999999998f, // + 4.805510627261856f, // + 4.923237f, // + 5.43936f, // + 5.093895741558431f, // + 6.0872254423617225f, // + 3.840915217993518f, // + 4.025208741558432f, // + 3.757835838431854f, // + 4.784259f, // + 5.574107f, // + 5.093895741558431f, // + 5.438461f, // + 5.4037359493250845f, // + 3.86956f, // + 4.035324490952577f, // + 4.237447515714274f, // + 4.605934f, // + 5.48533336146308f, // + 6.0872254423617225f, // + 5.4037359493250845f, // + 4.37787101190424f, + // c = 1 + 2.8236197786377537f, // + 6.495639358561486f, // + 9.310489207538302f, // + 10.64747864717083f, // + 11.07419143098738f, // + 17.146390223910462f, // + 18.463982229408998f, // + 29.087001644203088f, // + 6.495639358561486f, // + 8.890103846667353f, // + 8.976895794294748f, // + 13.666270550318826f, // + 16.547071905624193f, // + 16.63871382827686f, // + 26.778396930893695f, // + 21.33034294694781f, // + 9.310489207538302f, // + 8.976895794294748f, // + 11.08737706005991f, // + 18.20548239870446f, // + 19.752481654011646f, // + 23.985660533114896f, // + 102.6457378402362f, // + 24.450989f, // + 10.64747864717083f, // + 13.666270550318826f, // + 18.20548239870446f, // + 18.628012327860365f, // + 16.042509519487183f, // + 25.04918273242625f, // + 25.017140189353015f, // + 35.79788782635831f, // + 11.07419143098738f, // + 16.547071905624193f, // + 19.752481654011646f, // + 16.042509519487183f, // + 19.373482748612577f, // + 14.677529999999999f, // + 19.94695960400931f, // + 51.094112f, // + 17.146390223910462f, // + 16.63871382827686f, // + 23.985660533114896f, // + 25.04918273242625f, // + 14.677529999999999f, // + 31.320412426835304f, // + 46.357234000000005f, // + 67.48111451705412f, // + 18.463982229408998f, // + 26.778396930893695f, // + 102.6457378402362f, // + 25.017140189353015f, // + 19.94695960400931f, // + 46.357234000000005f, // + 61.315764694388044f, // + 88.34665293823721f, // + 29.087001644203088f, // + 21.33034294694781f, // + 24.450989f, // + 35.79788782635831f, // + 51.094112f, // + 67.48111451705412f, // + 88.34665293823721f, // + 112.16099098350989f, + // c = 2 + 2.9217254961255255f, // + 4.497681013199305f, // + 7.356344520940414f, // + 6.583891506504051f, // + 8.535608740100237f, // + 8.799434353234647f, // + 9.188341534163023f, // + 9.482700481227672f, // + 4.497681013199305f, // + 6.309548851989123f, // + 7.024608962670982f, // + 7.156445324163424f, // + 8.049059218663244f, // + 7.0124290657218555f, // + 6.711923184393611f, // + 8.380307846134853f, // + 7.356344520940414f, // + 7.024608962670982f, // + 6.892101177327445f, // + 6.882819916277163f, // + 8.782226090078568f, // + 6.8774750000000004f, // + 7.8858175969577955f, // + 8.67909f, // + 6.583891506504051f, // + 7.156445324163424f, // + 6.882819916277163f, // + 7.003072944847055f, // + 7.7223464701024875f, // + 7.955425720217421f, // + 7.4734110000000005f, // + 8.362933242943903f, // + 8.535608740100237f, // + 8.049059218663244f, // + 8.782226090078568f, // + 7.7223464701024875f, // + 6.778005927001542f, // + 9.484922741558432f, // + 9.043702663686046f, // + 8.053178199770173f, // + 8.799434353234647f, // + 7.0124290657218555f, // + 6.8774750000000004f, // + 7.955425720217421f, // + 9.484922741558432f, // + 8.607606527385098f, // + 9.922697394370815f, // + 64.25135180237939f, // + 9.188341534163023f, // + 6.711923184393611f, // + 7.8858175969577955f, // + 7.4734110000000005f, // + 9.043702663686046f, // + 9.922697394370815f, // + 63.184936549738225f, // + 83.35294340273799f, // + 9.482700481227672f, // + 8.380307846134853f, // + 8.67909f, // + 8.362933242943903f, // + 8.053178199770173f, // + 64.25135180237939f, // + 83.35294340273799f, // + 114.89202448569779f, // +}; + +const float k420GlobalScale = 1.22; +const float k420Rescale[64] = { + 0.4093, 0.3209, 0.3477, 0.3333, 0.3144, 0.2823, 0.3214, 0.3354, // + 0.3209, 0.3111, 0.3489, 0.2801, 0.3059, 0.3119, 0.4135, 0.3445, // + 0.3477, 0.3489, 0.3586, 0.3257, 0.2727, 0.3754, 0.3369, 0.3484, // + 0.3333, 0.2801, 0.3257, 0.3020, 0.3515, 0.3410, 0.3971, 0.3839, // + 0.3144, 0.3059, 0.2727, 0.3515, 0.3105, 0.3397, 0.2716, 0.3836, // + 0.2823, 0.3119, 0.3754, 0.3410, 0.3397, 0.3212, 0.3203, 0.0726, // + 0.3214, 0.4135, 0.3369, 0.3971, 0.2716, 0.3203, 0.0798, 0.0553, // + 0.3354, 0.3445, 0.3484, 0.3839, 0.3836, 0.0726, 0.0553, 0.3368, // +}; + +const float kBaseQuantMatrixStd[] = { + // c = 0 + 16.0f, 11.0f, 10.0f, 16.0f, 24.0f, 40.0f, 51.0f, 61.0f, // + 12.0f, 12.0f, 14.0f, 19.0f, 26.0f, 58.0f, 60.0f, 55.0f, // + 14.0f, 13.0f, 16.0f, 24.0f, 40.0f, 57.0f, 69.0f, 56.0f, // + 14.0f, 17.0f, 22.0f, 29.0f, 51.0f, 87.0f, 80.0f, 62.0f, // + 18.0f, 22.0f, 37.0f, 56.0f, 68.0f, 109.0f, 103.0f, 77.0f, // + 24.0f, 35.0f, 55.0f, 64.0f, 81.0f, 104.0f, 113.0f, 92.0f, // + 49.0f, 64.0f, 78.0f, 87.0f, 103.0f, 121.0f, 120.0f, 101.0f, // + 72.0f, 92.0f, 95.0f, 98.0f, 112.0f, 100.0f, 103.0f, 99.0f, // + // c = 1 + 17.0f, 18.0f, 24.0f, 47.0f, 99.0f, 99.0f, 99.0f, 99.0f, // + 18.0f, 21.0f, 26.0f, 66.0f, 99.0f, 99.0f, 99.0f, 99.0f, // + 24.0f, 26.0f, 56.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, // + 47.0f, 66.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, // + 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, // + 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, // + 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, // + 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, // +}; + +const float kZeroBiasMulYCbCrLQ[] = { + // c = 0 + 0.0000f, 0.0568f, 0.3880f, 0.6190f, 0.6190f, 0.4490f, 0.4490f, 0.6187f, // + 0.0568f, 0.5829f, 0.6189f, 0.6190f, 0.6190f, 0.7190f, 0.6190f, 0.6189f, // + 0.3880f, 0.6189f, 0.6190f, 0.6190f, 0.6190f, 0.6190f, 0.6187f, 0.6100f, // + 0.6190f, 0.6190f, 0.6190f, 0.6190f, 0.5890f, 0.3839f, 0.7160f, 0.6190f, // + 0.6190f, 0.6190f, 0.6190f, 0.5890f, 0.6190f, 0.3880f, 0.5860f, 0.4790f, // + 0.4490f, 0.7190f, 0.6190f, 0.3839f, 0.3880f, 0.6190f, 0.6190f, 0.6190f, // + 0.4490f, 0.6190f, 0.6187f, 0.7160f, 0.5860f, 0.6190f, 0.6204f, 0.6190f, // + 0.6187f, 0.6189f, 0.6100f, 0.6190f, 0.4790f, 0.6190f, 0.6190f, 0.3480f, // + // c = 1 + 0.0000f, 1.1640f, 0.9373f, 1.1319f, 0.8016f, 0.9136f, 1.1530f, 0.9430f, // + 1.1640f, 0.9188f, 0.9160f, 1.1980f, 1.1830f, 0.9758f, 0.9430f, 0.9430f, // + 0.9373f, 0.9160f, 0.8430f, 1.1720f, 0.7083f, 0.9430f, 0.9430f, 0.9430f, // + 1.1319f, 1.1980f, 1.1720f, 1.1490f, 0.8547f, 0.9430f, 0.9430f, 0.9430f, // + 0.8016f, 1.1830f, 0.7083f, 0.8547f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, // + 0.9136f, 0.9758f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, // + 1.1530f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9480f, // + 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9480f, 0.9430f, // + // c = 2 + 0.0000f, 1.3190f, 0.4308f, 0.4460f, 0.0661f, 0.0660f, 0.2660f, 0.2960f, // + 1.3190f, 0.3280f, 0.3093f, 0.0750f, 0.0505f, 0.1594f, 0.3060f, 0.2113f, // + 0.4308f, 0.3093f, 0.3060f, 0.1182f, 0.0500f, 0.3060f, 0.3915f, 0.2426f, // + 0.4460f, 0.0750f, 0.1182f, 0.0512f, 0.0500f, 0.2130f, 0.3930f, 0.1590f, // + 0.0661f, 0.0505f, 0.0500f, 0.0500f, 0.3055f, 0.3360f, 0.5148f, 0.5403f, // + 0.0660f, 0.1594f, 0.3060f, 0.2130f, 0.3360f, 0.5060f, 0.5874f, 0.3060f, // + 0.2660f, 0.3060f, 0.3915f, 0.3930f, 0.5148f, 0.5874f, 0.3060f, 0.3060f, // + 0.2960f, 0.2113f, 0.2426f, 0.1590f, 0.5403f, 0.3060f, 0.3060f, 0.3060f, // +}; + +const float kZeroBiasMulYCbCrHQ[] = { + // c = 0 + 0.0000f, 0.0044f, 0.2521f, 0.6547f, 0.8161f, 0.6130f, 0.8841f, 0.8155f, // + 0.0044f, 0.6831f, 0.6553f, 0.6295f, 0.7848f, 0.7843f, 0.8474f, 0.7836f, // + 0.2521f, 0.6553f, 0.7834f, 0.7829f, 0.8161f, 0.8072f, 0.7743f, 0.9242f, // + 0.6547f, 0.6295f, 0.7829f, 0.8654f, 0.7829f, 0.6986f, 0.7818f, 0.7726f, // + 0.8161f, 0.7848f, 0.8161f, 0.7829f, 0.7471f, 0.7827f, 0.7843f, 0.7653f, // + 0.6130f, 0.7843f, 0.8072f, 0.6986f, 0.7827f, 0.7848f, 0.9508f, 0.7653f, // + 0.8841f, 0.8474f, 0.7743f, 0.7818f, 0.7843f, 0.9508f, 0.7839f, 0.8437f, // + 0.8155f, 0.7836f, 0.9242f, 0.7726f, 0.7653f, 0.7653f, 0.8437f, 0.7819f, // + // c = 1 + 0.0000f, 1.0816f, 1.0556f, 1.2876f, 1.1554f, 1.1567f, 1.8851f, 0.5488f, // + 1.0816f, 1.1537f, 1.1850f, 1.0712f, 1.1671f, 2.0719f, 1.0544f, 1.4764f, // + 1.0556f, 1.1850f, 1.2870f, 1.1981f, 1.8181f, 1.2618f, 1.0564f, 1.1191f, // + 1.2876f, 1.0712f, 1.1981f, 1.4753f, 2.0609f, 1.0564f, 1.2645f, 1.0564f, // + 1.1554f, 1.1671f, 1.8181f, 2.0609f, 0.7324f, 1.1163f, 0.8464f, 1.0564f, // + 1.1567f, 2.0719f, 1.2618f, 1.0564f, 1.1163f, 1.0040f, 1.0564f, 1.0564f, // + 1.8851f, 1.0544f, 1.0564f, 1.2645f, 0.8464f, 1.0564f, 1.0564f, 1.0564f, // + 0.5488f, 1.4764f, 1.1191f, 1.0564f, 1.0564f, 1.0564f, 1.0564f, 1.0564f, // + // c = 2 + 0.0000f, 0.5392f, 0.6659f, 0.8968f, 0.6829f, 0.6328f, 0.5802f, 0.4836f, // + 0.5392f, 0.6746f, 0.6760f, 0.6102f, 0.6015f, 0.6958f, 0.7327f, 0.4897f, // + 0.6659f, 0.6760f, 0.6957f, 0.6543f, 0.4396f, 0.6330f, 0.7081f, 0.2583f, // + 0.8968f, 0.6102f, 0.6543f, 0.5913f, 0.6457f, 0.5828f, 0.5139f, 0.3565f, // + 0.6829f, 0.6015f, 0.4396f, 0.6457f, 0.5633f, 0.4263f, 0.6371f, 0.5949f, // + 0.6328f, 0.6958f, 0.6330f, 0.5828f, 0.4263f, 0.2847f, 0.2909f, 0.6629f, // + 0.5802f, 0.7327f, 0.7081f, 0.5139f, 0.6371f, 0.2909f, 0.6644f, 0.6644f, // + 0.4836f, 0.4897f, 0.2583f, 0.3565f, 0.5949f, 0.6629f, 0.6644f, 0.6644f, // +}; + +const float kZeroBiasOffsetYCbCrDC[] = {0.0f, 0.0f, 0.0f}; + +const float kZeroBiasOffsetYCbCrAC[] = { + 0.59082f, + 0.58146f, + 0.57988f, +}; + +constexpr uint8_t kTransferFunctionPQ = 16; +constexpr uint8_t kTransferFunctionHLG = 18; + +float DistanceToLinearQuality(float distance) { + if (distance <= 0.1f) { + return 1.0f; + } else if (distance <= 4.6f) { + return (200.0f / 9.0f) * (distance - 0.1f); + } else if (distance <= 6.4f) { + return 5000.0f / (100.0f - (distance - 0.1f) / 0.09f); + } else if (distance < 25.0f) { + return 530000.0f / + (3450.0f - + 300.0f * std::sqrt((848.0f * distance - 5330.0f) / 120.0f)); + } else { + return 5000.0f; + } +} + +constexpr float kExponent[DCTSIZE2] = { + 1.00f, 0.51f, 0.67f, 0.74f, 1.00f, 1.00f, 1.00f, 1.00f, // + 0.51f, 0.66f, 0.69f, 0.87f, 1.00f, 1.00f, 1.00f, 1.00f, // + 0.67f, 0.69f, 0.84f, 0.83f, 0.96f, 1.00f, 1.00f, 1.00f, // + 0.74f, 0.87f, 0.83f, 1.00f, 1.00f, 0.91f, 0.91f, 1.00f, // + 1.00f, 1.00f, 0.96f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, // + 1.00f, 1.00f, 1.00f, 0.91f, 1.00f, 1.00f, 1.00f, 1.00f, // + 1.00f, 1.00f, 1.00f, 0.91f, 1.00f, 1.00f, 1.00f, 1.00f, // + 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, // +}; +constexpr float kDist0 = 1.5f; // distance where non-linearity kicks in. + +float DistanceToScale(float distance, int k) { + if (distance < kDist0) { + return distance; + } + const float exp = kExponent[k]; + const float mul = std::pow(kDist0, 1.0 - exp); + return std::max(0.5f * distance, mul * std::pow(distance, exp)); +} + +float ScaleToDistance(float scale, int k) { + if (scale < kDist0) { + return scale; + } + const float exp = 1.0 / kExponent[k]; + const float mul = std::pow(kDist0, 1.0 - exp); + return std::min(2.0f * scale, mul * std::pow(scale, exp)); +} + +float QuantValsToDistance(j_compress_ptr cinfo) { + jpeg_comp_master* m = cinfo->master; + float global_scale = kGlobalScaleYCbCr; + if (m->cicp_transfer_function == kTransferFunctionPQ) { + global_scale *= .4f; + } else if (m->cicp_transfer_function == kTransferFunctionHLG) { + global_scale *= .5f; + } + int quant_max = m->force_baseline ? 255 : 32767U; + static const float kDistMax = 10000.0f; + float dist_min = 0.0f; + float dist_max = kDistMax; + for (int c = 0; c < cinfo->num_components; ++c) { + int quant_idx = cinfo->comp_info[c].quant_tbl_no; + uint16_t* quantval = cinfo->quant_tbl_ptrs[quant_idx]->quantval; + const float* base_qm = &kBaseQuantMatrixYCbCr[quant_idx * DCTSIZE2]; + for (int k = 0; k < DCTSIZE2; ++k) { + float dmin = 0.0; + float dmax = kDistMax; + float invq = 1.0f / base_qm[k] / global_scale; + int qval = quantval[k]; + if (qval > 1) { + float scale_min = (qval - 0.5f) * invq; + dmin = ScaleToDistance(scale_min, k); + } + if (qval < quant_max) { + float scale_max = (qval + 0.5f) * invq; + dmax = ScaleToDistance(scale_max, k); + } + if (dmin <= dist_max) { + dist_min = std::max(dmin, dist_min); + } + if (dmax >= dist_min) { + dist_max = std::min(dist_max, dmax); + } + } + } + float distance; + if (dist_min == 0) { + distance = dist_max; + } else if (dist_max == kDistMax) { + distance = dist_min; + } else { + distance = 0.5f * (dist_min + dist_max); + } + return distance; +} + +bool IsYUV420(j_compress_ptr cinfo) { + return (cinfo->jpeg_color_space == JCS_YCbCr && + cinfo->comp_info[0].h_samp_factor == 2 && + cinfo->comp_info[0].v_samp_factor == 2 && + cinfo->comp_info[1].h_samp_factor == 1 && + cinfo->comp_info[1].v_samp_factor == 1 && + cinfo->comp_info[2].h_samp_factor == 1 && + cinfo->comp_info[2].v_samp_factor == 1); +} + +} // namespace + +void SetQuantMatrices(j_compress_ptr cinfo, float distances[NUM_QUANT_TBLS], + bool add_two_chroma_tables) { + jpeg_comp_master* m = cinfo->master; + const bool xyb = m->xyb_mode && cinfo->jpeg_color_space == JCS_RGB; + const bool is_yuv420 = IsYUV420(cinfo); + + float global_scale; + bool non_linear_scaling = true; + const float* base_quant_matrix[NUM_QUANT_TBLS]; + int num_base_tables; + + if (xyb) { + global_scale = kGlobalScaleXYB; + num_base_tables = 3; + base_quant_matrix[0] = kBaseQuantMatrixXYB; + base_quant_matrix[1] = kBaseQuantMatrixXYB + DCTSIZE2; + base_quant_matrix[2] = kBaseQuantMatrixXYB + 2 * DCTSIZE2; + } else if (cinfo->jpeg_color_space == JCS_YCbCr && !m->use_std_tables) { + global_scale = kGlobalScaleYCbCr; + if (m->cicp_transfer_function == kTransferFunctionPQ) { + global_scale *= .4f; + } else if (m->cicp_transfer_function == kTransferFunctionHLG) { + global_scale *= .5f; + } + if (is_yuv420) { + global_scale *= k420GlobalScale; + } + if (add_two_chroma_tables) { + cinfo->comp_info[2].quant_tbl_no = 2; + num_base_tables = 3; + base_quant_matrix[0] = kBaseQuantMatrixYCbCr; + base_quant_matrix[1] = kBaseQuantMatrixYCbCr + DCTSIZE2; + base_quant_matrix[2] = kBaseQuantMatrixYCbCr + 2 * DCTSIZE2; + } else { + num_base_tables = 2; + base_quant_matrix[0] = kBaseQuantMatrixYCbCr; + // Use the Cr table for both Cb and Cr. + base_quant_matrix[1] = kBaseQuantMatrixYCbCr + 2 * DCTSIZE2; + } + } else { + global_scale = 0.01f; + non_linear_scaling = false; + num_base_tables = 2; + base_quant_matrix[0] = kBaseQuantMatrixStd; + base_quant_matrix[1] = kBaseQuantMatrixStd + DCTSIZE2; + } + + int quant_max = m->force_baseline ? 255 : 32767U; + for (int quant_idx = 0; quant_idx < num_base_tables; ++quant_idx) { + const float* base_qm = base_quant_matrix[quant_idx]; + JQUANT_TBL** qtable = &cinfo->quant_tbl_ptrs[quant_idx]; + if (*qtable == nullptr) { + *qtable = jpegli_alloc_quant_table(reinterpret_cast(cinfo)); + } + for (int k = 0; k < DCTSIZE2; ++k) { + float scale = global_scale; + if (non_linear_scaling) { + scale *= DistanceToScale(distances[quant_idx], k); + if (is_yuv420 && quant_idx > 0) { + scale *= k420Rescale[k]; + } + } else { + scale *= DistanceToLinearQuality(distances[quant_idx]); + } + int qval = std::round(scale * base_qm[k]); + (*qtable)->quantval[k] = std::max(1, std::min(qval, quant_max)); + } + (*qtable)->sent_table = FALSE; + } +} + +void InitQuantizer(j_compress_ptr cinfo, QuantPass pass) { + jpeg_comp_master* m = cinfo->master; + // Compute quantization multupliers from the quant table values. + for (int c = 0; c < cinfo->num_components; ++c) { + int quant_idx = cinfo->comp_info[c].quant_tbl_no; + JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[quant_idx]; + if (!quant_table) { + JPEGLI_ERROR("Missing quantization table %d for component %d", quant_idx, + c); + } + for (size_t k = 0; k < DCTSIZE2; k++) { + int val = quant_table->quantval[k]; + if (val == 0) { + JPEGLI_ERROR("Invalid quantval 0."); + } + switch (pass) { + case QuantPass::NO_SEARCH: + m->quant_mul[c][k] = 8.0f / val; + break; + case QuantPass::SEARCH_FIRST_PASS: + m->quant_mul[c][k] = 128.0f; + break; + case QuantPass::SEARCH_SECOND_PASS: + m->quant_mul[c][kJPEGZigZagOrder[k]] = 1.0f / (16 * val); + break; + } + } + } + if (m->use_adaptive_quantization) { + for (int c = 0; c < cinfo->num_components; ++c) { + for (int k = 0; k < DCTSIZE2; ++k) { + m->zero_bias_mul[c][k] = k == 0 ? 0.0f : 0.5f; + m->zero_bias_offset[c][k] = k == 0 ? 0.0f : 0.5f; + } + } + if (cinfo->jpeg_color_space == JCS_YCbCr) { + float distance = QuantValsToDistance(cinfo); + static const float kDistHQ = 1.0f; + static const float kDistLQ = 3.0f; + float mix0 = (distance - kDistHQ) / (kDistLQ - kDistHQ); + mix0 = std::max(0.0f, std::min(1.0f, mix0)); + float mix1 = 1.0f - mix0; + for (int c = 0; c < cinfo->num_components; ++c) { + for (int k = 0; k < DCTSIZE2; ++k) { + float mul0 = kZeroBiasMulYCbCrLQ[c * DCTSIZE2 + k]; + float mul1 = kZeroBiasMulYCbCrHQ[c * DCTSIZE2 + k]; + m->zero_bias_mul[c][k] = mix0 * mul0 + mix1 * mul1; + m->zero_bias_offset[c][k] = + k == 0 ? kZeroBiasOffsetYCbCrDC[c] : kZeroBiasOffsetYCbCrAC[c]; + } + } + } + } else if (cinfo->jpeg_color_space == JCS_YCbCr) { + for (int c = 0; c < cinfo->num_components; ++c) { + for (int k = 0; k < DCTSIZE2; ++k) { + m->zero_bias_offset[c][k] = + k == 0 ? kZeroBiasOffsetYCbCrDC[c] : kZeroBiasOffsetYCbCrAC[c]; + } + } + } +} + +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/quant.h b/media/libjxl/src/lib/jpegli/quant.h new file mode 100644 index 0000000000..cb37757ae2 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/quant.h @@ -0,0 +1,26 @@ +// 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_JPEGLI_QUANT_H_ +#define LIB_JPEGLI_QUANT_H_ + +#include "lib/jpegli/common.h" + +namespace jpegli { + +void SetQuantMatrices(j_compress_ptr cinfo, float distances[NUM_QUANT_TBLS], + bool add_two_chroma_tables); + +enum QuantPass { + NO_SEARCH, + SEARCH_FIRST_PASS, + SEARCH_SECOND_PASS, +}; + +void InitQuantizer(j_compress_ptr cinfo, QuantPass pass); + +} // namespace jpegli + +#endif // LIB_JPEGLI_QUANT_H_ diff --git a/media/libjxl/src/lib/jpegli/render.cc b/media/libjxl/src/lib/jpegli/render.cc new file mode 100644 index 0000000000..1510401fa7 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/render.cc @@ -0,0 +1,761 @@ +// 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/jpegli/render.h" + +#include +#include +#include +#include +#include +#include + +#include "lib/jpegli/color_quantize.h" +#include "lib/jpegli/color_transform.h" +#include "lib/jpegli/decode_internal.h" +#include "lib/jpegli/error.h" +#include "lib/jpegli/idct.h" +#include "lib/jpegli/upsample.h" +#include "lib/jxl/base/byte_order.h" +#include "lib/jxl/base/compiler_specific.h" + +#ifdef MEMORY_SANITIZER +#define JXL_MEMORY_SANITIZER 1 +#elif defined(__has_feature) +#if __has_feature(memory_sanitizer) +#define JXL_MEMORY_SANITIZER 1 +#else +#define JXL_MEMORY_SANITIZER 0 +#endif +#else +#define JXL_MEMORY_SANITIZER 0 +#endif + +#if JXL_MEMORY_SANITIZER +#include "sanitizer/msan_interface.h" +#endif + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jpegli/render.cc" +#include +#include + +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { + +// These templates are not found via ADL. +using hwy::HWY_NAMESPACE::Abs; +using hwy::HWY_NAMESPACE::Add; +using hwy::HWY_NAMESPACE::Clamp; +using hwy::HWY_NAMESPACE::Gt; +using hwy::HWY_NAMESPACE::IfThenElseZero; +using hwy::HWY_NAMESPACE::Mul; +using hwy::HWY_NAMESPACE::NearestInt; +using hwy::HWY_NAMESPACE::Or; +using hwy::HWY_NAMESPACE::Rebind; +using hwy::HWY_NAMESPACE::ShiftLeftSame; +using hwy::HWY_NAMESPACE::ShiftRightSame; +using hwy::HWY_NAMESPACE::Vec; +using D = HWY_FULL(float); +using DI = HWY_FULL(int32_t); +constexpr D d; +constexpr DI di; + +void GatherBlockStats(const int16_t* JXL_RESTRICT coeffs, + const size_t coeffs_size, int32_t* JXL_RESTRICT nonzeros, + int32_t* JXL_RESTRICT sumabs) { + for (size_t i = 0; i < coeffs_size; i += Lanes(d)) { + size_t k = i % DCTSIZE2; + const Rebind di16; + const Vec coeff = PromoteTo(di, Load(di16, coeffs + i)); + const auto abs_coeff = Abs(coeff); + const auto not_0 = Gt(abs_coeff, Zero(di)); + const auto nzero = IfThenElseZero(not_0, Set(di, 1)); + Store(Add(nzero, Load(di, nonzeros + k)), di, nonzeros + k); + Store(Add(abs_coeff, Load(di, sumabs + k)), di, sumabs + k); + } +} + +void DecenterRow(float* row, size_t xsize) { + const HWY_CAPPED(float, 8) df; + const auto c128 = Set(df, 128.0f / 255); + for (size_t x = 0; x < xsize; x += Lanes(df)) { + Store(Add(Load(df, row + x), c128), df, row + x); + } +} + +void DitherRow(j_decompress_ptr cinfo, float* row, int c, size_t y, + size_t xsize) { + jpeg_decomp_master* m = cinfo->master; + if (!m->dither_[c]) return; + const float* dither_row = + &m->dither_[c][(y & m->dither_mask_) * m->dither_size_]; + for (size_t x = 0; x < xsize; ++x) { + row[x] += dither_row[x & m->dither_mask_]; + } +} + +template +void StoreUnsignedRow(float* JXL_RESTRICT input[], size_t x0, size_t len, + size_t num_channels, float multiplier, T* output) { + const HWY_CAPPED(float, 8) d; + auto zero = Zero(d); + auto mul = Set(d, multiplier); + const Rebind du; +#if JXL_MEMORY_SANITIZER + const size_t padding = hwy::RoundUpTo(len, Lanes(d)) - len; + for (size_t c = 0; c < num_channels; ++c) { + __msan_unpoison(input[c] + x0 + len, sizeof(input[c][0]) * padding); + } +#endif + if (num_channels == 1) { + for (size_t i = 0; i < len; i += Lanes(d)) { + auto v0 = Clamp(zero, Mul(LoadU(d, &input[0][x0 + i]), mul), mul); + StoreU(DemoteTo(du, NearestInt(v0)), du, &output[i]); + } + } else if (num_channels == 2) { + for (size_t i = 0; i < len; i += Lanes(d)) { + auto v0 = Clamp(zero, Mul(LoadU(d, &input[0][x0 + i]), mul), mul); + auto v1 = Clamp(zero, Mul(LoadU(d, &input[1][x0 + i]), mul), mul); + StoreInterleaved2(DemoteTo(du, NearestInt(v0)), + DemoteTo(du, NearestInt(v1)), du, &output[2 * i]); + } + } else if (num_channels == 3) { + for (size_t i = 0; i < len; i += Lanes(d)) { + auto v0 = Clamp(zero, Mul(LoadU(d, &input[0][x0 + i]), mul), mul); + auto v1 = Clamp(zero, Mul(LoadU(d, &input[1][x0 + i]), mul), mul); + auto v2 = Clamp(zero, Mul(LoadU(d, &input[2][x0 + i]), mul), mul); + StoreInterleaved3(DemoteTo(du, NearestInt(v0)), + DemoteTo(du, NearestInt(v1)), + DemoteTo(du, NearestInt(v2)), du, &output[3 * i]); + } + } else if (num_channels == 4) { + for (size_t i = 0; i < len; i += Lanes(d)) { + auto v0 = Clamp(zero, Mul(LoadU(d, &input[0][x0 + i]), mul), mul); + auto v1 = Clamp(zero, Mul(LoadU(d, &input[1][x0 + i]), mul), mul); + auto v2 = Clamp(zero, Mul(LoadU(d, &input[2][x0 + i]), mul), mul); + auto v3 = Clamp(zero, Mul(LoadU(d, &input[3][x0 + i]), mul), mul); + StoreInterleaved4(DemoteTo(du, NearestInt(v0)), + DemoteTo(du, NearestInt(v1)), + DemoteTo(du, NearestInt(v2)), + DemoteTo(du, NearestInt(v3)), du, &output[4 * i]); + } + } +#if JXL_MEMORY_SANITIZER + __msan_poison(output + num_channels * len, + sizeof(output[0]) * num_channels * padding); +#endif +} + +void StoreFloatRow(float* JXL_RESTRICT input[3], size_t x0, size_t len, + size_t num_channels, float* output) { + const HWY_CAPPED(float, 8) d; + if (num_channels == 1) { + memcpy(output, input[0] + x0, len * sizeof(output[0])); + } else if (num_channels == 2) { + for (size_t i = 0; i < len; i += Lanes(d)) { + StoreInterleaved2(LoadU(d, &input[0][x0 + i]), + LoadU(d, &input[1][x0 + i]), d, &output[2 * i]); + } + } else if (num_channels == 3) { + for (size_t i = 0; i < len; i += Lanes(d)) { + StoreInterleaved3(LoadU(d, &input[0][x0 + i]), + LoadU(d, &input[1][x0 + i]), + LoadU(d, &input[2][x0 + i]), d, &output[3 * i]); + } + } else if (num_channels == 4) { + for (size_t i = 0; i < len; i += Lanes(d)) { + StoreInterleaved4(LoadU(d, &input[0][x0 + i]), + LoadU(d, &input[1][x0 + i]), + LoadU(d, &input[2][x0 + i]), + LoadU(d, &input[3][x0 + i]), d, &output[4 * i]); + } + } +} + +static constexpr float kFSWeightMR = 7.0f / 16.0f; +static constexpr float kFSWeightBL = 3.0f / 16.0f; +static constexpr float kFSWeightBM = 5.0f / 16.0f; +static constexpr float kFSWeightBR = 1.0f / 16.0f; + +float LimitError(float error) { + float abserror = std::abs(error); + if (abserror > 48.0f) { + abserror = 32.0f; + } else if (abserror > 16.0f) { + abserror = 0.5f * abserror + 8.0f; + } + return error > 0.0f ? abserror : -abserror; +} + +void WriteToOutput(j_decompress_ptr cinfo, float* JXL_RESTRICT rows[], + size_t xoffset, size_t len, size_t num_channels, + uint8_t* JXL_RESTRICT output) { + jpeg_decomp_master* m = cinfo->master; + uint8_t* JXL_RESTRICT scratch_space = m->output_scratch_; + if (cinfo->quantize_colors && m->quant_pass_ == 1) { + float* error_row[kMaxComponents]; + float* next_error_row[kMaxComponents]; + J_DITHER_MODE dither_mode = cinfo->dither_mode; + if (dither_mode == JDITHER_ORDERED) { + for (size_t c = 0; c < num_channels; ++c) { + DitherRow(cinfo, &rows[c][xoffset], c, cinfo->output_scanline, + cinfo->output_width); + } + } else if (dither_mode == JDITHER_FS) { + for (size_t c = 0; c < num_channels; ++c) { + if (cinfo->output_scanline % 2 == 0) { + error_row[c] = m->error_row_[c]; + next_error_row[c] = m->error_row_[c + kMaxComponents]; + } else { + error_row[c] = m->error_row_[c + kMaxComponents]; + next_error_row[c] = m->error_row_[c]; + } + memset(next_error_row[c], 0.0, cinfo->output_width * sizeof(float)); + } + } + const float mul = 255.0f; + if (dither_mode != JDITHER_FS) { + StoreUnsignedRow(rows, xoffset, len, num_channels, mul, scratch_space); + } + for (size_t i = 0; i < len; ++i) { + uint8_t* pixel = &scratch_space[num_channels * i]; + if (dither_mode == JDITHER_FS) { + for (size_t c = 0; c < num_channels; ++c) { + float val = rows[c][i] * mul + LimitError(error_row[c][i]); + pixel[c] = std::round(std::min(255.0f, std::max(0.0f, val))); + } + } + int index = LookupColorIndex(cinfo, pixel); + output[i] = index; + if (dither_mode == JDITHER_FS) { + size_t prev_i = i > 0 ? i - 1 : 0; + size_t next_i = i + 1 < len ? i + 1 : len - 1; + for (size_t c = 0; c < num_channels; ++c) { + float error = pixel[c] - cinfo->colormap[c][index]; + error_row[c][next_i] += kFSWeightMR * error; + next_error_row[c][prev_i] += kFSWeightBL * error; + next_error_row[c][i] += kFSWeightBM * error; + next_error_row[c][next_i] += kFSWeightBR * error; + } + } + } + } else if (m->output_data_type_ == JPEGLI_TYPE_UINT8) { + const float mul = 255.0; + StoreUnsignedRow(rows, xoffset, len, num_channels, mul, scratch_space); + memcpy(output, scratch_space, len * num_channels); + } else if (m->output_data_type_ == JPEGLI_TYPE_UINT16) { + const float mul = 65535.0; + uint16_t* tmp = reinterpret_cast(scratch_space); + StoreUnsignedRow(rows, xoffset, len, num_channels, mul, tmp); + if (m->swap_endianness_) { + const HWY_CAPPED(uint16_t, 8) du; + size_t output_len = len * num_channels; + for (size_t j = 0; j < output_len; j += Lanes(du)) { + auto v = LoadU(du, tmp + j); + auto vswap = Or(ShiftRightSame(v, 8), ShiftLeftSame(v, 8)); + StoreU(vswap, du, tmp + j); + } + } + memcpy(output, tmp, len * num_channels * 2); + } else if (m->output_data_type_ == JPEGLI_TYPE_FLOAT) { + float* tmp = reinterpret_cast(scratch_space); + StoreFloatRow(rows, xoffset, len, num_channels, tmp); + if (m->swap_endianness_) { + size_t output_len = len * num_channels; + for (size_t j = 0; j < output_len; ++j) { + tmp[j] = BSwapFloat(tmp[j]); + } + } + memcpy(output, tmp, len * num_channels * 4); + } +} + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE + +namespace jpegli { + +HWY_EXPORT(GatherBlockStats); +HWY_EXPORT(WriteToOutput); +HWY_EXPORT(DecenterRow); + +void GatherBlockStats(const int16_t* JXL_RESTRICT coeffs, + const size_t coeffs_size, int32_t* JXL_RESTRICT nonzeros, + int32_t* JXL_RESTRICT sumabs) { + HWY_DYNAMIC_DISPATCH(GatherBlockStats)(coeffs, coeffs_size, nonzeros, sumabs); +} + +void WriteToOutput(j_decompress_ptr cinfo, float* JXL_RESTRICT rows[], + size_t xoffset, size_t len, size_t num_channels, + uint8_t* JXL_RESTRICT output) { + HWY_DYNAMIC_DISPATCH(WriteToOutput) + (cinfo, rows, xoffset, len, num_channels, output); +} + +void DecenterRow(float* row, size_t xsize) { + HWY_DYNAMIC_DISPATCH(DecenterRow)(row, xsize); +} + +bool ShouldApplyDequantBiases(j_decompress_ptr cinfo, int ci) { + const auto& compinfo = cinfo->comp_info[ci]; + return (compinfo.h_samp_factor == cinfo->max_h_samp_factor && + compinfo.v_samp_factor == cinfo->max_v_samp_factor); +} + +// See the following article for the details: +// J. R. Price and M. Rabbani, "Dequantization bias for JPEG decompression" +// Proceedings International Conference on Information Technology: Coding and +// Computing (Cat. No.PR00540), 2000, pp. 30-35, doi: 10.1109/ITCC.2000.844179. +void ComputeOptimalLaplacianBiases(const int num_blocks, const int* nonzeros, + const int* sumabs, float* biases) { + for (size_t k = 1; k < DCTSIZE2; ++k) { + if (nonzeros[k] == 0) { + biases[k] = 0.5f; + continue; + } + // Notation adapted from the article + float N = num_blocks; + float N1 = nonzeros[k]; + float N0 = num_blocks - N1; + float S = sumabs[k]; + // Compute gamma from N0, N1, N, S (eq. 11), with A and B being just + // temporary grouping of terms. + float A = 4.0 * S + 2.0 * N; + float B = 4.0 * S - 2.0 * N1; + float gamma = (-1.0 * N0 + std::sqrt(N0 * N0 * 1.0 + A * B)) / A; + float gamma2 = gamma * gamma; + // The bias is computed from gamma with (eq. 5), where the quantization + // multiplier Q can be factored out and thus the bias can be applied + // directly on the quantized coefficient. + biases[k] = + 0.5 * (((1.0 + gamma2) / (1.0 - gamma2)) + 1.0 / std::log(gamma)); + } +} + +constexpr std::array Q_POS = {0, 1, 8, 16, 9, + 2, 3, 10, 17, 24}; + +bool is_nonzero_quantizers(const JQUANT_TBL* qtable) { + return std::all_of(Q_POS.begin(), Q_POS.end(), + [&](int pos) { return qtable->quantval[pos] != 0; }); +} + +// Determine whether smoothing should be applied during decompression +bool do_smoothing(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + bool smoothing_useful = false; + + if (!cinfo->progressive_mode || cinfo->coef_bits == nullptr) { + return false; + } + auto* coef_bits_latch = m->coef_bits_latch; + auto* prev_coef_bits_latch = m->prev_coef_bits_latch; + + for (int ci = 0; ci < cinfo->num_components; ci++) { + jpeg_component_info* compptr = &cinfo->comp_info[ci]; + JQUANT_TBL* qtable = compptr->quant_table; + int* coef_bits = cinfo->coef_bits[ci]; + int* prev_coef_bits = cinfo->coef_bits[ci + cinfo->num_components]; + + // Return early if conditions for smoothing are not met + if (qtable == nullptr || !is_nonzero_quantizers(qtable) || + coef_bits[0] < 0) { + return false; + } + + coef_bits_latch[ci][0] = coef_bits[0]; + + for (int coefi = 1; coefi < SAVED_COEFS; coefi++) { + prev_coef_bits_latch[ci][coefi] = + cinfo->input_scan_number > 1 ? prev_coef_bits[coefi] : -1; + if (coef_bits[coefi] != 0) { + smoothing_useful = true; + } + coef_bits_latch[ci][coefi] = coef_bits[coefi]; + } + } + + return smoothing_useful; +} + +void PredictSmooth(j_decompress_ptr cinfo, JBLOCKARRAY blocks, int component, + size_t bx, int iy) { + const size_t imcu_row = cinfo->output_iMCU_row; + int16_t* scratch = cinfo->master->smoothing_scratch_; + std::vector Q_VAL(SAVED_COEFS); + int* coef_bits; + + std::array, 5> dc_values; + auto& compinfo = cinfo->comp_info[component]; + const size_t by0 = imcu_row * compinfo.v_samp_factor; + const size_t by = by0 + iy; + + int prev_iy = by > 0 ? iy - 1 : 0; + int prev_prev_iy = by > 1 ? iy - 2 : prev_iy; + int next_iy = by + 1 < compinfo.height_in_blocks ? iy + 1 : iy; + int next_next_iy = by + 2 < compinfo.height_in_blocks ? iy + 2 : next_iy; + + const int16_t* cur_row = blocks[iy][bx]; + const int16_t* prev_row = blocks[prev_iy][bx]; + const int16_t* prev_prev_row = blocks[prev_prev_iy][bx]; + const int16_t* next_row = blocks[next_iy][bx]; + const int16_t* next_next_row = blocks[next_next_iy][bx]; + + int prev_block_ind = bx ? -DCTSIZE2 : 0; + int prev_prev_block_ind = bx > 1 ? -2 * DCTSIZE2 : prev_block_ind; + int next_block_ind = bx + 1 < compinfo.width_in_blocks ? DCTSIZE2 : 0; + int next_next_block_ind = + bx + 2 < compinfo.width_in_blocks ? DCTSIZE2 * 2 : next_block_ind; + + std::array row_ptrs = {prev_prev_row, prev_row, cur_row, + next_row, next_next_row}; + std::array block_inds = {prev_prev_block_ind, prev_block_ind, 0, + next_block_ind, next_next_block_ind}; + + memcpy(scratch, cur_row, DCTSIZE2 * sizeof(cur_row[0])); + + for (int r = 0; r < 5; ++r) { + for (int c = 0; c < 5; ++c) { + dc_values[r][c] = row_ptrs[r][block_inds[c]]; + } + } + // Get the correct coef_bits: In case of an incomplete scan, we use the + // prev coefficients. + if (cinfo->output_iMCU_row + 1 > cinfo->input_iMCU_row) { + coef_bits = cinfo->master->prev_coef_bits_latch[component]; + } else { + coef_bits = cinfo->master->coef_bits_latch[component]; + } + + bool change_dc = true; + for (int i = 1; i < SAVED_COEFS; i++) { + if (coef_bits[i] != -1) { + change_dc = false; + break; + } + } + + JQUANT_TBL* quanttbl = cinfo->quant_tbl_ptrs[compinfo.quant_tbl_no]; + for (size_t i = 0; i < 6; ++i) { + Q_VAL[i] = quanttbl->quantval[Q_POS[i]]; + } + if (change_dc) { + for (size_t i = 6; i < SAVED_COEFS; ++i) { + Q_VAL[i] = quanttbl->quantval[Q_POS[i]]; + } + } + auto calculate_dct_value = [&](int coef_index) { + int64_t num = 0; + int pred; + int Al; + // we use the symmetry of the smoothing matrices by transposing the 5x5 dc + // matrix in that case. + bool swap_indices = coef_index == 2 || coef_index == 5 || coef_index == 8 || + coef_index == 9; + auto dc = [&](int i, int j) { + return swap_indices ? dc_values[j][i] : dc_values[i][j]; + }; + JPEGLI_CHECK(coef_index >= 0 && coef_index < 10); + Al = coef_bits[coef_index]; + switch (coef_index) { + case 0: + // set the DC + num = (-2 * dc(0, 0) - 6 * dc(0, 1) - 8 * dc(0, 2) - 6 * dc(0, 3) - + 2 * dc(0, 4) - 6 * dc(1, 0) + 6 * dc(1, 1) + 42 * dc(1, 2) + + 6 * dc(1, 3) - 6 * dc(1, 4) - 8 * dc(2, 0) + 42 * dc(2, 1) + + 152 * dc(2, 2) + 42 * dc(2, 3) - 8 * dc(2, 4) - 6 * dc(3, 0) + + 6 * dc(3, 1) + 42 * dc(3, 2) + 6 * dc(3, 3) - 6 * dc(3, 4) - + 2 * dc(4, 0) - 6 * dc(4, 1) - 8 * dc(4, 2) - 6 * dc(4, 3) - + 2 * dc(4, 4)); + // special case: for the DC the dequantization is different + Al = 0; + break; + case 1: + case 2: + // set Q01 or Q10 + num = (change_dc ? (-dc(0, 0) - dc(0, 1) + dc(0, 3) + dc(0, 4) - + 3 * dc(1, 0) + 13 * dc(1, 1) - 13 * dc(1, 3) + + 3 * dc(1, 4) - 3 * dc(2, 0) + 38 * dc(2, 1) - + 38 * dc(2, 3) + 3 * dc(2, 4) - 3 * dc(3, 0) + + 13 * dc(3, 1) - 13 * dc(3, 3) + 3 * dc(3, 4) - + dc(4, 0) - dc(4, 1) + dc(4, 3) + dc(4, 4)) + : (-7 * dc(2, 0) + 50 * dc(2, 1) - 50 * dc(2, 3) + + 7 * dc(2, 4))); + break; + case 3: + case 5: + // set Q02 or Q20 + num = (change_dc + ? dc(0, 2) + 2 * dc(1, 1) + 7 * dc(1, 2) + 2 * dc(1, 3) - + 5 * dc(2, 1) - 14 * dc(2, 2) - 5 * dc(2, 3) + + 2 * dc(3, 1) + 7 * dc(3, 2) + 2 * dc(3, 3) + dc(4, 2) + : (-dc(0, 2) + 13 * dc(1, 2) - 24 * dc(2, 2) + + 13 * dc(3, 2) - dc(4, 2))); + break; + case 4: + // set Q11 + num = + (change_dc ? -dc(0, 0) + dc(0, 4) + 9 * dc(1, 1) - 9 * dc(1, 3) - + 9 * dc(3, 1) + 9 * dc(3, 3) + dc(4, 0) - dc(4, 4) + : (dc(1, 4) + dc(3, 0) - 10 * dc(3, 1) + 10 * dc(3, 3) - + dc(0, 1) - dc(3, 4) + dc(4, 1) - dc(4, 3) + dc(0, 3) - + dc(1, 0) + 10 * dc(1, 1) - 10 * dc(1, 3))); + break; + case 6: + case 9: + // set Q03 or Q30 + num = (dc(1, 1) - dc(1, 3) + 2 * dc(2, 1) - 2 * dc(2, 3) + dc(3, 1) - + dc(3, 3)); + break; + case 7: + case 8: + default: + // set Q12 and Q21 + num = (dc(1, 1) - 3 * dc(1, 2) + dc(1, 3) - dc(3, 1) + 3 * dc(3, 2) - + dc(3, 3)); + break; + } + num = Q_VAL[0] * num; + if (num >= 0) { + pred = ((Q_VAL[coef_index] << 7) + num) / (Q_VAL[coef_index] << 8); + if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; + } else { + pred = ((Q_VAL[coef_index] << 7) - num) / (Q_VAL[coef_index] << 8); + if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1; + pred = -pred; + } + return static_cast(pred); + }; + + int loop_end = change_dc ? SAVED_COEFS : 6; + for (int i = 1; i < loop_end; ++i) { + if (coef_bits[i] != 0 && scratch[Q_POS[i]] == 0) { + scratch[Q_POS[i]] = calculate_dct_value(i); + } + } + if (change_dc) { + scratch[0] = calculate_dct_value(0); + } +} + +void PrepareForOutput(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + bool smoothing = do_smoothing(cinfo); + m->apply_smoothing = smoothing && FROM_JXL_BOOL(cinfo->do_block_smoothing); + size_t coeffs_per_block = cinfo->num_components * DCTSIZE2; + memset(m->nonzeros_, 0, coeffs_per_block * sizeof(m->nonzeros_[0])); + memset(m->sumabs_, 0, coeffs_per_block * sizeof(m->sumabs_[0])); + memset(m->num_processed_blocks_, 0, sizeof(m->num_processed_blocks_)); + memset(m->biases_, 0, coeffs_per_block * sizeof(m->biases_[0])); + cinfo->output_iMCU_row = 0; + cinfo->output_scanline = 0; + const float kDequantScale = 1.0f / (8 * 255); + for (int c = 0; c < cinfo->num_components; c++) { + const auto& comp = cinfo->comp_info[c]; + JQUANT_TBL* table = comp.quant_table; + if (table == nullptr) continue; + for (size_t k = 0; k < DCTSIZE2; ++k) { + m->dequant_[c * DCTSIZE2 + k] = table->quantval[k] * kDequantScale; + } + } + JPEGLI_CHECK(ChooseInverseTransform(cinfo)); + ChooseColorTransform(cinfo); +} + +void DecodeCurrentiMCURow(j_decompress_ptr cinfo) { + jpeg_decomp_master* m = cinfo->master; + const size_t imcu_row = cinfo->output_iMCU_row; + JBLOCKARRAY blocks[kMaxComponents]; + for (int c = 0; c < cinfo->num_components; ++c) { + const jpeg_component_info* comp = &cinfo->comp_info[c]; + int by0 = imcu_row * comp->v_samp_factor; + int block_rows_left = comp->height_in_blocks - by0; + int max_block_rows = std::min(comp->v_samp_factor, block_rows_left); + int offset = m->streaming_mode_ ? 0 : by0; + blocks[c] = (*cinfo->mem->access_virt_barray)( + reinterpret_cast(cinfo), m->coef_arrays[c], offset, + max_block_rows, FALSE); + } + for (int c = 0; c < cinfo->num_components; ++c) { + size_t k0 = c * DCTSIZE2; + auto& compinfo = cinfo->comp_info[c]; + size_t block_row = imcu_row * compinfo.v_samp_factor; + if (ShouldApplyDequantBiases(cinfo, c)) { + // Update statistics for this iMCU row. + for (int iy = 0; iy < compinfo.v_samp_factor; ++iy) { + size_t by = block_row + iy; + if (by >= compinfo.height_in_blocks) { + continue; + } + int16_t* JXL_RESTRICT coeffs = &blocks[c][iy][0][0]; + size_t num = compinfo.width_in_blocks * DCTSIZE2; + GatherBlockStats(coeffs, num, &m->nonzeros_[k0], &m->sumabs_[k0]); + m->num_processed_blocks_[c] += compinfo.width_in_blocks; + } + if (imcu_row % 4 == 3) { + // Re-compute optimal biases every few iMCU-rows. + ComputeOptimalLaplacianBiases(m->num_processed_blocks_[c], + &m->nonzeros_[k0], &m->sumabs_[k0], + &m->biases_[k0]); + } + } + RowBuffer* raw_out = &m->raw_output_[c]; + for (int iy = 0; iy < compinfo.v_samp_factor; ++iy) { + size_t by = block_row + iy; + if (by >= compinfo.height_in_blocks) { + continue; + } + size_t dctsize = m->scaled_dct_size[c]; + int16_t* JXL_RESTRICT row_in = &blocks[c][iy][0][0]; + float* JXL_RESTRICT row_out = raw_out->Row(by * dctsize); + for (size_t bx = 0; bx < compinfo.width_in_blocks; ++bx) { + if (m->apply_smoothing) { + PredictSmooth(cinfo, blocks[c], c, bx, iy); + (*m->inverse_transform[c])(m->smoothing_scratch_, &m->dequant_[k0], + &m->biases_[k0], m->idct_scratch_, + &row_out[bx * dctsize], raw_out->stride(), + dctsize); + } else { + (*m->inverse_transform[c])(&row_in[bx * DCTSIZE2], &m->dequant_[k0], + &m->biases_[k0], m->idct_scratch_, + &row_out[bx * dctsize], raw_out->stride(), + dctsize); + } + } + if (m->streaming_mode_) { + memset(row_in, 0, compinfo.width_in_blocks * sizeof(JBLOCK)); + } + } + } +} + +void ProcessRawOutput(j_decompress_ptr cinfo, JSAMPIMAGE data) { + jpegli::DecodeCurrentiMCURow(cinfo); + jpeg_decomp_master* m = cinfo->master; + for (int c = 0; c < cinfo->num_components; ++c) { + const auto& compinfo = cinfo->comp_info[c]; + size_t comp_width = compinfo.width_in_blocks * DCTSIZE; + size_t comp_height = compinfo.height_in_blocks * DCTSIZE; + size_t comp_nrows = compinfo.v_samp_factor * DCTSIZE; + size_t y0 = cinfo->output_iMCU_row * compinfo.v_samp_factor * DCTSIZE; + size_t y1 = std::min(y0 + comp_nrows, comp_height); + for (size_t y = y0; y < y1; ++y) { + float* rows[1] = {m->raw_output_[c].Row(y)}; + uint8_t* output = data[c][y - y0]; + DecenterRow(rows[0], comp_width); + WriteToOutput(cinfo, rows, 0, comp_width, 1, output); + } + } + ++cinfo->output_iMCU_row; + cinfo->output_scanline += cinfo->max_v_samp_factor * DCTSIZE; + if (cinfo->output_scanline >= cinfo->output_height) { + ++m->output_passes_done_; + } +} + +void ProcessOutput(j_decompress_ptr cinfo, size_t* num_output_rows, + JSAMPARRAY scanlines, size_t max_output_rows) { + jpeg_decomp_master* m = cinfo->master; + const int vfactor = cinfo->max_v_samp_factor; + const int hfactor = cinfo->max_h_samp_factor; + const size_t context = m->need_context_rows_ ? 1 : 0; + const size_t imcu_row = cinfo->output_iMCU_row; + const size_t imcu_height = vfactor * m->min_scaled_dct_size; + const size_t imcu_width = hfactor * m->min_scaled_dct_size; + const size_t output_width = m->iMCU_cols_ * imcu_width; + if (imcu_row == cinfo->total_iMCU_rows || + (imcu_row > context && + cinfo->output_scanline < (imcu_row - context) * imcu_height)) { + // We are ready to output some scanlines. + size_t ybegin = cinfo->output_scanline; + size_t yend = (imcu_row == cinfo->total_iMCU_rows + ? cinfo->output_height + : (imcu_row - context) * imcu_height); + yend = std::min(yend, ybegin + max_output_rows - *num_output_rows); + size_t yb = (ybegin / vfactor) * vfactor; + size_t ye = DivCeil(yend, vfactor) * vfactor; + for (size_t y = yb; y < ye; y += vfactor) { + for (int c = 0; c < cinfo->num_components; ++c) { + RowBuffer* raw_out = &m->raw_output_[c]; + RowBuffer* render_out = &m->render_output_[c]; + int line_groups = vfactor / m->v_factor[c]; + int downsampled_width = output_width / m->h_factor[c]; + size_t yc = y / m->v_factor[c]; + for (int dy = 0; dy < line_groups; ++dy) { + size_t ymid = yc + dy; + const float* JXL_RESTRICT row_mid = raw_out->Row(ymid); + if (cinfo->do_fancy_upsampling && m->v_factor[c] == 2) { + const float* JXL_RESTRICT row_top = + ymid == 0 ? row_mid : raw_out->Row(ymid - 1); + const float* JXL_RESTRICT row_bot = ymid + 1 == m->raw_height_[c] + ? row_mid + : raw_out->Row(ymid + 1); + Upsample2Vertical(row_top, row_mid, row_bot, + render_out->Row(2 * dy), + render_out->Row(2 * dy + 1), downsampled_width); + } else { + for (int yix = 0; yix < m->v_factor[c]; ++yix) { + memcpy(render_out->Row(m->v_factor[c] * dy + yix), row_mid, + downsampled_width * sizeof(float)); + } + } + if (m->h_factor[c] > 1) { + for (int yix = 0; yix < m->v_factor[c]; ++yix) { + int row_ix = m->v_factor[c] * dy + yix; + float* JXL_RESTRICT row = render_out->Row(row_ix); + float* JXL_RESTRICT tmp = m->upsample_scratch_; + if (cinfo->do_fancy_upsampling && m->h_factor[c] == 2) { + Upsample2Horizontal(row, tmp, output_width); + } else { + // TODO(szabadka) SIMDify this. + for (size_t x = 0; x < output_width; ++x) { + tmp[x] = row[x / m->h_factor[c]]; + } + memcpy(row, tmp, output_width * sizeof(tmp[0])); + } + } + } + } + } + for (int yix = 0; yix < vfactor; ++yix) { + if (y + yix < ybegin || y + yix >= yend) continue; + float* rows[kMaxComponents]; + int num_all_components = + std::max(cinfo->out_color_components, cinfo->num_components); + for (int c = 0; c < num_all_components; ++c) { + rows[c] = m->render_output_[c].Row(yix); + } + (*m->color_transform)(rows, output_width); + for (int c = 0; c < cinfo->out_color_components; ++c) { + // Undo the centering of the sample values around zero. + DecenterRow(rows[c], output_width); + } + if (scanlines) { + uint8_t* output = scanlines[*num_output_rows]; + WriteToOutput(cinfo, rows, m->xoffset_, cinfo->output_width, + cinfo->out_color_components, output); + } + JPEGLI_CHECK(cinfo->output_scanline == y + yix); + ++cinfo->output_scanline; + ++(*num_output_rows); + if (cinfo->output_scanline == cinfo->output_height) { + ++m->output_passes_done_; + } + } + } + } else { + DecodeCurrentiMCURow(cinfo); + ++cinfo->output_iMCU_row; + } +} + +} // namespace jpegli +#endif // HWY_ONCE diff --git a/media/libjxl/src/lib/jpegli/render.h b/media/libjxl/src/lib/jpegli/render.h new file mode 100644 index 0000000000..ad69335d70 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/render.h @@ -0,0 +1,24 @@ +// 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_JPEGLI_RENDER_H_ +#define LIB_JPEGLI_RENDER_H_ + +#include + +#include "lib/jpegli/common.h" + +namespace jpegli { + +void PrepareForOutput(j_decompress_ptr cinfo); + +void ProcessOutput(j_decompress_ptr cinfo, size_t* num_output_rows, + JSAMPARRAY scanlines, size_t max_output_rows); + +void ProcessRawOutput(j_decompress_ptr cinfo, JSAMPIMAGE data); + +} // namespace jpegli + +#endif // LIB_JPEGLI_RENDER_H_ diff --git a/media/libjxl/src/lib/jpegli/simd.cc b/media/libjxl/src/lib/jpegli/simd.cc new file mode 100644 index 0000000000..5e84939342 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/simd.cc @@ -0,0 +1,38 @@ +// 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/jpegli/simd.h" + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jpegli/simd.cc" +#include +#include + +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { + +size_t GetVectorSize() { return HWY_LANES(uint8_t); } + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace jpegli { +namespace { + +HWY_EXPORT(GetVectorSize); // Local function. + +} // namespace + +size_t VectorSize() { + static size_t bytes = HWY_DYNAMIC_DISPATCH(GetVectorSize)(); + return bytes; +} + +} // namespace jpegli +#endif // HWY_ONCE diff --git a/media/libjxl/src/lib/jpegli/simd.h b/media/libjxl/src/lib/jpegli/simd.h new file mode 100644 index 0000000000..aec772e2d4 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/simd.h @@ -0,0 +1,18 @@ +// 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_JPEGLI_SIMD_H_ +#define LIB_JPEGLI_SIMD_H_ + +#include + +namespace jpegli { + +// Returns SIMD vector size in bytes. +size_t VectorSize(); + +} // namespace jpegli + +#endif // LIB_JPEGLI_SIMD_H_ diff --git a/media/libjxl/src/lib/jpegli/source_manager.cc b/media/libjxl/src/lib/jpegli/source_manager.cc new file mode 100644 index 0000000000..e0f0b4c275 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/source_manager.cc @@ -0,0 +1,91 @@ +// 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/jpegli/decode.h" +#include "lib/jpegli/error.h" +#include "lib/jpegli/memory_manager.h" + +namespace jpegli { + +void init_mem_source(j_decompress_ptr cinfo) {} +void init_stdio_source(j_decompress_ptr cinfo) {} + +void skip_input_data(j_decompress_ptr cinfo, long num_bytes /* NOLINT */) { + if (num_bytes <= 0) return; + while (num_bytes > + static_cast(cinfo->src->bytes_in_buffer)) { // NOLINT + num_bytes -= cinfo->src->bytes_in_buffer; + (*cinfo->src->fill_input_buffer)(cinfo); + } + cinfo->src->next_input_byte += num_bytes; + cinfo->src->bytes_in_buffer -= num_bytes; +} + +void term_source(j_decompress_ptr cinfo) {} + +boolean EmitFakeEoiMarker(j_decompress_ptr cinfo) { + static constexpr uint8_t kFakeEoiMarker[2] = {0xff, 0xd9}; + cinfo->src->next_input_byte = kFakeEoiMarker; + cinfo->src->bytes_in_buffer = 2; + return TRUE; +} + +constexpr size_t kStdioBufferSize = 64 << 10; + +struct StdioSourceManager { + jpeg_source_mgr pub; + FILE* f; + uint8_t* buffer; + + static boolean fill_input_buffer(j_decompress_ptr cinfo) { + auto* src = reinterpret_cast(cinfo->src); + size_t num_bytes_read = fread(src->buffer, 1, kStdioBufferSize, src->f); + if (num_bytes_read == 0) { + return EmitFakeEoiMarker(cinfo); + } + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = num_bytes_read; + return TRUE; + } +}; + +} // namespace jpegli + +void jpegli_mem_src(j_decompress_ptr cinfo, const unsigned char* inbuffer, + unsigned long insize /* NOLINT */) { + if (cinfo->src && cinfo->src->init_source != jpegli::init_mem_source) { + JPEGLI_ERROR("jpegli_mem_src: a different source manager was already set"); + } + if (!cinfo->src) { + cinfo->src = jpegli::Allocate(cinfo, 1); + } + cinfo->src->next_input_byte = inbuffer; + cinfo->src->bytes_in_buffer = insize; + cinfo->src->init_source = jpegli::init_mem_source; + cinfo->src->fill_input_buffer = jpegli::EmitFakeEoiMarker; + cinfo->src->skip_input_data = jpegli::skip_input_data; + cinfo->src->resync_to_restart = jpegli_resync_to_restart; + cinfo->src->term_source = jpegli::term_source; +} + +void jpegli_stdio_src(j_decompress_ptr cinfo, FILE* infile) { + if (cinfo->src && cinfo->src->init_source != jpegli::init_stdio_source) { + JPEGLI_ERROR("jpeg_stdio_src: a different source manager was already set"); + } + if (!cinfo->src) { + cinfo->src = reinterpret_cast( + jpegli::Allocate(cinfo, 1)); + } + auto* src = reinterpret_cast(cinfo->src); + src->f = infile; + src->buffer = jpegli::Allocate(cinfo, jpegli::kStdioBufferSize); + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = 0; + src->pub.init_source = jpegli::init_stdio_source; + src->pub.fill_input_buffer = jpegli::StdioSourceManager::fill_input_buffer; + src->pub.skip_input_data = jpegli::skip_input_data; + src->pub.resync_to_restart = jpegli_resync_to_restart; + src->pub.term_source = jpegli::term_source; +} diff --git a/media/libjxl/src/lib/jpegli/source_manager_test.cc b/media/libjxl/src/lib/jpegli/source_manager_test.cc new file mode 100644 index 0000000000..b70b43d8db --- /dev/null +++ b/media/libjxl/src/lib/jpegli/source_manager_test.cc @@ -0,0 +1,151 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/jpegli/decode.h" +#include "lib/jpegli/libjpeg_test_util.h" +#include "lib/jpegli/test_params.h" +#include "lib/jpegli/test_utils.h" +#include "lib/jpegli/testing.h" +#include "lib/jxl/base/status.h" + +namespace jpegli { +namespace { + +void ReadOutputImage(j_decompress_ptr cinfo, TestImage* output) { + jpegli_read_header(cinfo, /*require_image=*/TRUE); + jpegli_start_decompress(cinfo); + output->ysize = cinfo->output_height; + output->xsize = cinfo->output_width; + output->components = cinfo->num_components; + output->AllocatePixels(); + size_t stride = cinfo->output_width * cinfo->num_components; + while (cinfo->output_scanline < cinfo->output_height) { + JSAMPROW scanline = &output->pixels[cinfo->output_scanline * stride]; + jpegli_read_scanlines(cinfo, &scanline, 1); + } + jpegli_finish_decompress(cinfo); +} + +struct TestConfig { + std::string fn; + std::string fn_desc; + DecompressParams dparams; +}; + +class SourceManagerTestParam : public ::testing::TestWithParam {}; + +namespace { +FILE* MemOpen(const std::vector& data) { + FILE* src = tmpfile(); + if (!src) return nullptr; + fwrite(data.data(), 1, data.size(), src); + fseek(src, 0, SEEK_SET); + return src; +} +} // namespace + +TEST_P(SourceManagerTestParam, TestStdioSourceManager) { + TestConfig config = GetParam(); + JXL_ASSIGN_OR_QUIT(std::vector compressed, ReadTestData(config.fn), + "Failed to read test data."); + if (config.dparams.size_factor < 1.0) { + compressed.resize(compressed.size() * config.dparams.size_factor); + } + FILE* src = MemOpen(compressed); + ASSERT_TRUE(src); + TestImage output0; + jpeg_decompress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + jpegli_stdio_src(&cinfo, src); + ReadOutputImage(&cinfo, &output0); + return true; + }; + bool ok = try_catch_block(); + fclose(src); + ASSERT_TRUE(ok); + jpegli_destroy_decompress(&cinfo); + + TestImage output1; + DecodeWithLibjpeg(CompressParams(), DecompressParams(), compressed, &output1); + VerifyOutputImage(output1, output0, 1.0f); +} + +TEST_P(SourceManagerTestParam, TestMemSourceManager) { + TestConfig config = GetParam(); + JXL_ASSIGN_OR_QUIT(std::vector compressed, ReadTestData(config.fn), + "Failed to read test data."); + if (config.dparams.size_factor < 1.0f) { + compressed.resize(compressed.size() * config.dparams.size_factor); + } + TestImage output0; + jpeg_decompress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_decompress(&cinfo); + jpegli_mem_src(&cinfo, compressed.data(), compressed.size()); + ReadOutputImage(&cinfo, &output0); + return true; + }; + ASSERT_TRUE(try_catch_block()); + jpegli_destroy_decompress(&cinfo); + + TestImage output1; + DecodeWithLibjpeg(CompressParams(), DecompressParams(), compressed, &output1); + VerifyOutputImage(output1, output0, 1.0f); +} + +std::vector GenerateTests() { + std::vector all_tests; + { + std::vector> testfiles({ + {"jxl/flower/flower.png.im_q85_444.jpg", "Q85YUV444"}, + {"jxl/flower/flower.png.im_q85_420.jpg", "Q85YUV420"}, + {"jxl/flower/flower.png.im_q85_420_R13B.jpg", "Q85YUV420R13B"}, + }); + for (const auto& it : testfiles) { + for (float size_factor : {0.1f, 0.33f, 0.5f, 0.75f}) { + TestConfig config; + config.fn = it.first; + config.fn_desc = it.second; + config.dparams.size_factor = size_factor; + all_tests.push_back(config); + } + } + return all_tests; + } +} + +std::ostream& operator<<(std::ostream& os, const TestConfig& c) { + os << c.fn_desc; + if (c.dparams.size_factor < 1.0f) { + os << "Partial" << static_cast(c.dparams.size_factor * 100) << "p"; + } + return os; +} + +std::string TestDescription( + const testing::TestParamInfo& info) { + std::stringstream name; + name << info.param; + return name.str(); +} + +JPEGLI_INSTANTIATE_TEST_SUITE_P(SourceManagerTest, SourceManagerTestParam, + testing::ValuesIn(GenerateTests()), + TestDescription); + +} // namespace +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/streaming_test.cc b/media/libjxl/src/lib/jpegli/streaming_test.cc new file mode 100644 index 0000000000..ae85fd2451 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/streaming_test.cc @@ -0,0 +1,245 @@ +// 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 "lib/jpegli/decode.h" +#include "lib/jpegli/encode.h" +#include "lib/jpegli/test_params.h" +#include "lib/jpegli/test_utils.h" +#include "lib/jpegli/testing.h" + +namespace jpegli { +namespace { + +// A simple suspending source manager with an input buffer. +struct SourceManager { + jpeg_source_mgr pub; + std::vector buffer; + + SourceManager() { + pub.next_input_byte = nullptr; + pub.bytes_in_buffer = 0; + pub.init_source = init_source; + pub.fill_input_buffer = fill_input_buffer; + pub.skip_input_data = skip_input_data; + pub.resync_to_restart = jpegli_resync_to_restart; + pub.term_source = term_source; + } + + static void init_source(j_decompress_ptr cinfo) {} + static boolean fill_input_buffer(j_decompress_ptr cinfo) { return FALSE; } + static void skip_input_data(j_decompress_ptr cinfo, + long num_bytes /* NOLINT */) {} + static void term_source(j_decompress_ptr cinfo) {} +}; + +// A destination manager that empties its output buffer into a SourceManager's +// input buffer. The buffer size is kept short because empty_output_buffer() is +// called only when the output buffer is full, and we want to update the decoder +// input frequently to demonstrate that streaming works. +constexpr size_t kOutputBufferSize = 1024; +struct DestinationManager { + jpeg_destination_mgr pub; + std::vector buffer; + SourceManager* dest; + + explicit DestinationManager(SourceManager* src) + : buffer(kOutputBufferSize), dest(src) { + pub.next_output_byte = buffer.data(); + pub.free_in_buffer = buffer.size(); + pub.init_destination = init_destination; + pub.empty_output_buffer = empty_output_buffer; + pub.term_destination = term_destination; + } + + static void init_destination(j_compress_ptr cinfo) {} + + static boolean empty_output_buffer(j_compress_ptr cinfo) { + auto* us = reinterpret_cast(cinfo->dest); + jpeg_destination_mgr* src = &us->pub; + jpeg_source_mgr* dst = &us->dest->pub; + std::vector& src_buf = us->buffer; + std::vector& dst_buf = us->dest->buffer; + if (dst->bytes_in_buffer > 0 && dst->bytes_in_buffer < dst_buf.size()) { + memmove(dst_buf.data(), dst->next_input_byte, dst->bytes_in_buffer); + } + size_t src_len = src_buf.size() - src->free_in_buffer; + dst_buf.resize(dst->bytes_in_buffer + src_len); + memcpy(&dst_buf[dst->bytes_in_buffer], src_buf.data(), src_len); + dst->next_input_byte = dst_buf.data(); + dst->bytes_in_buffer = dst_buf.size(); + src->next_output_byte = src_buf.data(); + src->free_in_buffer = src_buf.size(); + return TRUE; + } + + static void term_destination(j_compress_ptr cinfo) { + empty_output_buffer(cinfo); + } +}; + +struct TestConfig { + TestImage input; + CompressParams jparams; +}; + +class StreamingTestParam : public ::testing::TestWithParam {}; + +TEST_P(StreamingTestParam, TestStreaming) { + jpeg_decompress_struct dinfo = {}; + jpeg_compress_struct cinfo = {}; + SourceManager src; + TestConfig config = GetParam(); + TestImage& input = config.input; + TestImage output; + GeneratePixels(&input); + const auto try_catch_block = [&]() { + ERROR_HANDLER_SETUP(jpegli); + dinfo.err = cinfo.err; + dinfo.client_data = cinfo.client_data; + // Create a pair of compressor and decompressor objects, where the + // compressor's output is connected to the decompressor's input. + jpegli_create_decompress(&dinfo); + jpegli_create_compress(&cinfo); + dinfo.src = reinterpret_cast(&src); + DestinationManager dest(&src); + cinfo.dest = reinterpret_cast(&dest); + + cinfo.image_width = input.xsize; + cinfo.image_height = input.ysize; + cinfo.input_components = input.components; + cinfo.in_color_space = static_cast(input.color_space); + jpegli_set_defaults(&cinfo); + cinfo.comp_info[0].v_samp_factor = config.jparams.v_sampling[0]; + jpegli_set_progressive_level(&cinfo, 0); + cinfo.optimize_coding = FALSE; + jpegli_start_compress(&cinfo, TRUE); + + size_t stride = cinfo.image_width * cinfo.input_components; + size_t iMCU_height = 8 * cinfo.max_v_samp_factor; + std::vector row_bytes(iMCU_height * stride); + size_t y_in = 0; + size_t y_out = 0; + while (y_in < cinfo.image_height) { + // Feed one iMCU row at a time to the compressor. + size_t lines_in = std::min(iMCU_height, cinfo.image_height - y_in); + memcpy(row_bytes.data(), &input.pixels[y_in * stride], lines_in * stride); + std::vector rows_in(lines_in); + for (size_t i = 0; i < lines_in; ++i) { + rows_in[i] = &row_bytes[i * stride]; + } + EXPECT_EQ(lines_in, + jpegli_write_scanlines(&cinfo, rows_in.data(), lines_in)); + y_in += lines_in; + if (y_in == cinfo.image_height) { + jpegli_finish_compress(&cinfo); + } + + // Atfer the first iMCU row, we don't yet expect any output because the + // compressor delays processing to have context rows after the iMCU row. + if (y_in < std::min(2 * iMCU_height, cinfo.image_height)) { + continue; + } + + // After two iMCU rows, the compressor has started emitting compressed + // data. We check here that at least the scan header was output, because + // we expect that the compressor's output buffer was filled at least once + // while emitting the first compressed iMCU row. + if (y_in == std::min(2 * iMCU_height, cinfo.image_height)) { + EXPECT_EQ(JPEG_REACHED_SOS, + jpegli_read_header(&dinfo, /*require_image=*/TRUE)); + output.xsize = dinfo.image_width; + output.ysize = dinfo.image_height; + output.components = dinfo.num_components; + EXPECT_EQ(output.xsize, input.xsize); + EXPECT_EQ(output.ysize, input.ysize); + EXPECT_EQ(output.components, input.components); + EXPECT_TRUE(jpegli_start_decompress(&dinfo)); + output.pixels.resize(output.ysize * stride); + if (y_in < cinfo.image_height) { + continue; + } + } + + // After six iMCU rows, the compressor has emitted five iMCU rows of + // compressed data, of which we expect four full iMCU row of compressed + // data to be in the decoder's input buffer, but since the decoder also + // needs context rows for upsampling and smoothing, we don't expect any + // output to be ready yet. + if (y_in < 7 * iMCU_height && y_in < cinfo.image_height) { + continue; + } + + // After five iMCU rows, we expect the decoder to have rendered the output + // with four iMCU rows of delay. + // TODO(szabadka) Reduce the processing delay in the decoder if possible. + size_t lines_out = + (y_in == cinfo.image_height ? cinfo.image_height - y_out + : iMCU_height); + std::vector rows_out(lines_out); + for (size_t i = 0; i < lines_out; ++i) { + rows_out[i] = + reinterpret_cast(&output.pixels[(y_out + i) * stride]); + } + EXPECT_EQ(lines_out, + jpegli_read_scanlines(&dinfo, rows_out.data(), lines_out)); + VerifyOutputImage(input, output, y_out, lines_out, 3.8f); + y_out += lines_out; + + if (y_out == cinfo.image_height) { + EXPECT_TRUE(jpegli_finish_decompress(&dinfo)); + } + } + return true; + }; + EXPECT_TRUE(try_catch_block()); + jpegli_destroy_decompress(&dinfo); + jpegli_destroy_compress(&cinfo); +} + +std::vector GenerateTests() { + std::vector all_tests; + const size_t xsize0 = 1920; + const size_t ysize0 = 1080; + for (int dysize : {0, 1, 8, 9}) { + for (int v_sampling : {1, 2}) { + TestConfig config; + config.input.xsize = xsize0; + config.input.ysize = ysize0 + dysize; + config.jparams.h_sampling = {1, 1, 1}; + config.jparams.v_sampling = {v_sampling, 1, 1}; + all_tests.push_back(config); + } + } + return all_tests; +} + +std::ostream& operator<<(std::ostream& os, const TestConfig& c) { + os << c.input; + os << c.jparams; + return os; +} + +std::string TestDescription( + const testing::TestParamInfo& info) { + std::stringstream name; + name << info.param; + return name.str(); +} + +JPEGLI_INSTANTIATE_TEST_SUITE_P(StreamingTest, StreamingTestParam, + testing::ValuesIn(GenerateTests()), + TestDescription); + +} // namespace +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/test_params.h b/media/libjxl/src/lib/jpegli/test_params.h new file mode 100644 index 0000000000..6ab9fa573a --- /dev/null +++ b/media/libjxl/src/lib/jpegli/test_params.h @@ -0,0 +1,163 @@ +// 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_JPEGLI_TEST_PARAMS_H_ +#define LIB_JPEGLI_TEST_PARAMS_H_ + +#include +#include + +#include +#include + +#include "lib/jpegli/types.h" + +namespace jpegli { + +// We define this here as well to make sure that the *_api_test.cc tests only +// use the public API and therefore we don't include any *_internal.h headers. +template +constexpr inline T1 DivCeil(T1 a, T2 b) { + return (a + b - 1) / b; +} + +#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0])) + +static constexpr int kLastScan = 0xffff; + +static uint32_t kTestColorMap[] = { + 0x000000, 0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0x00ffff, + 0xff00ff, 0xffffff, 0x6251fc, 0x45d9c7, 0xa7f059, 0xd9a945, + 0xfa4e44, 0xceaffc, 0xbad7db, 0xc1f0b1, 0xdbca9a, 0xfacac5, + 0xf201ff, 0x0063db, 0x00f01c, 0xdbb204, 0xf12f0c, 0x7ba1dc}; +static constexpr int kTestColorMapNumColors = ARRAY_SIZE(kTestColorMap); + +static constexpr int kSpecialMarker0 = 0xe5; +static constexpr int kSpecialMarker1 = 0xe9; +static constexpr uint8_t kMarkerData[] = {0, 1, 255, 0, 17}; +static constexpr uint8_t kMarkerSequence[] = {0xe6, 0xe8, 0xe7, + 0xe6, 0xe7, 0xe8}; +static constexpr size_t kMarkerSequenceLen = ARRAY_SIZE(kMarkerSequence); + +enum JpegIOMode { + PIXELS, + RAW_DATA, + COEFFICIENTS, +}; + +struct CustomQuantTable { + int slot_idx = 0; + uint16_t table_type = 0; + int scale_factor = 100; + bool add_raw = false; + bool force_baseline = true; + std::vector basic_table; + std::vector quantval; + void Generate(); +}; + +struct TestImage { + size_t xsize = 2268; + size_t ysize = 1512; + int color_space = 2; // JCS_RGB + size_t components = 3; + JpegliDataType data_type = JPEGLI_TYPE_UINT8; + JpegliEndianness endianness = JPEGLI_NATIVE_ENDIAN; + std::vector pixels; + std::vector> raw_data; + std::vector> coeffs; + void AllocatePixels() { + pixels.resize(ysize * xsize * components * + jpegli_bytes_per_sample(data_type)); + } + void Clear() { + pixels.clear(); + raw_data.clear(); + coeffs.clear(); + } +}; + +struct CompressParams { + int quality = 90; + bool set_jpeg_colorspace = false; + int jpeg_color_space = 0; // JCS_UNKNOWN + std::vector quant_indexes; + std::vector quant_tables; + std::vector h_sampling; + std::vector v_sampling; + std::vector comp_ids; + int override_JFIF = -1; + int override_Adobe = -1; + bool add_marker = false; + bool simple_progression = false; + // -1 is library default + // 0, 1, 2 is set through jpegli_set_progressive_level() + // 2 + N is kScriptN + int progressive_mode = -1; + unsigned int restart_interval = 0; + int restart_in_rows = 0; + int smoothing_factor = 0; + int optimize_coding = -1; + bool use_flat_dc_luma_code = false; + bool omit_standard_tables = false; + bool xyb_mode = false; + bool libjpeg_mode = false; + bool use_adaptive_quantization = true; + std::vector icc; + + int h_samp(int c) const { return h_sampling.empty() ? 1 : h_sampling[c]; } + int v_samp(int c) const { return v_sampling.empty() ? 1 : v_sampling[c]; } + int max_h_sample() const { + auto it = std::max_element(h_sampling.begin(), h_sampling.end()); + return it == h_sampling.end() ? 1 : *it; + } + int max_v_sample() const { + auto it = std::max_element(v_sampling.begin(), v_sampling.end()); + return it == v_sampling.end() ? 1 : *it; + } + int comp_width(const TestImage& input, int c) const { + return DivCeil(input.xsize * h_samp(c), max_h_sample() * 8) * 8; + } + int comp_height(const TestImage& input, int c) const { + return DivCeil(input.ysize * v_samp(c), max_v_sample() * 8) * 8; + } +}; + +enum ColorQuantMode { + CQUANT_1PASS, + CQUANT_2PASS, + CQUANT_EXTERNAL, + CQUANT_REUSE, +}; + +struct ScanDecompressParams { + int max_scan_number; + int dither_mode; + ColorQuantMode color_quant_mode; +}; + +struct DecompressParams { + float size_factor = 1.0f; + size_t chunk_size = 65536; + size_t max_output_lines = 16; + JpegIOMode output_mode = PIXELS; + JpegliDataType data_type = JPEGLI_TYPE_UINT8; + JpegliEndianness endianness = JPEGLI_NATIVE_ENDIAN; + bool set_out_color_space = false; + int out_color_space = 0; // JCS_UNKNOWN + bool crop_output = false; + bool do_block_smoothing = false; + bool do_fancy_upsampling = true; + bool skip_scans = false; + int scale_num = 1; + int scale_denom = 1; + bool quantize_colors = false; + int desired_number_of_colors = 256; + std::vector scan_params; +}; + +} // namespace jpegli + +#endif // LIB_JPEGLI_TEST_PARAMS_H_ diff --git a/media/libjxl/src/lib/jpegli/test_utils-inl.h b/media/libjxl/src/lib/jpegli/test_utils-inl.h new file mode 100644 index 0000000000..da1a924e21 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/test_utils-inl.h @@ -0,0 +1,432 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This template file is included in both the libjpeg_test_util.cc and the +// test_utils.cc files with different JPEG_API_FN macros and possibly different +// include paths for the jpeg headers. + +// Sequential non-interleaved. +constexpr jpeg_scan_info kScript1[] = { + {1, {0}, 0, 63, 0, 0}, + {1, {1}, 0, 63, 0, 0}, + {1, {2}, 0, 63, 0, 0}, +}; +// Sequential partially interleaved, chroma first. +constexpr jpeg_scan_info kScript2[] = { + {2, {1, 2}, 0, 63, 0, 0}, + {1, {0}, 0, 63, 0, 0}, +}; + +// Rest of the scan scripts are progressive. + +constexpr jpeg_scan_info kScript3[] = { + // Interleaved full DC. + {3, {0, 1, 2}, 0, 0, 0, 0}, + // Full AC scans. + {1, {0}, 1, 63, 0, 0}, + {1, {1}, 1, 63, 0, 0}, + {1, {2}, 1, 63, 0, 0}, +}; +constexpr jpeg_scan_info kScript4[] = { + // Non-interleaved full DC. + {1, {0}, 0, 0, 0, 0}, + {1, {1}, 0, 0, 0, 0}, + {1, {2}, 0, 0, 0, 0}, + // Full AC scans. + {1, {0}, 1, 63, 0, 0}, + {1, {1}, 1, 63, 0, 0}, + {1, {2}, 1, 63, 0, 0}, +}; +constexpr jpeg_scan_info kScript5[] = { + // Partially interleaved full DC, chroma first. + {2, {1, 2}, 0, 0, 0, 0}, + {1, {0}, 0, 0, 0, 0}, + // AC shifted by 1 bit. + {1, {0}, 1, 63, 0, 1}, + {1, {1}, 1, 63, 0, 1}, + {1, {2}, 1, 63, 0, 1}, + // AC refinement scan. + {1, {0}, 1, 63, 1, 0}, + {1, {1}, 1, 63, 1, 0}, + {1, {2}, 1, 63, 1, 0}, +}; +constexpr jpeg_scan_info kScript6[] = { + // Interleaved DC shifted by 2 bits. + {3, {0, 1, 2}, 0, 0, 0, 2}, + // Interleaved DC refinement scans. + {3, {0, 1, 2}, 0, 0, 2, 1}, + {3, {0, 1, 2}, 0, 0, 1, 0}, + // Full AC scans. + {1, {0}, 1, 63, 0, 0}, + {1, {1}, 1, 63, 0, 0}, + {1, {2}, 1, 63, 0, 0}, +}; + +constexpr jpeg_scan_info kScript7[] = { + // Non-interleaved DC shifted by 2 bits. + {1, {0}, 0, 0, 0, 2}, + {1, {1}, 0, 0, 0, 2}, + {1, {2}, 0, 0, 0, 2}, + // Non-interleaved DC first refinement scans. + {1, {0}, 0, 0, 2, 1}, + {1, {1}, 0, 0, 2, 1}, + {1, {2}, 0, 0, 2, 1}, + // Non-interleaved DC second refinement scans. + {1, {0}, 0, 0, 1, 0}, + {1, {1}, 0, 0, 1, 0}, + {1, {2}, 0, 0, 1, 0}, + // Full AC scans. + {1, {0}, 1, 63, 0, 0}, + {1, {1}, 1, 63, 0, 0}, + {1, {2}, 1, 63, 0, 0}, +}; + +constexpr jpeg_scan_info kScript8[] = { + // Partially interleaved DC shifted by 2 bits, chroma first + {2, {1, 2}, 0, 0, 0, 2}, + {1, {0}, 0, 0, 0, 2}, + // Partially interleaved DC first refinement scans. + {2, {0, 2}, 0, 0, 2, 1}, + {1, {1}, 0, 0, 2, 1}, + // Partially interleaved DC first refinement scans, chroma first. + {2, {1, 2}, 0, 0, 1, 0}, + {1, {0}, 0, 0, 1, 0}, + // Full AC scans. + {1, {0}, 1, 63, 0, 0}, + {1, {1}, 1, 63, 0, 0}, + {1, {2}, 1, 63, 0, 0}, +}; + +constexpr jpeg_scan_info kScript9[] = { + // Interleaved full DC. + {3, {0, 1, 2}, 0, 0, 0, 0}, + // AC scans for component 0 + // shifted by 1 bit, two spectral ranges + {1, {0}, 1, 6, 0, 1}, + {1, {0}, 7, 63, 0, 1}, + // refinement scan, full + {1, {0}, 1, 63, 1, 0}, + // AC scans for component 1 + // shifted by 1 bit, full + {1, {1}, 1, 63, 0, 1}, + // refinement scan, two spectral ranges + {1, {1}, 1, 6, 1, 0}, + {1, {1}, 7, 63, 1, 0}, + // AC scans for component 2 + // shifted by 1 bit, two spectral ranges + {1, {2}, 1, 6, 0, 1}, + {1, {2}, 7, 63, 0, 1}, + // refinement scan, two spectral ranges (but different from above) + {1, {2}, 1, 16, 1, 0}, + {1, {2}, 17, 63, 1, 0}, +}; + +constexpr jpeg_scan_info kScript10[] = { + // Interleaved full DC. + {3, {0, 1, 2}, 0, 0, 0, 0}, + // AC scans for spectral range 1..16 + // shifted by 1 + {1, {0}, 1, 16, 0, 1}, + {1, {1}, 1, 16, 0, 1}, + {1, {2}, 1, 16, 0, 1}, + // refinement scans, two sub-ranges + {1, {0}, 1, 8, 1, 0}, + {1, {0}, 9, 16, 1, 0}, + {1, {1}, 1, 8, 1, 0}, + {1, {1}, 9, 16, 1, 0}, + {1, {2}, 1, 8, 1, 0}, + {1, {2}, 9, 16, 1, 0}, + // AC scans for spectral range 17..63 + {1, {0}, 17, 63, 0, 1}, + {1, {1}, 17, 63, 0, 1}, + {1, {2}, 17, 63, 0, 1}, + // refinement scans, two sub-ranges + {1, {0}, 17, 28, 1, 0}, + {1, {0}, 29, 63, 1, 0}, + {1, {1}, 17, 28, 1, 0}, + {1, {1}, 29, 63, 1, 0}, + {1, {2}, 17, 28, 1, 0}, + {1, {2}, 29, 63, 1, 0}, +}; + +struct ScanScript { + int num_scans; + const jpeg_scan_info* scans; +}; + +constexpr ScanScript kTestScript[] = { + {ARRAY_SIZE(kScript1), kScript1}, {ARRAY_SIZE(kScript2), kScript2}, + {ARRAY_SIZE(kScript3), kScript3}, {ARRAY_SIZE(kScript4), kScript4}, + {ARRAY_SIZE(kScript5), kScript5}, {ARRAY_SIZE(kScript6), kScript6}, + {ARRAY_SIZE(kScript7), kScript7}, {ARRAY_SIZE(kScript8), kScript8}, + {ARRAY_SIZE(kScript9), kScript9}, {ARRAY_SIZE(kScript10), kScript10}, +}; +constexpr int kNumTestScripts = ARRAY_SIZE(kTestScript); + +void SetScanDecompressParams(const DecompressParams& dparams, + j_decompress_ptr cinfo, int scan_number) { + const ScanDecompressParams* sparams = nullptr; + for (const auto& sp : dparams.scan_params) { + if (scan_number <= sp.max_scan_number) { + sparams = &sp; + break; + } + } + if (sparams == nullptr) { + return; + } + if (dparams.quantize_colors) { + cinfo->dither_mode = static_cast(sparams->dither_mode); + if (sparams->color_quant_mode == CQUANT_1PASS) { + cinfo->two_pass_quantize = FALSE; + cinfo->colormap = nullptr; + } else if (sparams->color_quant_mode == CQUANT_2PASS) { + Check(cinfo->out_color_space == JCS_RGB); + cinfo->two_pass_quantize = TRUE; + cinfo->colormap = nullptr; + } else if (sparams->color_quant_mode == CQUANT_EXTERNAL) { + Check(cinfo->out_color_space == JCS_RGB); + cinfo->two_pass_quantize = FALSE; + bool have_colormap = cinfo->colormap != nullptr; + cinfo->actual_number_of_colors = kTestColorMapNumColors; + cinfo->colormap = (*cinfo->mem->alloc_sarray)( + reinterpret_cast(cinfo), JPOOL_IMAGE, + cinfo->actual_number_of_colors, 3); + jxl::msan::UnpoisonMemory(reinterpret_cast(cinfo->colormap), + 3 * sizeof(JSAMPLE*)); + for (int i = 0; i < kTestColorMapNumColors; ++i) { + cinfo->colormap[0][i] = (kTestColorMap[i] >> 16) & 0xff; + cinfo->colormap[1][i] = (kTestColorMap[i] >> 8) & 0xff; + cinfo->colormap[2][i] = (kTestColorMap[i] >> 0) & 0xff; + } + if (have_colormap) { + JPEG_API_FN(new_colormap)(cinfo); + } + } else if (sparams->color_quant_mode == CQUANT_REUSE) { + Check(cinfo->out_color_space == JCS_RGB); + Check(cinfo->colormap); + } + } +} + +void SetDecompressParams(const DecompressParams& dparams, + j_decompress_ptr cinfo) { + cinfo->do_block_smoothing = dparams.do_block_smoothing ? 1 : 0; + cinfo->do_fancy_upsampling = dparams.do_fancy_upsampling ? 1 : 0; + if (dparams.output_mode == RAW_DATA) { + cinfo->raw_data_out = TRUE; + } + if (dparams.set_out_color_space) { + cinfo->out_color_space = + static_cast(dparams.out_color_space); + if (dparams.out_color_space == JCS_UNKNOWN) { + cinfo->jpeg_color_space = JCS_UNKNOWN; + } + } + cinfo->scale_num = dparams.scale_num; + cinfo->scale_denom = dparams.scale_denom; + cinfo->quantize_colors = dparams.quantize_colors ? 1 : 0; + cinfo->desired_number_of_colors = dparams.desired_number_of_colors; + if (!dparams.scan_params.empty()) { + if (cinfo->buffered_image) { + for (const auto& sparams : dparams.scan_params) { + if (sparams.color_quant_mode == CQUANT_1PASS) { + cinfo->enable_1pass_quant = TRUE; + } else if (sparams.color_quant_mode == CQUANT_2PASS) { + cinfo->enable_2pass_quant = TRUE; + } else if (sparams.color_quant_mode == CQUANT_EXTERNAL) { + cinfo->enable_external_quant = TRUE; + } + } + SetScanDecompressParams(dparams, cinfo, 1); + } else { + SetScanDecompressParams(dparams, cinfo, kLastScan); + } + } +} + +void CheckMarkerPresent(j_decompress_ptr cinfo, uint8_t marker_type) { + bool marker_found = false; + for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr; + marker = marker->next) { + jxl::msan::UnpoisonMemory(marker, sizeof(*marker)); + jxl::msan::UnpoisonMemory(marker->data, marker->data_length); + if (marker->marker == marker_type && + marker->data_length == sizeof(kMarkerData) && + memcmp(marker->data, kMarkerData, sizeof(kMarkerData)) == 0) { + marker_found = true; + } + } + Check(marker_found); +} + +void VerifyHeader(const CompressParams& jparams, j_decompress_ptr cinfo) { + if (jparams.set_jpeg_colorspace) { + Check(cinfo->jpeg_color_space == jparams.jpeg_color_space); + } + if (jparams.override_JFIF >= 0) { + Check(cinfo->saw_JFIF_marker == jparams.override_JFIF); + } + if (jparams.override_Adobe >= 0) { + Check(cinfo->saw_Adobe_marker == jparams.override_Adobe); + } + if (jparams.add_marker) { + CheckMarkerPresent(cinfo, kSpecialMarker0); + CheckMarkerPresent(cinfo, kSpecialMarker1); + } + jxl::msan::UnpoisonMemory( + cinfo->comp_info, cinfo->num_components * sizeof(cinfo->comp_info[0])); + int max_h_samp_factor = 1; + int max_v_samp_factor = 1; + for (int i = 0; i < cinfo->num_components; ++i) { + jpeg_component_info* comp = &cinfo->comp_info[i]; + if (!jparams.comp_ids.empty()) { + Check(comp->component_id == jparams.comp_ids[i]); + } + if (!jparams.h_sampling.empty()) { + Check(comp->h_samp_factor == jparams.h_sampling[i]); + } + if (!jparams.v_sampling.empty()) { + Check(comp->v_samp_factor == jparams.v_sampling[i]); + } + if (!jparams.quant_indexes.empty()) { + Check(comp->quant_tbl_no == jparams.quant_indexes[i]); + } + max_h_samp_factor = std::max(max_h_samp_factor, comp->h_samp_factor); + max_v_samp_factor = std::max(max_v_samp_factor, comp->v_samp_factor); + } + Check(max_h_samp_factor == cinfo->max_h_samp_factor); + Check(max_v_samp_factor == cinfo->max_v_samp_factor); + int referenced_tables[NUM_QUANT_TBLS] = {}; + for (int i = 0; i < cinfo->num_components; ++i) { + jpeg_component_info* comp = &cinfo->comp_info[i]; + Check(comp->width_in_blocks == + DivCeil(cinfo->image_width * comp->h_samp_factor, + max_h_samp_factor * DCTSIZE)); + Check(comp->height_in_blocks == + DivCeil(cinfo->image_height * comp->v_samp_factor, + max_v_samp_factor * DCTSIZE)); + referenced_tables[comp->quant_tbl_no] = 1; + } + for (const auto& table : jparams.quant_tables) { + JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[table.slot_idx]; + if (!referenced_tables[table.slot_idx]) { + Check(quant_table == nullptr); + continue; + } + Check(quant_table != nullptr); + jxl::msan::UnpoisonMemory(quant_table, sizeof(*quant_table)); + for (int k = 0; k < DCTSIZE2; ++k) { + Check(quant_table->quantval[k] == table.quantval[k]); + } + } +} + +void VerifyScanHeader(const CompressParams& jparams, j_decompress_ptr cinfo) { + Check(cinfo->input_scan_number > 0); + if (cinfo->progressive_mode) { + Check(cinfo->Ss != 0 || cinfo->Se != 63); + } else { + Check(cinfo->Ss == 0 && cinfo->Se == 63); + } + if (jparams.progressive_mode > 2) { + Check(jparams.progressive_mode < 3 + kNumTestScripts); + const ScanScript& script = kTestScript[jparams.progressive_mode - 3]; + Check(cinfo->input_scan_number <= script.num_scans); + const jpeg_scan_info& scan = script.scans[cinfo->input_scan_number - 1]; + Check(cinfo->comps_in_scan == scan.comps_in_scan); + for (int i = 0; i < cinfo->comps_in_scan; ++i) { + Check(cinfo->cur_comp_info[i]->component_index == + scan.component_index[i]); + } + Check(cinfo->Ss == scan.Ss); + Check(cinfo->Se == scan.Se); + Check(cinfo->Ah == scan.Ah); + Check(cinfo->Al == scan.Al); + } + if (jparams.restart_interval > 0) { + Check(cinfo->restart_interval == jparams.restart_interval); + } else if (jparams.restart_in_rows > 0) { + Check(cinfo->restart_interval == + jparams.restart_in_rows * cinfo->MCUs_per_row); + } + if (jparams.progressive_mode == 0 && jparams.optimize_coding == 0) { + if (cinfo->jpeg_color_space == JCS_RGB) { + Check(cinfo->comp_info[0].dc_tbl_no == 0); + Check(cinfo->comp_info[1].dc_tbl_no == 0); + Check(cinfo->comp_info[2].dc_tbl_no == 0); + Check(cinfo->comp_info[0].ac_tbl_no == 0); + Check(cinfo->comp_info[1].ac_tbl_no == 0); + Check(cinfo->comp_info[2].ac_tbl_no == 0); + } else if (cinfo->jpeg_color_space == JCS_YCbCr) { + Check(cinfo->comp_info[0].dc_tbl_no == 0); + Check(cinfo->comp_info[1].dc_tbl_no == 1); + Check(cinfo->comp_info[2].dc_tbl_no == 1); + Check(cinfo->comp_info[0].ac_tbl_no == 0); + Check(cinfo->comp_info[1].ac_tbl_no == 1); + Check(cinfo->comp_info[2].ac_tbl_no == 1); + } else if (cinfo->jpeg_color_space == JCS_CMYK) { + Check(cinfo->comp_info[0].dc_tbl_no == 0); + Check(cinfo->comp_info[1].dc_tbl_no == 0); + Check(cinfo->comp_info[2].dc_tbl_no == 0); + Check(cinfo->comp_info[3].dc_tbl_no == 0); + Check(cinfo->comp_info[0].ac_tbl_no == 0); + Check(cinfo->comp_info[1].ac_tbl_no == 0); + Check(cinfo->comp_info[2].ac_tbl_no == 0); + Check(cinfo->comp_info[3].ac_tbl_no == 0); + } else if (cinfo->jpeg_color_space == JCS_YCCK) { + Check(cinfo->comp_info[0].dc_tbl_no == 0); + Check(cinfo->comp_info[1].dc_tbl_no == 1); + Check(cinfo->comp_info[2].dc_tbl_no == 1); + Check(cinfo->comp_info[3].dc_tbl_no == 0); + Check(cinfo->comp_info[0].ac_tbl_no == 0); + Check(cinfo->comp_info[1].ac_tbl_no == 1); + Check(cinfo->comp_info[2].ac_tbl_no == 1); + Check(cinfo->comp_info[3].ac_tbl_no == 0); + } + if (jparams.use_flat_dc_luma_code) { + JHUFF_TBL* tbl = cinfo->dc_huff_tbl_ptrs[0]; + jxl::msan::UnpoisonMemory(tbl, sizeof(*tbl)); + for (int i = 0; i < 15; ++i) { + Check(tbl->huffval[i] == i); + } + } + } +} + +void UnmapColors(uint8_t* row, size_t xsize, int components, + JSAMPARRAY colormap, size_t num_colors) { + Check(colormap != nullptr); + std::vector tmp(xsize * components); + for (size_t x = 0; x < xsize; ++x) { + Check(row[x] < num_colors); + for (int c = 0; c < components; ++c) { + tmp[x * components + c] = colormap[c][row[x]]; + } + } + memcpy(row, tmp.data(), tmp.size()); +} + +void CopyCoefficients(j_decompress_ptr cinfo, jvirt_barray_ptr* coef_arrays, + TestImage* output) { + output->xsize = cinfo->image_width; + output->ysize = cinfo->image_height; + output->components = cinfo->num_components; + output->color_space = cinfo->out_color_space; + j_common_ptr comptr = reinterpret_cast(cinfo); + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + std::vector coeffs(comp->width_in_blocks * comp->height_in_blocks * + DCTSIZE2); + for (size_t by = 0; by < comp->height_in_blocks; ++by) { + JBLOCKARRAY blocks = (*cinfo->mem->access_virt_barray)( + comptr, coef_arrays[c], by, 1, TRUE); + size_t stride = comp->width_in_blocks * sizeof(JBLOCK); + size_t offset = by * comp->width_in_blocks * DCTSIZE2; + memcpy(&coeffs[offset], blocks[0], stride); + } + output->coeffs.emplace_back(std::move(coeffs)); + } +} diff --git a/media/libjxl/src/lib/jpegli/test_utils.cc b/media/libjxl/src/lib/jpegli/test_utils.cc new file mode 100644 index 0000000000..2b903fa0c0 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/test_utils.cc @@ -0,0 +1,867 @@ +// 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/jpegli/test_utils.h" + +#include +#include +#include +#include +#include + +#include "lib/jpegli/decode.h" +#include "lib/jpegli/encode.h" +#include "lib/jxl/base/byte_order.h" +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/printf_macros.h" +#include "lib/jxl/base/sanitizers.h" +#include "lib/jxl/base/status.h" + +#if !defined(TEST_DATA_PATH) +#include "tools/cpp/runfiles/runfiles.h" +#endif + +namespace jpegli { + +namespace { +void Check(bool ok) { + if (!ok) { + JXL_CRASH(); + } +} +#define QUIT(M) Check(false); +} // namespace + +#define JPEG_API_FN(name) jpegli_##name +#include "lib/jpegli/test_utils-inl.h" +#undef JPEG_API_FN + +#if defined(TEST_DATA_PATH) +std::string GetTestDataPath(const std::string& filename) { + return std::string(TEST_DATA_PATH "/") + filename; +} +#else +using ::bazel::tools::cpp::runfiles::Runfiles; +const std::unique_ptr kRunfiles(Runfiles::Create("")); +std::string GetTestDataPath(const std::string& filename) { + std::string root(JPEGXL_ROOT_PACKAGE "/testdata/"); + return kRunfiles->Rlocation(root + filename); +} +#endif + +jxl::StatusOr> ReadTestData(const std::string& filename) { + std::vector data; + 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_ENSURE(file.good()); + const uint8_t* raw = reinterpret_cast(str.data()); + data = std::vector(raw, raw + str.size()); + printf("Test data %s is %d bytes long.\n", filename.c_str(), + static_cast(data.size())); + return data; +} + +void CustomQuantTable::Generate() { + basic_table.resize(DCTSIZE2); + quantval.resize(DCTSIZE2); + switch (table_type) { + case 0: { + for (int k = 0; k < DCTSIZE2; ++k) { + basic_table[k] = k + 1; + } + break; + } + default: + for (int k = 0; k < DCTSIZE2; ++k) { + basic_table[k] = table_type; + } + } + for (int k = 0; k < DCTSIZE2; ++k) { + quantval[k] = (basic_table[k] * scale_factor + 50U) / 100U; + quantval[k] = std::max(quantval[k], 1U); + quantval[k] = std::min(quantval[k], 65535U); + if (!add_raw) { + quantval[k] = std::min(quantval[k], force_baseline ? 255U : 32767U); + } + } +} + +bool PNMParser::ParseHeader(const uint8_t** pos, size_t* xsize, size_t* ysize, + size_t* num_channels, size_t* bitdepth) { + if (pos_[0] != 'P' || (pos_[1] != '5' && pos_[1] != '6')) { + fprintf(stderr, "Invalid PNM header."); + return false; + } + *num_channels = (pos_[1] == '5' ? 1 : 3); + pos_ += 2; + + size_t maxval; + if (!SkipWhitespace() || !ParseUnsigned(xsize) || !SkipWhitespace() || + !ParseUnsigned(ysize) || !SkipWhitespace() || !ParseUnsigned(&maxval) || + !SkipWhitespace()) { + return false; + } + if (maxval == 0 || maxval >= 65536) { + fprintf(stderr, "Invalid maxval value.\n"); + return false; + } + bool found_bitdepth = false; + for (int bits = 1; bits <= 16; ++bits) { + if (maxval == (1u << bits) - 1) { + *bitdepth = bits; + found_bitdepth = true; + break; + } + } + if (!found_bitdepth) { + fprintf(stderr, "Invalid maxval value.\n"); + return false; + } + + *pos = pos_; + return true; +} + +bool PNMParser::ParseUnsigned(size_t* number) { + if (pos_ == end_ || *pos_ < '0' || *pos_ > '9') { + fprintf(stderr, "Expected unsigned number.\n"); + return false; + } + *number = 0; + while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') { + *number *= 10; + *number += *pos_ - '0'; + ++pos_; + } + + return true; +} + +bool PNMParser::SkipWhitespace() { + if (pos_ == end_ || !IsWhitespace(*pos_)) { + fprintf(stderr, "Expected whitespace.\n"); + return false; + } + while (pos_ < end_ && IsWhitespace(*pos_)) { + ++pos_; + } + return true; +} + +bool ReadPNM(const std::vector& data, size_t* xsize, size_t* ysize, + size_t* num_channels, size_t* bitdepth, + std::vector* pixels) { + if (data.size() < 2) { + fprintf(stderr, "PNM file too small.\n"); + return false; + } + PNMParser parser(data.data(), data.size()); + const uint8_t* pos = nullptr; + if (!parser.ParseHeader(&pos, xsize, ysize, num_channels, bitdepth)) { + return false; + } + pixels->resize(data.data() + data.size() - pos); + memcpy(pixels->data(), pos, pixels->size()); + return true; +} + +std::string ColorSpaceName(J_COLOR_SPACE colorspace) { + switch (colorspace) { + case JCS_UNKNOWN: + return "UNKNOWN"; + case JCS_GRAYSCALE: + return "GRAYSCALE"; + case JCS_RGB: + return "RGB"; + case JCS_YCbCr: + return "YCbCr"; + case JCS_CMYK: + return "CMYK"; + case JCS_YCCK: + return "YCCK"; + case JCS_EXT_RGB: + return "EXT_RGB"; + case JCS_EXT_BGR: + return "EXT_BGR"; + case JCS_EXT_RGBA: + return "EXT_RGBA"; + case JCS_EXT_BGRA: + return "EXT_BGRA"; + case JCS_EXT_ARGB: + return "EXT_ARGB"; + case JCS_EXT_ABGR: + return "EXT_ABGR"; + default: + return ""; + } +} + +std::string IOMethodName(JpegliDataType data_type, + JpegliEndianness endianness) { + std::string retval; + if (data_type == JPEGLI_TYPE_UINT8) { + return ""; + } else if (data_type == JPEGLI_TYPE_UINT16) { + retval = "UINT16"; + } else if (data_type == JPEGLI_TYPE_FLOAT) { + retval = "FLOAT"; + } + if (endianness == JPEGLI_LITTLE_ENDIAN) { + retval += "LE"; + } else if (endianness == JPEGLI_BIG_ENDIAN) { + retval += "BE"; + } + return retval; +} + +std::string SamplingId(const CompressParams& jparams) { + std::stringstream os; + Check(jparams.h_sampling.size() == jparams.v_sampling.size()); + if (!jparams.h_sampling.empty()) { + size_t len = jparams.h_sampling.size(); + while (len > 1 && jparams.h_sampling[len - 1] == 1 && + jparams.v_sampling[len - 1] == 1) { + --len; + } + os << "SAMP"; + for (size_t i = 0; i < len; ++i) { + if (i > 0) os << "_"; + os << jparams.h_sampling[i] << "x" << jparams.v_sampling[i]; + } + } + return os.str(); +} + +std::ostream& operator<<(std::ostream& os, const TestImage& input) { + os << input.xsize << "x" << input.ysize; + os << IOMethodName(input.data_type, input.endianness); + if (input.color_space != JCS_RGB) { + os << "InputColor" + << ColorSpaceName(static_cast(input.color_space)); + } + if (input.color_space == JCS_UNKNOWN) { + os << input.components; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, const CompressParams& jparams) { + os << "Q" << jparams.quality; + os << SamplingId(jparams); + if (jparams.set_jpeg_colorspace) { + os << "JpegColor" + << ColorSpaceName(static_cast(jparams.jpeg_color_space)); + } + if (!jparams.comp_ids.empty()) { + os << "CID"; + for (int cid : jparams.comp_ids) { + os << cid; + } + } + if (!jparams.quant_indexes.empty()) { + os << "QIDX"; + for (int qi : jparams.quant_indexes) { + os << qi; + } + for (const auto& table : jparams.quant_tables) { + os << "TABLE" << table.slot_idx << "T" << table.table_type << "F" + << table.scale_factor + << (table.add_raw ? "R" + : table.force_baseline ? "B" + : ""); + } + } + if (jparams.progressive_mode >= 0) { + os << "P" << jparams.progressive_mode; + } else if (jparams.simple_progression) { + os << "Psimple"; + } + if (jparams.optimize_coding == 1) { + os << "OptimizedCode"; + } else if (jparams.optimize_coding == 0) { + os << "FixedCode"; + if (jparams.use_flat_dc_luma_code) { + os << "FlatDCLuma"; + } else if (jparams.omit_standard_tables) { + os << "OmitDHT"; + } + } + if (!jparams.use_adaptive_quantization) { + os << "NoAQ"; + } + if (jparams.restart_interval > 0) { + os << "R" << jparams.restart_interval; + } + if (jparams.restart_in_rows > 0) { + os << "RR" << jparams.restart_in_rows; + } + if (jparams.xyb_mode) { + os << "XYB"; + } else if (jparams.libjpeg_mode) { + os << "Libjpeg"; + } + if (jparams.override_JFIF >= 0) { + os << (jparams.override_JFIF ? "AddJFIF" : "NoJFIF"); + } + if (jparams.override_Adobe >= 0) { + os << (jparams.override_Adobe ? "AddAdobe" : "NoAdobe"); + } + if (jparams.add_marker) { + os << "AddMarker"; + } + if (!jparams.icc.empty()) { + os << "ICCSize" << jparams.icc.size(); + } + if (jparams.smoothing_factor != 0) { + os << "SF" << jparams.smoothing_factor; + } + return os; +} + +jxl::Status SetNumChannels(J_COLOR_SPACE colorspace, size_t* channels) { + if (colorspace == JCS_GRAYSCALE) { + *channels = 1; + } else if (colorspace == JCS_RGB || colorspace == JCS_YCbCr || + colorspace == JCS_EXT_RGB || colorspace == JCS_EXT_BGR) { + *channels = 3; + } else if (colorspace == JCS_CMYK || colorspace == JCS_YCCK || + colorspace == JCS_EXT_RGBA || colorspace == JCS_EXT_BGRA || + colorspace == JCS_EXT_ARGB || colorspace == JCS_EXT_ABGR) { + *channels = 4; + } else if (colorspace == JCS_UNKNOWN) { + JXL_ENSURE(*channels <= 4); + } else { + return JXL_FAILURE("Unsupported colorspace: %d", + static_cast(colorspace)); + } + return true; +} + +void RGBToYCbCr(float r, float g, float b, float* y, float* cb, float* cr) { + *y = 0.299f * r + 0.587f * g + 0.114f * b; + *cb = -0.168736f * r - 0.331264f * g + 0.5f * b + 0.5f; + *cr = 0.5f * r - 0.418688f * g - 0.081312f * b + 0.5f; +} + +void ConvertPixel(const uint8_t* input_rgb, uint8_t* out, + J_COLOR_SPACE colorspace, size_t num_channels, + JpegliDataType data_type = JPEGLI_TYPE_UINT8, + JXL_BOOL swap_endianness = JPEGLI_NATIVE_ENDIAN) { + const float kMul = 255.0f; + float r = input_rgb[0] / kMul; + float g = input_rgb[1] / kMul; + float b = input_rgb[2] / kMul; + uint8_t out8[MAX_COMPONENTS]; + if (colorspace == JCS_GRAYSCALE) { + const float Y = 0.299f * r + 0.587f * g + 0.114f * b; + out8[0] = static_cast(std::round(Y * kMul)); + } else if (colorspace == JCS_RGB || colorspace == JCS_EXT_RGB || + colorspace == JCS_EXT_RGBA) { + out8[0] = input_rgb[0]; + out8[1] = input_rgb[1]; + out8[2] = input_rgb[2]; + if (colorspace == JCS_EXT_RGBA) out8[3] = 255; + } else if (colorspace == JCS_EXT_BGR || colorspace == JCS_EXT_BGRA) { + out8[2] = input_rgb[0]; + out8[1] = input_rgb[1]; + out8[0] = input_rgb[2]; + if (colorspace == JCS_EXT_BGRA) out8[3] = 255; + } else if (colorspace == JCS_EXT_ABGR) { + out8[0] = 255; + out8[3] = input_rgb[0]; + out8[2] = input_rgb[1]; + out8[1] = input_rgb[2]; + } else if (colorspace == JCS_EXT_ARGB) { + out8[0] = 255; + out8[1] = input_rgb[0]; + out8[2] = input_rgb[1]; + out8[3] = input_rgb[2]; + } else if (colorspace == JCS_UNKNOWN) { + for (size_t c = 0; c < num_channels; ++c) { + out8[c] = input_rgb[std::min(2, c)]; + } + } else if (colorspace == JCS_YCbCr) { + float Y; + float Cb; + float Cr; + RGBToYCbCr(r, g, b, &Y, &Cb, &Cr); + out8[0] = static_cast(std::round(Y * kMul)); + out8[1] = static_cast(std::round(Cb * kMul)); + out8[2] = static_cast(std::round(Cr * kMul)); + } else if (colorspace == JCS_CMYK || colorspace == JCS_YCCK) { + float K = 1.0f - std::max(r, std::max(g, b)); + float scaleK = 1.0f / (1.0f - K); + r *= scaleK; + g *= scaleK; + b *= scaleK; + if (colorspace == JCS_CMYK) { + out8[0] = static_cast(std::round((1.0f - r) * kMul)); + out8[1] = static_cast(std::round((1.0f - g) * kMul)); + out8[2] = static_cast(std::round((1.0f - b) * kMul)); + } else if (colorspace == JCS_YCCK) { + float Y; + float Cb; + float Cr; + RGBToYCbCr(r, g, b, &Y, &Cb, &Cr); + out8[0] = static_cast(std::round(Y * kMul)); + out8[1] = static_cast(std::round(Cb * kMul)); + out8[2] = static_cast(std::round(Cr * kMul)); + } + out8[3] = static_cast(std::round(K * kMul)); + } else { + Check(false); + } + if (data_type == JPEGLI_TYPE_UINT8) { + memcpy(out, out8, num_channels); + } else if (data_type == JPEGLI_TYPE_UINT16) { + for (size_t c = 0; c < num_channels; ++c) { + uint16_t val = (out8[c] << 8) + out8[c]; + val |= 0x40; // Make little-endian and big-endian asymmetric + if (swap_endianness) { + val = JXL_BSWAP16(val); + } + memcpy(&out[sizeof(val) * c], &val, sizeof(val)); + } + } else if (data_type == JPEGLI_TYPE_FLOAT) { + for (size_t c = 0; c < num_channels; ++c) { + float val = out8[c] / 255.0f; + if (swap_endianness) { + val = BSwapFloat(val); + } + memcpy(&out[sizeof(val) * c], &val, sizeof(val)); + } + } +} + +void ConvertToGrayscale(TestImage* img) { + if (img->color_space == JCS_GRAYSCALE) return; + Check(img->data_type == JPEGLI_TYPE_UINT8); + bool rgb_pre_alpha = + img->color_space == JCS_EXT_ARGB || img->color_space == JCS_EXT_ABGR; + bool rgb_post_alpha = + img->color_space == JCS_EXT_RGBA || img->color_space == JCS_EXT_BGRA; + bool rgb_alpha = rgb_pre_alpha || rgb_post_alpha; + bool is_rgb = img->color_space == JCS_RGB || + img->color_space == JCS_EXT_RGB || + img->color_space == JCS_EXT_BGR || rgb_alpha; + bool switch_br = img->color_space == JCS_EXT_BGR || + img->color_space == JCS_EXT_ABGR || + img->color_space == JCS_EXT_BGRA; + size_t stride = rgb_alpha ? 4 : 3; + size_t offset = rgb_pre_alpha ? 1 : 0; + for (size_t i = offset; i < img->pixels.size(); i += stride) { + if (is_rgb) { + if (switch_br) std::swap(img->pixels[i], img->pixels[i + 2]); + ConvertPixel(&img->pixels[i], &img->pixels[i / stride], JCS_GRAYSCALE, 1); + } else if (img->color_space == JCS_YCbCr) { + img->pixels[i / 3] = img->pixels[i]; + } + } + img->pixels.resize(img->pixels.size() / 3); + img->color_space = JCS_GRAYSCALE; + img->components = 1; +} + +void GeneratePixels(TestImage* img) { + JXL_ASSIGN_OR_QUIT(std::vector imgdata, + ReadTestData("jxl/flower/flower.pnm"), + "Failed to read test data"); + size_t xsize; + size_t ysize; + size_t channels; + size_t bitdepth; + std::vector pixels; + Check(ReadPNM(imgdata, &xsize, &ysize, &channels, &bitdepth, &pixels)); + if (img->xsize == 0) img->xsize = xsize; + if (img->ysize == 0) img->ysize = ysize; + Check(img->xsize <= xsize); + Check(img->ysize <= ysize); + Check(3 == channels); + Check(8 == bitdepth); + size_t in_bytes_per_pixel = channels; + size_t in_stride = xsize * in_bytes_per_pixel; + size_t x0 = (xsize - img->xsize) / 2; + size_t y0 = (ysize - img->ysize) / 2; + Check(SetNumChannels(static_cast(img->color_space), + &img->components)); + size_t out_bytes_per_pixel = + jpegli_bytes_per_sample(img->data_type) * img->components; + size_t out_stride = img->xsize * out_bytes_per_pixel; + bool swap_endianness = + (img->endianness == JPEGLI_LITTLE_ENDIAN && !IsLittleEndian()) || + (img->endianness == JPEGLI_BIG_ENDIAN && IsLittleEndian()); + img->pixels.resize(img->ysize * out_stride); + for (size_t iy = 0; iy < img->ysize; ++iy) { + size_t y = y0 + iy; + for (size_t ix = 0; ix < img->xsize; ++ix) { + size_t x = x0 + ix; + size_t idx_in = y * in_stride + x * in_bytes_per_pixel; + size_t idx_out = iy * out_stride + ix * out_bytes_per_pixel; + ConvertPixel(&pixels[idx_in], &img->pixels[idx_out], + static_cast(img->color_space), + img->components, img->data_type, + TO_JXL_BOOL(swap_endianness)); + } + } +} + +void GenerateRawData(const CompressParams& jparams, TestImage* img) { + for (size_t c = 0; c < img->components; ++c) { + size_t xsize = jparams.comp_width(*img, c); + size_t ysize = jparams.comp_height(*img, c); + size_t factor_y = jparams.max_v_sample() / jparams.v_samp(c); + size_t factor_x = jparams.max_h_sample() / jparams.h_samp(c); + size_t factor = factor_x * factor_y; + std::vector plane(ysize * xsize); + size_t bytes_per_pixel = img->components; + for (size_t y = 0; y < ysize; ++y) { + for (size_t x = 0; x < xsize; ++x) { + int result = 0; + for (size_t iy = 0; iy < factor_y; ++iy) { + size_t yy = std::min(y * factor_y + iy, img->ysize - 1); + for (size_t ix = 0; ix < factor_x; ++ix) { + size_t xx = std::min(x * factor_x + ix, img->xsize - 1); + size_t pixel_ix = (yy * img->xsize + xx) * bytes_per_pixel + c; + result += img->pixels[pixel_ix]; + } + } + result = static_cast((result + factor / 2) / factor); + plane[y * xsize + x] = result; + } + } + img->raw_data.emplace_back(std::move(plane)); + } +} + +void GenerateCoeffs(const CompressParams& jparams, TestImage* img) { + for (size_t c = 0; c < img->components; ++c) { + int xsize_blocks = jparams.comp_width(*img, c) / DCTSIZE; + int ysize_blocks = jparams.comp_height(*img, c) / DCTSIZE; + std::vector plane(ysize_blocks * xsize_blocks * DCTSIZE2); + for (int by = 0; by < ysize_blocks; ++by) { + for (int bx = 0; bx < xsize_blocks; ++bx) { + JCOEF* block = &plane[(by * xsize_blocks + bx) * DCTSIZE2]; + for (int k = 0; k < DCTSIZE2; ++k) { + block[k] = (bx - by) / (k + 1); + } + } + } + img->coeffs.emplace_back(std::move(plane)); + } +} + +void EncodeWithJpegli(const TestImage& input, const CompressParams& jparams, + j_compress_ptr cinfo) { + cinfo->image_width = input.xsize; + cinfo->image_height = input.ysize; + cinfo->input_components = input.components; + if (jparams.xyb_mode) { + jpegli_set_xyb_mode(cinfo); + } + if (jparams.libjpeg_mode) { + jpegli_enable_adaptive_quantization(cinfo, FALSE); + jpegli_use_standard_quant_tables(cinfo); + jpegli_set_progressive_level(cinfo, 0); + } + jpegli_set_defaults(cinfo); + cinfo->in_color_space = static_cast(input.color_space); + jpegli_default_colorspace(cinfo); + if (jparams.override_JFIF >= 0) { + cinfo->write_JFIF_header = jparams.override_JFIF; + } + if (jparams.override_Adobe >= 0) { + cinfo->write_Adobe_marker = jparams.override_Adobe; + } + if (jparams.set_jpeg_colorspace) { + jpegli_set_colorspace(cinfo, + static_cast(jparams.jpeg_color_space)); + } + if (!jparams.comp_ids.empty()) { + for (int c = 0; c < cinfo->num_components; ++c) { + cinfo->comp_info[c].component_id = jparams.comp_ids[c]; + } + } + if (!jparams.h_sampling.empty()) { + for (int c = 0; c < cinfo->num_components; ++c) { + cinfo->comp_info[c].h_samp_factor = jparams.h_sampling[c]; + cinfo->comp_info[c].v_samp_factor = jparams.v_sampling[c]; + } + } + jpegli_set_quality(cinfo, jparams.quality, TRUE); + if (!jparams.quant_indexes.empty()) { + for (int c = 0; c < cinfo->num_components; ++c) { + cinfo->comp_info[c].quant_tbl_no = jparams.quant_indexes[c]; + } + for (const auto& table : jparams.quant_tables) { + if (table.add_raw) { + cinfo->quant_tbl_ptrs[table.slot_idx] = + jpegli_alloc_quant_table(reinterpret_cast(cinfo)); + for (int k = 0; k < DCTSIZE2; ++k) { + cinfo->quant_tbl_ptrs[table.slot_idx]->quantval[k] = + table.quantval[k]; + } + cinfo->quant_tbl_ptrs[table.slot_idx]->sent_table = FALSE; + } else { + jpegli_add_quant_table(cinfo, table.slot_idx, table.basic_table.data(), + table.scale_factor, + TO_JXL_BOOL(table.force_baseline)); + } + } + } + if (jparams.simple_progression) { + jpegli_simple_progression(cinfo); + Check(jparams.progressive_mode == -1); + } + if (jparams.progressive_mode > 2) { + const ScanScript& script = kTestScript[jparams.progressive_mode - 3]; + cinfo->scan_info = script.scans; + cinfo->num_scans = script.num_scans; + } else if (jparams.progressive_mode >= 0) { + jpegli_set_progressive_level(cinfo, jparams.progressive_mode); + } + jpegli_set_input_format(cinfo, input.data_type, input.endianness); + jpegli_enable_adaptive_quantization( + cinfo, TO_JXL_BOOL(jparams.use_adaptive_quantization)); + cinfo->restart_interval = jparams.restart_interval; + cinfo->restart_in_rows = jparams.restart_in_rows; + cinfo->smoothing_factor = jparams.smoothing_factor; + if (jparams.optimize_coding == 1) { + cinfo->optimize_coding = TRUE; + } else if (jparams.optimize_coding == 0) { + cinfo->optimize_coding = FALSE; + } + cinfo->raw_data_in = TO_JXL_BOOL(!input.raw_data.empty()); + if (jparams.optimize_coding == 0 && jparams.use_flat_dc_luma_code) { + JHUFF_TBL* tbl = cinfo->dc_huff_tbl_ptrs[0]; + memset(tbl, 0, sizeof(*tbl)); + tbl->bits[4] = 15; + for (int i = 0; i < 15; ++i) tbl->huffval[i] = i; + } + if (input.coeffs.empty()) { + bool write_all_tables = TRUE; + if (jparams.optimize_coding == 0 && !jparams.use_flat_dc_luma_code && + jparams.omit_standard_tables) { + write_all_tables = FALSE; + cinfo->dc_huff_tbl_ptrs[0]->sent_table = TRUE; + cinfo->dc_huff_tbl_ptrs[1]->sent_table = TRUE; + cinfo->ac_huff_tbl_ptrs[0]->sent_table = TRUE; + cinfo->ac_huff_tbl_ptrs[1]->sent_table = TRUE; + } + jpegli_start_compress(cinfo, TO_JXL_BOOL(write_all_tables)); + if (jparams.add_marker) { + jpegli_write_marker(cinfo, kSpecialMarker0, kMarkerData, + sizeof(kMarkerData)); + jpegli_write_m_header(cinfo, kSpecialMarker1, sizeof(kMarkerData)); + for (uint8_t c : kMarkerData) { + jpegli_write_m_byte(cinfo, c); + } + for (size_t i = 0; i < kMarkerSequenceLen; ++i) { + jpegli_write_marker(cinfo, kMarkerSequence[i], kMarkerData, + ((i + 2) % sizeof(kMarkerData))); + } + } + if (!jparams.icc.empty()) { + jpegli_write_icc_profile(cinfo, jparams.icc.data(), jparams.icc.size()); + } + } + if (cinfo->raw_data_in) { + // Need to copy because jpeg API requires non-const pointers. + std::vector> raw_data = input.raw_data; + size_t max_lines = jparams.max_v_sample() * DCTSIZE; + std::vector> rowdata(cinfo->num_components); + std::vector data(cinfo->num_components); + for (int c = 0; c < cinfo->num_components; ++c) { + rowdata[c].resize(jparams.v_samp(c) * DCTSIZE); + data[c] = rowdata[c].data(); + } + while (cinfo->next_scanline < cinfo->image_height) { + for (int c = 0; c < cinfo->num_components; ++c) { + size_t cwidth = cinfo->comp_info[c].width_in_blocks * DCTSIZE; + size_t cheight = cinfo->comp_info[c].height_in_blocks * DCTSIZE; + size_t num_lines = jparams.v_samp(c) * DCTSIZE; + size_t y0 = (cinfo->next_scanline / max_lines) * num_lines; + for (size_t i = 0; i < num_lines; ++i) { + rowdata[c][i] = + (y0 + i < cheight ? &raw_data[c][(y0 + i) * cwidth] : nullptr); + } + } + size_t num_lines = jpegli_write_raw_data(cinfo, data.data(), max_lines); + Check(num_lines == max_lines); + } + } else if (!input.coeffs.empty()) { + j_common_ptr comptr = reinterpret_cast(cinfo); + jvirt_barray_ptr* coef_arrays = reinterpret_cast(( + *cinfo->mem->alloc_small)( + comptr, JPOOL_IMAGE, cinfo->num_components * sizeof(jvirt_barray_ptr))); + for (int c = 0; c < cinfo->num_components; ++c) { + size_t xsize_blocks = jparams.comp_width(input, c) / DCTSIZE; + size_t ysize_blocks = jparams.comp_height(input, c) / DCTSIZE; + coef_arrays[c] = (*cinfo->mem->request_virt_barray)( + comptr, JPOOL_IMAGE, FALSE, xsize_blocks, ysize_blocks, + cinfo->comp_info[c].v_samp_factor); + } + jpegli_write_coefficients(cinfo, coef_arrays); + if (jparams.add_marker) { + jpegli_write_marker(cinfo, kSpecialMarker0, kMarkerData, + sizeof(kMarkerData)); + jpegli_write_m_header(cinfo, kSpecialMarker1, sizeof(kMarkerData)); + for (uint8_t c : kMarkerData) { + jpegli_write_m_byte(cinfo, c); + } + } + for (int c = 0; c < cinfo->num_components; ++c) { + jpeg_component_info* comp = &cinfo->comp_info[c]; + for (size_t by = 0; by < comp->height_in_blocks; ++by) { + JBLOCKARRAY blocks = (*cinfo->mem->access_virt_barray)( + comptr, coef_arrays[c], by, 1, TRUE); + size_t stride = comp->width_in_blocks * sizeof(JBLOCK); + size_t offset = by * comp->width_in_blocks * DCTSIZE2; + memcpy(blocks[0], &input.coeffs[c][offset], stride); + } + } + } else { + size_t stride = cinfo->image_width * cinfo->input_components * + jpegli_bytes_per_sample(input.data_type); + std::vector row_bytes(stride); + for (size_t y = 0; y < cinfo->image_height; ++y) { + memcpy(row_bytes.data(), &input.pixels[y * stride], stride); + JSAMPROW row[] = {row_bytes.data()}; + jpegli_write_scanlines(cinfo, row, 1); + } + } + jpegli_finish_compress(cinfo); +} + +bool EncodeWithJpegli(const TestImage& input, const CompressParams& jparams, + std::vector* compressed) { + uint8_t* buffer = nullptr; + unsigned long buffer_size = 0; // NOLINT + jpeg_compress_struct cinfo; + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &buffer, &buffer_size); + EncodeWithJpegli(input, jparams, &cinfo); + return true; + }; + bool success = try_catch_block(); + jpegli_destroy_compress(&cinfo); + if (success) { + compressed->resize(buffer_size); + std::copy_n(buffer, buffer_size, compressed->data()); + } + if (buffer) std::free(buffer); + return success; +} + +int NumTestScanScripts() { return kNumTestScripts; } + +void DumpImage(const TestImage& image, const std::string& fn) { + Check(image.components == 1 || image.components == 3); + size_t bytes_per_sample = jpegli_bytes_per_sample(image.data_type); + uint32_t maxval = (1u << (8 * bytes_per_sample)) - 1; + char type = image.components == 1 ? '5' : '6'; + std::ofstream out(fn.c_str(), std::ofstream::binary); + out << "P" << type << "\n" + << image.xsize << " " << image.ysize << "\n" + << maxval << "\n"; + out.write(reinterpret_cast(image.pixels.data()), + image.pixels.size()); + out.close(); +} + +double DistanceRms(const TestImage& input, const TestImage& output, + size_t start_line, size_t num_lines, double* max_diff) { + size_t stride = input.xsize * input.components; + size_t start_offset = start_line * stride; + auto get_sample = [&](const TestImage& im, const std::vector& data, + size_t idx) -> double { + size_t bytes_per_sample = jpegli_bytes_per_sample(im.data_type); + bool is_little_endian = + (im.endianness == JPEGLI_LITTLE_ENDIAN || + (im.endianness == JPEGLI_NATIVE_ENDIAN && IsLittleEndian())); + size_t offset = start_offset + idx * bytes_per_sample; + Check(offset < data.size()); + const uint8_t* p = &data[offset]; + if (im.data_type == JPEGLI_TYPE_UINT8) { + static const double mul8 = 1.0 / 255.0; + return p[0] * mul8; + } else if (im.data_type == JPEGLI_TYPE_UINT16) { + static const double mul16 = 1.0 / 65535.0; + return (is_little_endian ? LoadLE16(p) : LoadBE16(p)) * mul16; + } else if (im.data_type == JPEGLI_TYPE_FLOAT) { + return (is_little_endian ? LoadLEFloat(p) : LoadBEFloat(p)); + } + return 0.0; + }; + double diff2 = 0.0; + size_t num_samples = 0; + if (max_diff) *max_diff = 0.0; + if (!input.pixels.empty() && !output.pixels.empty()) { + num_samples = num_lines * stride; + for (size_t i = 0; i < num_samples; ++i) { + double sample_orig = get_sample(input, input.pixels, i); + double sample_output = get_sample(output, output.pixels, i); + double diff = sample_orig - sample_output; + if (max_diff) *max_diff = std::max(*max_diff, 255.0 * std::abs(diff)); + diff2 += diff * diff; + } + } else { + Check(!input.raw_data.empty()); + Check(!output.raw_data.empty()); + for (size_t c = 0; c < input.raw_data.size(); ++c) { + Check(c < output.raw_data.size()); + num_samples += input.raw_data[c].size(); + for (size_t i = 0; i < input.raw_data[c].size(); ++i) { + double sample_orig = get_sample(input, input.raw_data[c], i); + double sample_output = get_sample(output, output.raw_data[c], i); + double diff = sample_orig - sample_output; + if (max_diff) *max_diff = std::max(*max_diff, 255.0 * std::abs(diff)); + diff2 += diff * diff; + } + } + } + return std::sqrt(diff2 / num_samples) * 255.0; +} + +double DistanceRms(const TestImage& input, const TestImage& output, + double* max_diff) { + return DistanceRms(input, output, 0, output.ysize, max_diff); +} + +void VerifyOutputImage(const TestImage& input, const TestImage& output, + size_t start_line, size_t num_lines, double max_rms, + double max_diff) { + double max_d; + double rms = DistanceRms(input, output, start_line, num_lines, &max_d); + printf("rms: %f, max_rms: %f, max_d: %f, max_diff: %f\n", rms, max_rms, + max_d, max_diff); + Check(rms <= max_rms); + Check(max_d <= max_diff); +} + +void VerifyOutputImage(const TestImage& input, const TestImage& output, + double max_rms, double max_diff) { + Check(output.xsize == input.xsize); + Check(output.ysize == input.ysize); + Check(output.components == input.components); + Check(output.color_space == input.color_space); + if (!input.coeffs.empty()) { + Check(input.coeffs.size() == input.components); + Check(output.coeffs.size() == input.components); + for (size_t c = 0; c < input.components; ++c) { + Check(output.coeffs[c].size() == input.coeffs[c].size()); + Check(0 == memcmp(input.coeffs[c].data(), output.coeffs[c].data(), + input.coeffs[c].size())); + } + } else { + VerifyOutputImage(input, output, 0, output.ysize, max_rms, max_diff); + } +} + +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/test_utils.h b/media/libjxl/src/lib/jpegli/test_utils.h new file mode 100644 index 0000000000..0ebd7ffbc8 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/test_utils.h @@ -0,0 +1,123 @@ +// 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_JPEGLI_TEST_UTILS_H_ +#define LIB_JPEGLI_TEST_UTILS_H_ + +#include +#include +#include +#include + +#include "lib/jpegli/test_params.h" +#include "lib/jpegli/types.h" +#include "lib/jxl/base/include_jpeglib.h" // NOLINT +#include "lib/jxl/base/status.h" + +namespace jpegli { + +#define ERROR_HANDLER_SETUP(flavor) \ + jpeg_error_mgr jerr; \ + jmp_buf env; \ + cinfo.err = flavor##_std_error(&jerr); \ + if (setjmp(env)) { \ + return false; \ + } \ + cinfo.client_data = reinterpret_cast(&env); \ + cinfo.err->error_exit = [](j_common_ptr cinfo) { \ + (*cinfo->err->output_message)(cinfo); \ + jmp_buf* env = reinterpret_cast(cinfo->client_data); \ + flavor##_destroy(cinfo); \ + longjmp(*env, 1); \ + }; + +std::string IOMethodName(JpegliDataType data_type, JpegliEndianness endianness); + +std::string ColorSpaceName(J_COLOR_SPACE colorspace); + +std::ostream& operator<<(std::ostream& os, const TestImage& input); + +std::ostream& operator<<(std::ostream& os, const CompressParams& jparams); + +int NumTestScanScripts(); + +void VerifyHeader(const CompressParams& jparams, j_decompress_ptr cinfo); +void VerifyScanHeader(const CompressParams& jparams, j_decompress_ptr cinfo); + +void SetDecompressParams(const DecompressParams& dparams, + j_decompress_ptr cinfo); + +void SetScanDecompressParams(const DecompressParams& dparams, + j_decompress_ptr cinfo, int scan_number); + +void CopyCoefficients(j_decompress_ptr cinfo, jvirt_barray_ptr* coef_arrays, + TestImage* output); + +void UnmapColors(uint8_t* row, size_t xsize, int components, + JSAMPARRAY colormap, size_t num_colors); + +std::string GetTestDataPath(const std::string& filename); +jxl::StatusOr> ReadTestData(const std::string& filename); + +class PNMParser { + public: + explicit PNMParser(const uint8_t* data, const size_t len) + : pos_(data), end_(data + len) {} + + // Sets "pos" to the first non-header byte/pixel on success. + bool ParseHeader(const uint8_t** pos, size_t* xsize, size_t* ysize, + size_t* num_channels, size_t* bitdepth); + + private: + static bool IsLineBreak(const uint8_t c) { return c == '\r' || c == '\n'; } + static bool IsWhitespace(const uint8_t c) { + return IsLineBreak(c) || c == '\t' || c == ' '; + } + + bool ParseUnsigned(size_t* number); + + bool SkipWhitespace(); + + const uint8_t* pos_; + const uint8_t* const end_; +}; + +bool ReadPNM(const std::vector& data, size_t* xsize, size_t* ysize, + size_t* num_channels, size_t* bitdepth, + std::vector* pixels); + +jxl::Status SetNumChannels(J_COLOR_SPACE colorspace, size_t* channels); + +void ConvertToGrayscale(TestImage* img); + +void GeneratePixels(TestImage* img); + +void GenerateRawData(const CompressParams& jparams, TestImage* img); + +void GenerateCoeffs(const CompressParams& jparams, TestImage* img); + +void EncodeWithJpegli(const TestImage& input, const CompressParams& jparams, + j_compress_ptr cinfo); + +bool EncodeWithJpegli(const TestImage& input, const CompressParams& jparams, + std::vector* compressed); + +double DistanceRms(const TestImage& input, const TestImage& output, + size_t start_line, size_t num_lines, + double* max_diff = nullptr); + +double DistanceRms(const TestImage& input, const TestImage& output, + double* max_diff = nullptr); + +void VerifyOutputImage(const TestImage& input, const TestImage& output, + size_t start_line, size_t num_lines, double max_rms, + double max_diff = 255.0); + +void VerifyOutputImage(const TestImage& input, const TestImage& output, + double max_rms, double max_diff = 255.0); + +} // namespace jpegli + +#endif // LIB_JPEGLI_TEST_UTILS_H_ diff --git a/media/libjxl/src/lib/jpegli/testing.h b/media/libjxl/src/lib/jpegli/testing.h new file mode 100644 index 0000000000..58df563f06 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/testing.h @@ -0,0 +1,37 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_JPEGLI_TESTING_H_ +#define LIB_JPEGLI_TESTING_H_ + +// GTest specific macros / wrappers. + +#include "gtest/gtest.h" + +// googletest before 1.10 didn't define INSTANTIATE_TEST_SUITE_P() but instead +// used INSTANTIATE_TEST_CASE_P which is now deprecated. +#ifdef INSTANTIATE_TEST_SUITE_P +#define JPEGLI_INSTANTIATE_TEST_SUITE_P INSTANTIATE_TEST_SUITE_P +#else +#define JPEGLI_INSTANTIATE_TEST_SUITE_P INSTANTIATE_TEST_CASE_P +#endif + +// Replacement for ASSERT_TRUE inside try-catch blocks. +#define JPEGLI_TEST_ENSURE_TRUE(C) \ + if (!(C)) return false; + +#define QUIT(M) FAIL() << M + +// Ensures that we don't make our test bounds too lax, effectively disabling the +// tests. +#define EXPECT_SLIGHTLY_BELOW(A, E) \ + { \ + double _actual = (A); \ + double _expected = (E); \ + EXPECT_LE(_actual, _expected); \ + EXPECT_GE(_actual, 0.75 * _expected); \ + } + +#endif // LIB_JPEGLI_TESTING_H_ diff --git a/media/libjxl/src/lib/jpegli/transcode_api_test.cc b/media/libjxl/src/lib/jpegli/transcode_api_test.cc new file mode 100644 index 0000000000..42d7e4f7fe --- /dev/null +++ b/media/libjxl/src/lib/jpegli/transcode_api_test.cc @@ -0,0 +1,141 @@ +// 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 "lib/jpegli/decode.h" +#include "lib/jpegli/encode.h" +#include "lib/jpegli/libjpeg_test_util.h" +#include "lib/jpegli/test_params.h" +#include "lib/jpegli/test_utils.h" +#include "lib/jpegli/testing.h" + +namespace jpegli { +namespace { + +void TranscodeWithJpegli(const std::vector& jpeg_input, + const CompressParams& jparams, + std::vector* jpeg_output) { + jpeg_decompress_struct dinfo = {}; + jpeg_compress_struct cinfo = {}; + uint8_t* transcoded_data = nullptr; + unsigned long transcoded_size; // NOLINT + const auto try_catch_block = [&]() -> bool { + ERROR_HANDLER_SETUP(jpegli); + dinfo.err = cinfo.err; + dinfo.client_data = cinfo.client_data; + jpegli_create_decompress(&dinfo); + jpegli_mem_src(&dinfo, jpeg_input.data(), jpeg_input.size()); + EXPECT_EQ(JPEG_REACHED_SOS, + jpegli_read_header(&dinfo, /*require_image=*/TRUE)); + jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(&dinfo); + JPEGLI_TEST_ENSURE_TRUE(coef_arrays != nullptr); + jpegli_create_compress(&cinfo); + jpegli_mem_dest(&cinfo, &transcoded_data, &transcoded_size); + jpegli_copy_critical_parameters(&dinfo, &cinfo); + jpegli_set_progressive_level(&cinfo, jparams.progressive_mode); + cinfo.optimize_coding = jparams.optimize_coding; + jpegli_write_coefficients(&cinfo, coef_arrays); + jpegli_finish_compress(&cinfo); + jpegli_finish_decompress(&dinfo); + return true; + }; + ASSERT_TRUE(try_catch_block()); + jpegli_destroy_decompress(&dinfo); + jpegli_destroy_compress(&cinfo); + if (transcoded_data) { + jpeg_output->assign(transcoded_data, transcoded_data + transcoded_size); + free(transcoded_data); + } +} + +struct TestConfig { + TestImage input; + CompressParams jparams; +}; + +class TranscodeAPITestParam : public ::testing::TestWithParam {}; + +TEST_P(TranscodeAPITestParam, TestAPI) { + TestConfig config = GetParam(); + CompressParams& jparams = config.jparams; + GeneratePixels(&config.input); + + // Start with sequential non-optimized jpeg. + jparams.progressive_mode = 0; + jparams.optimize_coding = 0; + std::vector compressed; + ASSERT_TRUE(EncodeWithJpegli(config.input, jparams, &compressed)); + TestImage output0; + DecodeWithLibjpeg(jparams, DecompressParams(), compressed, &output0); + + // Transcode first to a sequential optimized jpeg, and then further to + // a progressive jpeg. + for (int progr : {0, 2}) { + std::vector transcoded; + jparams.progressive_mode = progr; + jparams.optimize_coding = 1; + TranscodeWithJpegli(compressed, jparams, &transcoded); + + // We expect a size reduction of at least 2%. + EXPECT_LT(transcoded.size(), compressed.size() * 0.98f); + + // Verify that transcoding is lossless. + TestImage output1; + DecodeWithLibjpeg(jparams, DecompressParams(), transcoded, &output1); + ASSERT_EQ(output0.pixels.size(), output1.pixels.size()); + EXPECT_EQ(0, memcmp(output0.pixels.data(), output1.pixels.data(), + output0.pixels.size())); + compressed = transcoded; + } +} + +std::vector GenerateTests() { + std::vector all_tests; + const size_t xsize0 = 1024; + const size_t ysize0 = 768; + for (int dxsize : {0, 1, 8, 9}) { + for (int dysize : {0, 1, 8, 9}) { + for (int h_sampling : {1, 2}) { + for (int v_sampling : {1, 2}) { + TestConfig config; + config.input.xsize = xsize0 + dxsize; + config.input.ysize = ysize0 + dysize; + config.jparams.h_sampling = {h_sampling, 1, 1}; + config.jparams.v_sampling = {v_sampling, 1, 1}; + all_tests.push_back(config); + } + } + } + } + return all_tests; +} + +std::ostream& operator<<(std::ostream& os, const TestConfig& c) { + os << c.input; + os << c.jparams; + return os; +} + +std::string TestDescription( + const testing::TestParamInfo& info) { + std::stringstream name; + name << info.param; + return name.str(); +} + +JPEGLI_INSTANTIATE_TEST_SUITE_P(TranscodeAPITest, TranscodeAPITestParam, + testing::ValuesIn(GenerateTests()), + TestDescription); + +} // namespace +} // namespace jpegli diff --git a/media/libjxl/src/lib/jpegli/transpose-inl.h b/media/libjxl/src/lib/jpegli/transpose-inl.h new file mode 100644 index 0000000000..cdc289f96c --- /dev/null +++ b/media/libjxl/src/lib/jpegli/transpose-inl.h @@ -0,0 +1,111 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#if defined(LIB_JPEGLI_TRANSPOSE_INL_H_) == defined(HWY_TARGET_TOGGLE) +#ifdef LIB_JPEGLI_TRANSPOSE_INL_H_ +#undef LIB_JPEGLI_TRANSPOSE_INL_H_ +#else +#define LIB_JPEGLI_TRANSPOSE_INL_H_ +#endif + +#include "lib/jxl/base/compiler_specific.h" + +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { +namespace { + +#if HWY_CAP_GE256 +JXL_INLINE void Transpose8x8Block(const float* JXL_RESTRICT from, + float* JXL_RESTRICT to) { + const HWY_CAPPED(float, 8) d; + auto i0 = Load(d, from); + auto i1 = Load(d, from + 1 * 8); + auto i2 = Load(d, from + 2 * 8); + auto i3 = Load(d, from + 3 * 8); + auto i4 = Load(d, from + 4 * 8); + auto i5 = Load(d, from + 5 * 8); + auto i6 = Load(d, from + 6 * 8); + auto i7 = Load(d, from + 7 * 8); + + const auto q0 = InterleaveLower(d, i0, i2); + const auto q1 = InterleaveLower(d, i1, i3); + const auto q2 = InterleaveUpper(d, i0, i2); + const auto q3 = InterleaveUpper(d, i1, i3); + const auto q4 = InterleaveLower(d, i4, i6); + const auto q5 = InterleaveLower(d, i5, i7); + const auto q6 = InterleaveUpper(d, i4, i6); + const auto q7 = InterleaveUpper(d, i5, i7); + + const auto r0 = InterleaveLower(d, q0, q1); + const auto r1 = InterleaveUpper(d, q0, q1); + const auto r2 = InterleaveLower(d, q2, q3); + const auto r3 = InterleaveUpper(d, q2, q3); + const auto r4 = InterleaveLower(d, q4, q5); + const auto r5 = InterleaveUpper(d, q4, q5); + const auto r6 = InterleaveLower(d, q6, q7); + const auto r7 = InterleaveUpper(d, q6, q7); + + i0 = ConcatLowerLower(d, r4, r0); + i1 = ConcatLowerLower(d, r5, r1); + i2 = ConcatLowerLower(d, r6, r2); + i3 = ConcatLowerLower(d, r7, r3); + i4 = ConcatUpperUpper(d, r4, r0); + i5 = ConcatUpperUpper(d, r5, r1); + i6 = ConcatUpperUpper(d, r6, r2); + i7 = ConcatUpperUpper(d, r7, r3); + + Store(i0, d, to); + Store(i1, d, to + 1 * 8); + Store(i2, d, to + 2 * 8); + Store(i3, d, to + 3 * 8); + Store(i4, d, to + 4 * 8); + Store(i5, d, to + 5 * 8); + Store(i6, d, to + 6 * 8); + Store(i7, d, to + 7 * 8); +} +#elif HWY_TARGET != HWY_SCALAR +JXL_INLINE void Transpose8x8Block(const float* JXL_RESTRICT from, + float* JXL_RESTRICT to) { + const HWY_CAPPED(float, 4) d; + for (size_t n = 0; n < 8; n += 4) { + for (size_t m = 0; m < 8; m += 4) { + auto p0 = Load(d, from + n * 8 + m); + auto p1 = Load(d, from + (n + 1) * 8 + m); + auto p2 = Load(d, from + (n + 2) * 8 + m); + auto p3 = Load(d, from + (n + 3) * 8 + m); + const auto q0 = InterleaveLower(d, p0, p2); + const auto q1 = InterleaveLower(d, p1, p3); + const auto q2 = InterleaveUpper(d, p0, p2); + const auto q3 = InterleaveUpper(d, p1, p3); + + const auto r0 = InterleaveLower(d, q0, q1); + const auto r1 = InterleaveUpper(d, q0, q1); + const auto r2 = InterleaveLower(d, q2, q3); + const auto r3 = InterleaveUpper(d, q2, q3); + Store(r0, d, to + m * 8 + n); + Store(r1, d, to + (1 + m) * 8 + n); + Store(r2, d, to + (2 + m) * 8 + n); + Store(r3, d, to + (3 + m) * 8 + n); + } + } +} +#else +static JXL_INLINE void Transpose8x8Block(const float* JXL_RESTRICT from, + float* JXL_RESTRICT to) { + for (size_t n = 0; n < 8; ++n) { + for (size_t m = 0; m < 8; ++m) { + to[8 * n + m] = from[8 * m + n]; + } + } +} +#endif + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); +#endif // LIB_JPEGLI_TRANSPOSE_INL_H_ diff --git a/media/libjxl/src/lib/jpegli/types.h b/media/libjxl/src/lib/jpegli/types.h new file mode 100644 index 0000000000..c0c0450c18 --- /dev/null +++ b/media/libjxl/src/lib/jpegli/types.h @@ -0,0 +1,38 @@ +// 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_JPEGLI_TYPES_H_ +#define LIB_JPEGLI_TYPES_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +// +// New API structs and functions that are not available in libjpeg +// +// NOTE: This part of the API is still experimental and will probably change in +// the future. +// + +typedef enum { + JPEGLI_TYPE_FLOAT = 0, + JPEGLI_TYPE_UINT8 = 2, + JPEGLI_TYPE_UINT16 = 3, +} JpegliDataType; + +typedef enum { + JPEGLI_NATIVE_ENDIAN = 0, + JPEGLI_LITTLE_ENDIAN = 1, + JPEGLI_BIG_ENDIAN = 2, +} JpegliEndianness; + +int jpegli_bytes_per_sample(JpegliDataType data_type); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // LIB_JPEGLI_TYPES_H_ diff --git a/media/libjxl/src/lib/jpegli/upsample.cc b/media/libjxl/src/lib/jpegli/upsample.cc new file mode 100644 index 0000000000..7dae841b8a --- /dev/null +++ b/media/libjxl/src/lib/jpegli/upsample.cc @@ -0,0 +1,137 @@ +// 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/jpegli/upsample.h" + +#include + +#undef HWY_TARGET_INCLUDE +#define HWY_TARGET_INCLUDE "lib/jpegli/upsample.cc" +#include +#include + +HWY_BEFORE_NAMESPACE(); +namespace jpegli { +namespace HWY_NAMESPACE { + +// These templates are not found via ADL. +using hwy::HWY_NAMESPACE::Mul; +using hwy::HWY_NAMESPACE::MulAdd; +using hwy::HWY_NAMESPACE::Vec; + +#if HWY_CAP_GE512 +using hwy::HWY_NAMESPACE::Half; +using hwy::HWY_NAMESPACE::Vec; +template +HWY_INLINE Vec>> Quarter(const DF df, V v) { + using HF = Half; + using HHF = Half; + auto half = i >= 2 ? UpperHalf(HF(), v) : LowerHalf(HF(), v); + return i & 1 ? UpperHalf(HHF(), half) : LowerHalf(HHF(), half); +} + +template +HWY_INLINE Vec Concat4(const DF df, V v0, V v1, V v2, V v3) { + using HF = Half; + return Combine(DF(), Combine(HF(), v3, v2), Combine(HF(), v1, v0)); +} + +#endif + +// Stores v0[0], v1[0], v0[1], v1[1], ... to mem, in this order. Mem must be +// aligned. +template +void StoreInterleaved(const DF df, V v0, V v1, T* mem) { + static_assert(sizeof(T) == 4, "only use StoreInterleaved for 4-byte types"); +#if HWY_TARGET == HWY_SCALAR + Store(v0, df, mem); + Store(v1, df, mem + 1); +#elif !HWY_CAP_GE256 + Store(InterleaveLower(df, v0, v1), df, mem); + Store(InterleaveUpper(df, v0, v1), df, mem + Lanes(df)); +#else + if (!HWY_CAP_GE512 || Lanes(df) == 8) { + auto t0 = InterleaveLower(df, v0, v1); + auto t1 = InterleaveUpper(df, v0, v1); + Store(ConcatLowerLower(df, t1, t0), df, mem); + Store(ConcatUpperUpper(df, t1, t0), df, mem + Lanes(df)); + } else { +#if HWY_CAP_GE512 + auto t0 = InterleaveLower(df, v0, v1); + auto t1 = InterleaveUpper(df, v0, v1); + Store(Concat4(df, Quarter<0>(df, t0), Quarter<0>(df, t1), + Quarter<1>(df, t0), Quarter<1>(df, t1)), + df, mem); + Store(Concat4(df, Quarter<2>(df, t0), Quarter<2>(df, t1), + Quarter<3>(df, t0), Quarter<3>(df, t1)), + df, mem + Lanes(df)); +#endif + } +#endif +} + +void Upsample2Horizontal(float* JXL_RESTRICT row, + float* JXL_RESTRICT scratch_space, size_t len_out) { + HWY_FULL(float) df; + auto threefour = Set(df, 0.75f); + auto onefour = Set(df, 0.25f); + const size_t len_in = (len_out + 1) >> 1; + memcpy(scratch_space, row, len_in * sizeof(row[0])); + scratch_space[-1] = scratch_space[0]; + scratch_space[len_in] = scratch_space[len_in - 1]; + for (size_t x = 0; x < len_in; x += Lanes(df)) { + auto current = Mul(Load(df, scratch_space + x), threefour); + auto prev = LoadU(df, scratch_space + x - 1); + auto next = LoadU(df, scratch_space + x + 1); + auto left = MulAdd(onefour, prev, current); + auto right = MulAdd(onefour, next, current); + StoreInterleaved(df, left, right, row + x * 2); + } +} + +void Upsample2Vertical(const float* JXL_RESTRICT row_top, + const float* JXL_RESTRICT row_mid, + const float* JXL_RESTRICT row_bot, + float* JXL_RESTRICT row_out0, + float* JXL_RESTRICT row_out1, size_t len) { + HWY_FULL(float) df; + auto threefour = Set(df, 0.75f); + auto onefour = Set(df, 0.25f); + for (size_t x = 0; x < len; x += Lanes(df)) { + auto it = Load(df, row_top + x); + auto im = Load(df, row_mid + x); + auto ib = Load(df, row_bot + x); + auto im_scaled = Mul(im, threefour); + Store(MulAdd(it, onefour, im_scaled), df, row_out0 + x); + Store(MulAdd(ib, onefour, im_scaled), df, row_out1 + x); + } +} + +// NOLINTNEXTLINE(google-readability-namespace-comments) +} // namespace HWY_NAMESPACE +} // namespace jpegli +HWY_AFTER_NAMESPACE(); + +#if HWY_ONCE +namespace jpegli { + +HWY_EXPORT(Upsample2Horizontal); +HWY_EXPORT(Upsample2Vertical); + +void Upsample2Horizontal(float* JXL_RESTRICT row, + float* JXL_RESTRICT scratch_space, size_t len_out) { + HWY_DYNAMIC_DISPATCH(Upsample2Horizontal)(row, scratch_space, len_out); +} + +void Upsample2Vertical(const float* JXL_RESTRICT row_top, + const float* JXL_RESTRICT row_mid, + const float* JXL_RESTRICT row_bot, + float* JXL_RESTRICT row_out0, + float* JXL_RESTRICT row_out1, size_t len) { + HWY_DYNAMIC_DISPATCH(Upsample2Vertical) + (row_top, row_mid, row_bot, row_out0, row_out1, len); +} +} // namespace jpegli +#endif // HWY_ONCE diff --git a/media/libjxl/src/lib/jpegli/upsample.h b/media/libjxl/src/lib/jpegli/upsample.h new file mode 100644 index 0000000000..1a057208dc --- /dev/null +++ b/media/libjxl/src/lib/jpegli/upsample.h @@ -0,0 +1,26 @@ +// 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_JPEGLI_UPSAMPLE_H_ +#define LIB_JPEGLI_UPSAMPLE_H_ + +#include + +#include "lib/jxl/base/compiler_specific.h" + +namespace jpegli { + +void Upsample2Horizontal(float* JXL_RESTRICT row, + float* JXL_RESTRICT scratch_space, size_t len_out); + +void Upsample2Vertical(const float* JXL_RESTRICT row_top, + const float* JXL_RESTRICT row_mid, + const float* JXL_RESTRICT row_bot, + float* JXL_RESTRICT row_out0, + float* JXL_RESTRICT row_out1, size_t len); + +} // namespace jpegli + +#endif // LIB_JPEGLI_UPSAMPLE_H_ diff --git a/media/libjxl/src/lib/jxl.cmake b/media/libjxl/src/lib/jxl.cmake index 72c07f48e5..59b2cca426 100644 --- a/media/libjxl/src/lib/jxl.cmake +++ b/media/libjxl/src/lib/jxl.cmake @@ -3,515 +3,184 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -# Lists all source files for the JPEG XL decoder library. These are also used -# by the encoder: the encoder uses both dec and enc ourse files, while the -# decoder uses only dec source files. -# TODO(lode): further prune these files and move to JPEGXL_INTERNAL_SOURCES_ENC: -# only those files that the decoder absolutely needs, and or not -# only for encoding, should be listed here. -set(JPEGXL_INTERNAL_SOURCES_DEC - jxl/ac_context.h - jxl/ac_strategy.cc - jxl/ac_strategy.h - jxl/alpha.cc - jxl/alpha.h - jxl/ans_common.cc - jxl/ans_common.h - jxl/ans_params.h - jxl/aux_out.cc - jxl/aux_out.h - jxl/aux_out_fwd.h - 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/compiler_specific.h - jxl/base/data_parallel.cc - jxl/base/data_parallel.h - jxl/base/file_io.h - jxl/base/iaca.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/profiler.h - jxl/base/random.cc - jxl/base/random.h - jxl/base/sanitizer_definitions.h - jxl/base/scope_guard.h - jxl/base/span.h - jxl/base/status.h - jxl/base/thread_pool_internal.h - jxl/blending.cc - jxl/blending.h - jxl/box_content_decoder.cc - jxl/box_content_decoder.h - jxl/chroma_from_luma.cc - jxl/chroma_from_luma.h - jxl/codec_in_out.h - jxl/coeff_order.cc - jxl/coeff_order.h - jxl/coeff_order_fwd.h - jxl/color_encoding_internal.cc - jxl/color_encoding_internal.h - jxl/color_management.cc - jxl/color_management.h - jxl/common.h - jxl/compressed_dc.cc - jxl/compressed_dc.h - jxl/convolve-inl.h - jxl/convolve.h - jxl/convolve_separable5.cc - jxl/convolve_separable7.cc - jxl/convolve_slow.cc - jxl/convolve_symmetric3.cc - jxl/convolve_symmetric5.cc - jxl/dct-inl.h - jxl/dct_block-inl.h - jxl/dct_scales.cc - jxl/dct_scales.h - jxl/dct_util.h - jxl/dec_ans.cc - jxl/dec_ans.h - jxl/dec_bit_reader.h - jxl/dec_cache.cc - jxl/dec_cache.h - jxl/dec_context_map.cc - jxl/dec_context_map.h - jxl/dec_external_image.cc - jxl/dec_external_image.h - jxl/dec_frame.cc - jxl/dec_frame.h - jxl/dec_group.cc - jxl/dec_group.h - jxl/dec_group_border.cc - jxl/dec_group_border.h - jxl/dec_huffman.cc - jxl/dec_huffman.h - jxl/dec_modular.cc - jxl/dec_modular.h - jxl/dec_noise.cc - jxl/dec_noise.h - jxl/dec_patch_dictionary.cc - jxl/dec_patch_dictionary.h - jxl/dec_tone_mapping-inl.h - jxl/dec_transforms-inl.h - jxl/dec_xyb-inl.h - jxl/dec_xyb.cc - jxl/dec_xyb.h - jxl/decode.cc - jxl/decode_to_jpeg.cc - jxl/decode_to_jpeg.h - jxl/enc_bit_writer.cc - jxl/enc_bit_writer.h - jxl/entropy_coder.cc - jxl/entropy_coder.h - jxl/epf.cc - jxl/epf.h - jxl/exif.h - jxl/fast_dct-inl.h - jxl/fast_dct.cc - jxl/fast_dct.h - jxl/fast_dct128-inl.h - jxl/fast_dct16-inl.h - jxl/fast_dct256-inl.h - jxl/fast_dct32-inl.h - jxl/fast_dct64-inl.h - jxl/fast_dct8-inl.h - jxl/fast_math-inl.h - jxl/field_encodings.h - jxl/fields.cc - jxl/fields.h - jxl/frame_header.cc - jxl/frame_header.h - jxl/gauss_blur.cc - jxl/gauss_blur.h - jxl/headers.cc - jxl/headers.h - jxl/huffman_table.cc - jxl/huffman_table.h - jxl/icc_codec.cc - jxl/icc_codec.h - jxl/icc_codec_common.cc - jxl/icc_codec_common.h - jxl/image.cc - jxl/image.h - jxl/image_bundle.cc - jxl/image_bundle.h - jxl/image_metadata.cc - jxl/image_metadata.h - jxl/image_ops.h - jxl/jpeg/dec_jpeg_data.cc - jxl/jpeg/dec_jpeg_data.h - jxl/jpeg/dec_jpeg_data_writer.cc - jxl/jpeg/dec_jpeg_data_writer.h - jxl/jpeg/dec_jpeg_output_chunk.h - jxl/jpeg/dec_jpeg_serialization_state.h - jxl/jpeg/jpeg_data.cc - jxl/jpeg/jpeg_data.h - jxl/jxl_inspection.h - jxl/lehmer_code.h - jxl/linalg.h - jxl/loop_filter.cc - jxl/loop_filter.h - jxl/luminance.cc - jxl/luminance.h - jxl/memory_manager_internal.cc - jxl/memory_manager_internal.h - jxl/modular/encoding/context_predict.h - jxl/modular/encoding/dec_ma.cc - jxl/modular/encoding/dec_ma.h - jxl/modular/encoding/encoding.cc - jxl/modular/encoding/encoding.h - jxl/modular/encoding/ma_common.h - jxl/modular/modular_image.cc - jxl/modular/modular_image.h - jxl/modular/options.h - jxl/modular/transform/palette.h - jxl/modular/transform/rct.cc - jxl/modular/transform/rct.h - jxl/modular/transform/squeeze.cc - jxl/modular/transform/squeeze.h - jxl/modular/transform/transform.cc - jxl/modular/transform/transform.h - jxl/noise.h - jxl/opsin_params.cc - jxl/opsin_params.h - jxl/passes_state.cc - jxl/passes_state.h - jxl/patch_dictionary_internal.h - jxl/quant_weights.cc - jxl/quant_weights.h - jxl/quantizer-inl.h - jxl/quantizer.cc - jxl/quantizer.h - jxl/rational_polynomial-inl.h - jxl/render_pipeline/low_memory_render_pipeline.cc - jxl/render_pipeline/low_memory_render_pipeline.h - jxl/render_pipeline/render_pipeline.cc - jxl/render_pipeline/render_pipeline.h - jxl/render_pipeline/render_pipeline_stage.h - jxl/render_pipeline/simple_render_pipeline.cc - 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_epf.cc - jxl/render_pipeline/stage_epf.h - jxl/render_pipeline/stage_from_linear.cc - jxl/render_pipeline/stage_from_linear.h - jxl/render_pipeline/stage_gaborish.cc - jxl/render_pipeline/stage_gaborish.h - jxl/render_pipeline/stage_noise.cc - jxl/render_pipeline/stage_noise.h - jxl/render_pipeline/stage_patches.cc - jxl/render_pipeline/stage_patches.h - jxl/render_pipeline/stage_splines.cc - jxl/render_pipeline/stage_splines.h - jxl/render_pipeline/stage_spot.cc - jxl/render_pipeline/stage_spot.h - jxl/render_pipeline/stage_to_linear.cc - jxl/render_pipeline/stage_to_linear.h - jxl/render_pipeline/stage_tone_mapping.cc - jxl/render_pipeline/stage_tone_mapping.h - jxl/render_pipeline/stage_upsampling.cc - jxl/render_pipeline/stage_upsampling.h - jxl/render_pipeline/stage_write.cc - jxl/render_pipeline/stage_write.h - jxl/render_pipeline/stage_xyb.cc - jxl/render_pipeline/stage_xyb.h - jxl/render_pipeline/stage_ycbcr.cc - jxl/render_pipeline/stage_ycbcr.h - jxl/render_pipeline/test_render_pipeline_stages.h - jxl/sanitizers.h - jxl/simd_util-inl.h - jxl/size_constraints.h - jxl/splines.cc - jxl/splines.h - jxl/toc.cc - jxl/toc.h - jxl/transfer_functions-inl.h - jxl/transpose-inl.h - jxl/xorshift128plus-inl.h -) +include(jxl_lists.cmake) -# List of source files only needed by the encoder or by tools (including -# decoding tools), but not by the decoder library. -set(JPEGXL_INTERNAL_SOURCES_ENC - jxl/butteraugli/butteraugli.cc - jxl/butteraugli/butteraugli.h - jxl/butteraugli_wrapper.cc - jxl/enc_ac_strategy.cc - jxl/enc_ac_strategy.h - jxl/enc_adaptive_quantization.cc - jxl/enc_adaptive_quantization.h - jxl/enc_ans.cc - jxl/enc_ans.h - jxl/enc_ans_params.h - jxl/enc_ar_control_field.cc - jxl/enc_ar_control_field.h - jxl/enc_butteraugli_comparator.cc - jxl/enc_butteraugli_comparator.h - jxl/enc_butteraugli_pnorm.cc - jxl/enc_butteraugli_pnorm.h - jxl/enc_cache.cc - jxl/enc_cache.h - jxl/enc_chroma_from_luma.cc - jxl/enc_chroma_from_luma.h - jxl/enc_cluster.cc - jxl/enc_cluster.h - jxl/enc_coeff_order.cc - jxl/enc_coeff_order.h - jxl/enc_color_management.cc - jxl/enc_color_management.h - jxl/enc_comparator.cc - jxl/enc_comparator.h - jxl/enc_context_map.cc - jxl/enc_context_map.h - jxl/enc_detect_dots.cc - jxl/enc_detect_dots.h - jxl/enc_dot_dictionary.cc - jxl/enc_dot_dictionary.h - jxl/enc_entropy_coder.cc - jxl/enc_entropy_coder.h - jxl/enc_external_image.cc - jxl/enc_external_image.h - jxl/enc_file.cc - jxl/enc_file.h - jxl/enc_frame.cc - jxl/enc_frame.h - jxl/enc_gamma_correct.h - jxl/enc_group.cc - jxl/enc_group.h - jxl/enc_heuristics.cc - jxl/enc_heuristics.h - jxl/enc_huffman.cc - jxl/enc_huffman.h - jxl/enc_icc_codec.cc - jxl/enc_icc_codec.h - jxl/enc_image_bundle.cc - jxl/enc_image_bundle.h - jxl/enc_jxl_skcms.h - jxl/enc_modular.cc - jxl/enc_modular.h - jxl/enc_noise.cc - jxl/enc_noise.h - jxl/enc_params.h - jxl/enc_patch_dictionary.cc - jxl/enc_patch_dictionary.h - jxl/enc_photon_noise.cc - jxl/enc_photon_noise.h - jxl/enc_quant_weights.cc - jxl/enc_quant_weights.h - jxl/enc_splines.cc - jxl/enc_splines.h - jxl/enc_toc.cc - jxl/enc_toc.h - jxl/enc_transforms-inl.h - jxl/enc_transforms.cc - jxl/enc_transforms.h - jxl/enc_xyb.cc - jxl/enc_xyb.h - jxl/encode.cc - jxl/encode_internal.h - jxl/gaborish.cc - jxl/gaborish.h - jxl/huffman_tree.cc - jxl/huffman_tree.h - jxl/jpeg/enc_jpeg_data.cc - jxl/jpeg/enc_jpeg_data.h - jxl/jpeg/enc_jpeg_data_reader.cc - jxl/jpeg/enc_jpeg_data_reader.h - jxl/jpeg/enc_jpeg_huffman_decode.cc - jxl/jpeg/enc_jpeg_huffman_decode.h - jxl/linalg.cc - jxl/modular/encoding/enc_debug_tree.cc - jxl/modular/encoding/enc_debug_tree.h - jxl/modular/encoding/enc_encoding.cc - jxl/modular/encoding/enc_encoding.h - jxl/modular/encoding/enc_ma.cc - jxl/modular/encoding/enc_ma.h - jxl/modular/transform/enc_palette.cc - jxl/modular/transform/enc_palette.h - jxl/modular/transform/enc_rct.cc - 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/optimize.cc - jxl/optimize.h - jxl/progressive_split.cc - jxl/progressive_split.h -) +if (JPEGXL_ENABLE_TOOLS OR JPEGXL_ENABLE_DEVTOOLS OR JPEGXL_ENABLE_BOXES) +list(APPEND JPEGXL_INTERNAL_DEC_SOURCES ${JPEGXL_INTERNAL_DEC_BOX_SOURCES}) +endif() + +if (JPEGXL_ENABLE_TRANSCODE_JPEG OR JPEGXL_ENABLE_TOOLS OR JPEGXL_ENABLE_DEVTOOLS) +list(APPEND JPEGXL_INTERNAL_DEC_SOURCES ${JPEGXL_INTERNAL_DEC_JPEG_SOURCES}) +endif() + +set(FJXL_COMPILE_FLAGS "-O3") + +set_source_files_properties(jxl/enc_fast_lossless.cc PROPERTIES COMPILE_FLAGS "${FJXL_COMPILE_FLAGS}") set(JPEGXL_DEC_INTERNAL_LIBS - brotlidec-static - brotlicommon-static hwy Threads::Threads ${ATOMICS_LIBRARIES} ) -if(JPEGXL_ENABLE_PROFILER) -list(APPEND JPEGXL_DEC_INTERNAL_LIBS jxl_profiler) +if (JPEGXL_ENABLE_TRANSCODE_JPEG OR JPEGXL_ENABLE_BOXES) +list(APPEND JPEGXL_DEC_INTERNAL_LIBS brotlidec brotlicommon) endif() set(JPEGXL_INTERNAL_LIBS ${JPEGXL_DEC_INTERNAL_LIBS} - brotlienc-static + brotlienc ) -# 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() - -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 (NOT JPEGXL_ENABLE_TRANSCODE_JPEG) +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 () +if (JPEGXL_ENABLE_BOXES) + list(APPEND JPEGXL_INTERNAL_FLAGS -DJPEGXL_ENABLE_BOXES=1) +else() + list(APPEND JPEGXL_INTERNAL_FLAGS -DJPEGXL_ENABLE_BOXES=0) +endif () + set(OBJ_COMPILE_DEFINITIONS - JPEGXL_MAJOR_VERSION=${JPEGXL_MAJOR_VERSION} - JPEGXL_MINOR_VERSION=${JPEGXL_MINOR_VERSION} - JPEGXL_PATCH_VERSION=${JPEGXL_PATCH_VERSION} # Used to determine if we are building the library when defined or just # including the library when not defined. This is public so libjxl shared # library gets this define too. JXL_INTERNAL_LIBRARY_BUILD ) +# Generate version.h +configure_file("jxl/version.h.in" "include/jxl/version.h") + +list(APPEND JPEGXL_INTERNAL_PUBLIC_HEADERS + ${CMAKE_CURRENT_BINARY_DIR}/include/jxl/version.h) + +# 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_export OBJECT ${JPEGXL_INTERNAL_PUBLIC_HEADERS} nothing.cc) +set_target_properties(jxl_export PROPERTIES + CXX_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN 1 + DEFINE_SYMBOL JXL_INTERNAL_LIBRARY_BUILD + LINKER_LANGUAGE CXX +) +generate_export_header(jxl_export + BASE_NAME JXL + EXPORT_FILE_NAME include/jxl/jxl_export.h) +# Place all public headers in a single directory. +foreach(path ${JPEGXL_INTERNAL_PUBLIC_HEADERS}) + configure_file( + ${path} + ${path} + COPYONLY + ) +endforeach() + +add_library(jxl_base INTERFACE) +target_include_directories(jxl_base SYSTEM BEFORE INTERFACE + "$" +) +target_include_directories(jxl_base BEFORE INTERFACE + ${PROJECT_SOURCE_DIR} + ${JXL_HWY_INCLUDE_DIRS} +) + +# On android, link with log to use android-related log functions. +if(CMAKE_SYSTEM_NAME STREQUAL "Android") + find_library(log-lib log) + if(log-lib) + target_link_libraries(jxl_base INTERFACE ${log-lib}) + target_compile_definitions(jxl_base INTERFACE USE_ANDROID_LOGGER) + endif() +endif() + +add_dependencies(jxl_base jxl_export) + # Decoder-only object library -add_library(jxl_dec-obj OBJECT ${JPEGXL_INTERNAL_SOURCES_DEC}) +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}) set_property(TARGET jxl_dec-obj PROPERTY POSITION_INDEPENDENT_CODE ON) -target_include_directories(jxl_dec-obj PUBLIC +target_include_directories(jxl_dec-obj BEFORE PUBLIC "$" - "$" - "$>" - "$>" + "${JXL_HWY_INCLUDE_DIRS}" + "$>" ) target_compile_definitions(jxl_dec-obj PUBLIC ${OBJ_COMPILE_DEFINITIONS} ) -if (JPEGXL_ENABLE_PROFILER) -target_link_libraries(jxl_dec-obj PUBLIC jxl_profiler) -endif() +target_link_libraries(jxl_dec-obj PUBLIC jxl_base) # Object library. This is used to hold the set of objects and properties. -add_library(jxl_enc-obj OBJECT ${JPEGXL_INTERNAL_SOURCES_ENC}) +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}) set_property(TARGET jxl_enc-obj PROPERTY POSITION_INDEPENDENT_CODE ON) -target_include_directories(jxl_enc-obj PUBLIC +target_include_directories(jxl_enc-obj BEFORE PUBLIC ${PROJECT_SOURCE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/include - $ - $ + ${JXL_HWY_INCLUDE_DIRS} + $ ) target_compile_definitions(jxl_enc-obj PUBLIC ${OBJ_COMPILE_DEFINITIONS} ) -if (JPEGXL_ENABLE_PROFILER) -target_link_libraries(jxl_enc-obj PUBLIC jxl_profiler) -endif() +target_link_libraries(jxl_enc-obj PUBLIC jxl_base) -#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 () - -# Generate version.h -configure_file("jxl/version.h.in" "include/jxl/version.h") - -# Headers for exporting/importing public headers -include(GenerateExportHeader) set_target_properties(jxl_dec-obj PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1 DEFINE_SYMBOL JXL_INTERNAL_LIBRARY_BUILD ) -target_include_directories(jxl_dec-obj PUBLIC - ${CMAKE_CURRENT_BINARY_DIR}/include) set_target_properties(jxl_enc-obj PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1 DEFINE_SYMBOL JXL_INTERNAL_LIBRARY_BUILD ) -generate_export_header(jxl_enc-obj - BASE_NAME JXL - EXPORT_FILE_NAME include/jxl/jxl_export.h) -target_include_directories(jxl_enc-obj PUBLIC - ${CMAKE_CURRENT_BINARY_DIR}/include) # Private static library. This exposes all the internal functions and is used # for tests. -add_library(jxl_dec-static STATIC +add_library(jxl_dec-internal STATIC $ + ${JXL_CMS_OBJECTS} +) +target_link_libraries(jxl_dec-internal PUBLIC + ${JPEGXL_COVERAGE_FLAGS} + ${JPEGXL_DEC_INTERNAL_LIBS} + jxl_base ) -target_link_libraries(jxl_dec-static - PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_DEC_INTERNAL_LIBS}) -target_include_directories(jxl_dec-static PUBLIC - "$" - "$" - "$") # 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}) -target_include_directories(jxl-static PUBLIC - "$" - "$" - "$") +# to do, remove $ here and depend on jxl_dec-internal +add_library(jxl-internal STATIC + ${JPEGXL_INTERNAL_OBJECTS} +) +target_link_libraries(jxl-internal PUBLIC + ${JPEGXL_COVERAGE_FLAGS} + ${JPEGXL_INTERNAL_LIBS} + jxl_cms + jxl_base +) +target_include_directories(jxl-internal BEFORE PUBLIC + "$") -# JXL_EXPORT is defined to "__declspec(dllimport)" automatically by CMake -# in Windows builds when including headers from the C API and compiling from -# outside the jxl library. This is required when using the shared library, -# 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-internal INTERFACE -DJXL_STATIC_DEFINE) +target_compile_definitions(jxl_dec-internal INTERFACE -DJXL_STATIC_DEFINE) +target_compile_definitions(jxl-internal INTERFACE -DJXL_STATIC_DEFINE) +target_compile_definitions(jxl_dec-internal INTERFACE -DJXL_STATIC_DEFINE) # TODO(deymo): Move TCMalloc linkage to the tools/ directory since the library # shouldn't do any allocs anyway. @@ -530,49 +199,27 @@ if(JPEGXL_ENABLE_TCMALLOC) "bug for details:\n" " https://github.com/gperftools/gperftools/issues/1204\n") endif() - target_link_libraries(jxl-static PUBLIC PkgConfig::TCMallocMinimal) + target_link_libraries(jxl-internal PUBLIC PkgConfig::TCMallocMinimal) endif() # JPEGXL_ENABLE_TCMALLOC -# Install the static library too, but as jxl.a file without the -static except -# in Windows. -if (NOT WIN32 OR MINGW) - set_target_properties(jxl-static PROPERTIES OUTPUT_NAME "jxl") - set_target_properties(jxl_dec-static PROPERTIES OUTPUT_NAME "jxl_dec") -endif() -install(TARGETS jxl-static DESTINATION ${CMAKE_INSTALL_LIBDIR}) -install(TARGETS jxl_dec-static DESTINATION ${CMAKE_INSTALL_LIBDIR}) - -if (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}) +# Public library. +add_library(jxl ${JPEGXL_INTERNAL_OBJECTS}) +strip_internal(JPEGXL_INTERNAL_SHARED_LIBS JPEGXL_INTERNAL_LIBS) +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. -target_include_directories(jxl PUBLIC - "$" - "$") 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}") + SOVERSION ${JPEGXL_LIBRARY_SOVERSION}) -# Public shared decoder library. -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}) +# Public decoder library. +add_library(jxl_dec $) +strip_internal(JPEGXL_DEC_INTERNAL_SHARED_LIBS JPEGXL_DEC_INTERNAL_LIBS) +target_link_libraries(jxl_dec PUBLIC ${JPEGXL_COVERAGE_FLAGS} jxl_base) target_link_libraries(jxl_dec PRIVATE ${JPEGXL_DEC_INTERNAL_SHARED_LIBS}) -# Shared library include path contains only the "include/" paths. -target_include_directories(jxl_dec PUBLIC - "${CMAKE_CURRENT_SOURCE_DIR}/include" - "${CMAKE_CURRENT_BINARY_DIR}/include") set_target_properties(jxl_dec PROPERTIES VERSION ${JPEGXL_LIBRARY_VERSION} - SOVERSION ${JPEGXL_LIBRARY_SOVERSION} - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") + SOVERSION ${JPEGXL_LIBRARY_SOVERSION}) # Check whether the linker support excluding libs set(LINKER_EXCLUDE_LIBS_FLAG "-Wl,--exclude-libs=ALL") @@ -581,6 +228,11 @@ list(APPEND CMAKE_EXE_LINKER_FLAGS ${LINKER_EXCLUDE_LIBS_FLAG}) check_c_source_compiles("int main(){return 0;}" LINKER_SUPPORT_EXCLUDE_LIBS) list(REMOVE_ITEM CMAKE_EXE_LINKER_FLAGS ${LINKER_EXCLUDE_LIBS_FLAG}) +if(NOT BUILD_SHARED_LIBS) + target_compile_definitions(jxl PUBLIC -DJXL_STATIC_DEFINE) + target_compile_definitions(jxl_dec PUBLIC -DJXL_STATIC_DEFINE) +endif() + # Add a jxl.version file as a version script to tag symbols with the # appropriate version number. This script is also used to limit what's exposed # in the shared library from the static dependencies bundled here. @@ -605,36 +257,24 @@ foreach(target IN ITEMS jxl jxl_dec) endif() endforeach() -# Only install libjxl shared library. The libjxl_dec is not installed since it +# Only install libjxl public library. The libjxl_dec is not installed since it # contains symbols also in libjxl which would conflict if programs try to use # both. install(TARGETS jxl RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) -else() -add_library(jxl ALIAS jxl-static) -add_library(jxl_dec ALIAS jxl_dec-static) -endif() # BUILD_SHARED_LIBS # Add a pkg-config file for libjxl. set(JPEGXL_LIBRARY_REQUIRES - "libhwy libbrotlicommon libbrotlienc libbrotlidec") -if(NOT JPEGXL_ENABLE_SKCMS) - set(JPEGXL_LIBRARY_REQUIRES "${JPEGXL_LIBRARY_REQUIRES} lcms2") -endif() + "libhwy libbrotlienc libbrotlidec libjxl_cms") -# Allow adding prefix if CMAKE_INSTALL_INCLUDEDIR not absolute. -if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") - set(PKGCONFIG_TARGET_INCLUDES "${CMAKE_INSTALL_INCLUDEDIR}") +if (BUILD_SHARED_LIBS) + set(JPEGXL_REQUIRES_TYPE "Requires.private") + set(JPEGXL_PRIVATE_LIBS "-lm ${PKGCONFIG_CXX_LIB}") 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}") + set(JPEGXL_REQUIRES_TYPE "Requires") + set(JPEGXL_PUBLIC_LIBS "-lm ${PKGCONFIG_CXX_LIB}") endif() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/jxl/libjxl.pc.in" diff --git a/media/libjxl/src/lib/jxl/ac_context.h b/media/libjxl/src/lib/jxl/ac_context.h index a2b9e046d1..6529a9bb88 100644 --- a/media/libjxl/src/lib/jxl/ac_context.h +++ b/media/libjxl/src/lib/jxl/ac_context.h @@ -62,7 +62,8 @@ static JXL_INLINE size_t ZeroDensityContext(size_t nonzeros_left, size_t k, size_t covered_blocks, size_t log2_covered_blocks, size_t prev) { - JXL_DASSERT((1u << log2_covered_blocks) == covered_blocks); + JXL_DASSERT((static_cast(1) << log2_covered_blocks) == + covered_blocks); nonzeros_left = (nonzeros_left + covered_blocks - 1) >> log2_covered_blocks; k >>= log2_covered_blocks; JXL_DASSERT(k > 0); @@ -109,7 +110,8 @@ struct BlockCtxMap { // Non-zero context is based on number of non-zeros and block context. // For better clustering, contexts with same number of non-zeros are grouped. constexpr uint32_t ZeroDensityContextsOffset(uint32_t block_ctx) const { - return num_ctxs * kNonZeroBuckets + kZeroDensityContextCount * block_ctx; + return static_cast(num_ctxs * kNonZeroBuckets + + kZeroDensityContextCount * block_ctx); } // Context map for AC coefficients consists of 2 blocks: @@ -121,7 +123,8 @@ struct BlockCtxMap { // number of non-zeros left and // index in scan order constexpr uint32_t NumACContexts() const { - return num_ctxs * (kNonZeroBuckets + kZeroDensityContextCount); + return static_cast(num_ctxs * + (kNonZeroBuckets + kZeroDensityContextCount)); } // Non-zero context is based on number of non-zeros and block context. @@ -134,7 +137,7 @@ struct BlockCtxMap { } else { ctx = 4 + non_zeros / 2; } - return ctx * num_ctxs + block_ctx; + return static_cast(ctx * num_ctxs + block_ctx); } BlockCtxMap() { diff --git a/media/libjxl/src/lib/jxl/ac_strategy.cc b/media/libjxl/src/lib/jxl/ac_strategy.cc index ada3bcb6f5..69e8ae6f47 100644 --- a/media/libjxl/src/lib/jxl/ac_strategy.cc +++ b/media/libjxl/src/lib/jxl/ac_strategy.cc @@ -5,17 +5,14 @@ #include "lib/jxl/ac_strategy.h" -#include +#include #include -#include // iota -#include +#include #include #include "lib/jxl/base/bits.h" -#include "lib/jxl/base/profiler.h" -#include "lib/jxl/common.h" -#include "lib/jxl/image_ops.h" +#include "lib/jxl/base/compiler_specific.h" namespace jxl { @@ -83,18 +80,23 @@ void AcStrategy::ComputeNaturalCoeffOrderLut(coeff_order_t* lut) const { CoeffOrderAndLut(*this, lut); } -// These definitions are needed before C++17. +#if JXL_CXX_LANG < JXL_CXX_17 constexpr size_t AcStrategy::kMaxCoeffBlocks; constexpr size_t AcStrategy::kMaxBlockDim; constexpr size_t AcStrategy::kMaxCoeffArea; +#endif -AcStrategyImage::AcStrategyImage(size_t xsize, size_t ysize) - : layers_(xsize, ysize) { - row_ = layers_.Row(0); - stride_ = layers_.PixelsPerRow(); +StatusOr AcStrategyImage::Create( + JxlMemoryManager* memory_manager, size_t xsize, size_t ysize) { + AcStrategyImage img; + JXL_ASSIGN_OR_RETURN(img.layers_, + ImageB::Create(memory_manager, xsize, ysize)); + img.row_ = img.layers_.Row(0); + img.stride_ = img.layers_.PixelsPerRow(); + return img; } -size_t AcStrategyImage::CountBlocks(AcStrategy::Type type) const { +size_t AcStrategyImage::CountBlocks(AcStrategyType type) const { size_t ret = 0; for (size_t y = 0; y < layers_.ysize(); y++) { const uint8_t* JXL_RESTRICT row = layers_.ConstRow(y); diff --git a/media/libjxl/src/lib/jxl/ac_strategy.h b/media/libjxl/src/lib/jxl/ac_strategy.h index 7d21167e6e..8c3191c791 100644 --- a/media/libjxl/src/lib/jxl/ac_strategy.h +++ b/media/libjxl/src/lib/jxl/ac_strategy.h @@ -6,14 +6,18 @@ #ifndef LIB_JXL_AC_STRATEGY_H_ #define LIB_JXL_AC_STRATEGY_H_ -#include -#include +#include +#include +#include #include // kMaxVectorSize +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/rect.h" #include "lib/jxl/base/status.h" #include "lib/jxl/coeff_order_fwd.h" -#include "lib/jxl/common.h" +#include "lib/jxl/frame_dimensions.h" +#include "lib/jxl/image.h" #include "lib/jxl/image_ops.h" // Defines the different kinds of transforms, and heuristics to choose between @@ -27,6 +31,53 @@ namespace jxl { +// Raw strategy types. +enum class AcStrategyType : uint32_t { + // Regular block size DCT + DCT = 0, + // Encode pixels without transforming + IDENTITY = 1, + // Use 2-by-2 DCT + DCT2X2 = 2, + // Use 4-by-4 DCT + DCT4X4 = 3, + // Use 16-by-16 DCT + DCT16X16 = 4, + // Use 32-by-32 DCT + DCT32X32 = 5, + // Use 16-by-8 DCT + DCT16X8 = 6, + // Use 8-by-16 DCT + DCT8X16 = 7, + // Use 32-by-8 DCT + DCT32X8 = 8, + // Use 8-by-32 DCT + DCT8X32 = 9, + // Use 32-by-16 DCT + DCT32X16 = 10, + // Use 16-by-32 DCT + DCT16X32 = 11, + // 4x8 and 8x4 DCT + DCT4X8 = 12, + DCT8X4 = 13, + // Corner-DCT. + AFV0 = 14, + AFV1 = 15, + AFV2 = 16, + AFV3 = 17, + // Larger DCTs + DCT64X64 = 18, + DCT64X32 = 19, + DCT32X64 = 20, + // No transforms smaller than 64x64 are allowed below. + DCT128X128 = 21, + DCT128X64 = 22, + DCT64X128 = 23, + DCT256X256 = 24, + DCT256X128 = 25, + DCT128X256 = 26 +}; + class AcStrategy { public: // Extremal values for the number of blocks/coefficients of a single strategy. @@ -38,55 +89,10 @@ class AcStrategy { static_assert((kMaxCoeffArea * sizeof(float)) % hwy::kMaxVectorSize == 0, "Coefficient area is not a multiple of vector size"); - // Raw strategy types. - enum Type : uint32_t { - // Regular block size DCT - DCT = 0, - // Encode pixels without transforming - IDENTITY = 1, - // Use 2-by-2 DCT - DCT2X2 = 2, - // Use 4-by-4 DCT - DCT4X4 = 3, - // Use 16-by-16 DCT - DCT16X16 = 4, - // Use 32-by-32 DCT - DCT32X32 = 5, - // Use 16-by-8 DCT - DCT16X8 = 6, - // Use 8-by-16 DCT - DCT8X16 = 7, - // Use 32-by-8 DCT - DCT32X8 = 8, - // Use 8-by-32 DCT - DCT8X32 = 9, - // Use 32-by-16 DCT - DCT32X16 = 10, - // Use 16-by-32 DCT - DCT16X32 = 11, - // 4x8 and 8x4 DCT - DCT4X8 = 12, - DCT8X4 = 13, - // Corner-DCT. - AFV0 = 14, - AFV1 = 15, - AFV2 = 16, - AFV3 = 17, - // Larger DCTs - DCT64X64 = 18, - DCT64X32 = 19, - DCT32X64 = 20, - DCT128X128 = 21, - DCT128X64 = 22, - DCT64X128 = 23, - DCT256X256 = 24, - DCT256X128 = 25, - DCT128X256 = 26, - // Marker for num of valid strategies. - kNumValidStrategies - }; + static constexpr uint8_t kNumValidStrategies = + static_cast(AcStrategyType::DCT128X256) + 1; - static constexpr uint32_t TypeBit(const Type type) { + static constexpr uint32_t TypeBit(const AcStrategyType type) { return 1u << static_cast(type); } @@ -96,15 +102,17 @@ class AcStrategy { JXL_INLINE bool IsMultiblock() const { constexpr uint32_t bits = - TypeBit(Type::DCT16X16) | TypeBit(Type::DCT32X32) | - TypeBit(Type::DCT16X8) | TypeBit(Type::DCT8X16) | - TypeBit(Type::DCT32X8) | TypeBit(Type::DCT8X32) | - TypeBit(Type::DCT16X32) | TypeBit(Type::DCT32X16) | - TypeBit(Type::DCT32X64) | TypeBit(Type::DCT64X32) | - TypeBit(Type::DCT64X64) | TypeBit(DCT64X128) | TypeBit(DCT128X64) | - TypeBit(DCT128X128) | TypeBit(DCT128X256) | TypeBit(DCT256X128) | - TypeBit(DCT256X256); - JXL_DASSERT(Strategy() < kNumValidStrategies); + TypeBit(AcStrategyType::DCT16X16) | TypeBit(AcStrategyType::DCT32X32) | + TypeBit(AcStrategyType::DCT16X8) | TypeBit(AcStrategyType::DCT8X16) | + TypeBit(AcStrategyType::DCT32X8) | TypeBit(AcStrategyType::DCT8X32) | + TypeBit(AcStrategyType::DCT16X32) | TypeBit(AcStrategyType::DCT32X16) | + TypeBit(AcStrategyType::DCT32X64) | TypeBit(AcStrategyType::DCT64X32) | + TypeBit(AcStrategyType::DCT64X64) | TypeBit(AcStrategyType::DCT64X128) | + TypeBit(AcStrategyType::DCT128X64) | + TypeBit(AcStrategyType::DCT128X128) | + TypeBit(AcStrategyType::DCT128X256) | + TypeBit(AcStrategyType::DCT256X128) | + TypeBit(AcStrategyType::DCT256X256); return ((1u << static_cast(Strategy())) & bits) != 0; } @@ -113,17 +121,16 @@ class AcStrategy { return static_cast(strategy_); } - JXL_INLINE Type Strategy() const { return strategy_; } + JXL_INLINE AcStrategyType Strategy() const { return strategy_; } // Inverse check static JXL_INLINE constexpr bool IsRawStrategyValid(int raw_strategy) { - return raw_strategy < static_cast(kNumValidStrategies) && - raw_strategy >= 0; + return raw_strategy < kNumValidStrategies && raw_strategy >= 0; } static JXL_INLINE AcStrategy FromRawStrategy(uint8_t raw_strategy) { - return FromRawStrategy(static_cast(raw_strategy)); + return FromRawStrategy(static_cast(raw_strategy)); } - static JXL_INLINE AcStrategy FromRawStrategy(Type raw_strategy) { + static JXL_INLINE AcStrategy FromRawStrategy(AcStrategyType raw_strategy) { JXL_DASSERT(IsRawStrategyValid(static_cast(raw_strategy))); return AcStrategy(raw_strategy, /*is_first=*/true); } @@ -144,7 +151,7 @@ class AcStrategy { 8, 4, 8, 16, 8, 16, 32, 16, 32}; static_assert(sizeof(kLut) / sizeof(*kLut) == kNumValidStrategies, "Update LUT"); - return kLut[size_t(strategy_)]; + return kLut[static_cast(strategy_)]; } JXL_INLINE size_t covered_blocks_y() const { @@ -153,7 +160,7 @@ class AcStrategy { 8, 8, 4, 16, 16, 8, 32, 32, 16}; static_assert(sizeof(kLut) / sizeof(*kLut) == kNumValidStrategies, "Update LUT"); - return kLut[size_t(strategy_)]; + return kLut[static_cast(strategy_)]; } JXL_INLINE size_t log2_covered_blocks() const { @@ -162,17 +169,17 @@ class AcStrategy { 6, 5, 5, 8, 7, 7, 10, 9, 9}; static_assert(sizeof(kLut) / sizeof(*kLut) == kNumValidStrategies, "Update LUT"); - return kLut[size_t(strategy_)]; + return kLut[static_cast(strategy_)]; } private: friend class AcStrategyRow; - JXL_INLINE AcStrategy(Type strategy, bool is_first) + JXL_INLINE AcStrategy(AcStrategyType strategy, bool is_first) : strategy_(strategy), is_first_(is_first) { JXL_DASSERT(IsMultiblock() || is_first == true); } - Type strategy_; + AcStrategyType strategy_; bool is_first_; }; @@ -181,7 +188,9 @@ class AcStrategyRow { public: explicit AcStrategyRow(const uint8_t* row) : row_(row) {} AcStrategy operator[](size_t x) const { - return AcStrategy(static_cast(row_[x] >> 1), row_[x] & 1); + AcStrategyType strategy = static_cast(row_[x] >> 1); + bool is_first = static_cast(row_[x] & 1); + return AcStrategy(strategy, is_first); } private: @@ -191,28 +200,31 @@ class AcStrategyRow { class AcStrategyImage { public: AcStrategyImage() = default; - AcStrategyImage(size_t xsize, size_t ysize); + static StatusOr Create(JxlMemoryManager* memory_manager, + size_t xsize, size_t ysize); + AcStrategyImage(AcStrategyImage&&) = default; AcStrategyImage& operator=(AcStrategyImage&&) = default; void FillDCT8(const Rect& rect) { - FillPlane((static_cast(AcStrategy::Type::DCT) << 1) | 1, + FillPlane((static_cast(AcStrategyType::DCT) << 1) | 1, &layers_, rect); } void FillDCT8() { FillDCT8(Rect(layers_)); } void FillInvalid() { FillImage(INVALID, &layers_); } - void Set(size_t x, size_t y, AcStrategy::Type type) { -#if JXL_ENABLE_ASSERT + Status Set(size_t x, size_t y, AcStrategyType type) { +#if (JXL_IS_DEBUG_BUILD) AcStrategy acs = AcStrategy::FromRawStrategy(type); -#endif // JXL_ENABLE_ASSERT - JXL_ASSERT(y + acs.covered_blocks_y() <= layers_.ysize()); - JXL_ASSERT(x + acs.covered_blocks_x() <= layers_.xsize()); - JXL_CHECK(SetNoBoundsCheck(x, y, type, /*check=*/false)); + JXL_DASSERT(y + acs.covered_blocks_y() <= layers_.ysize()); + JXL_DASSERT(x + acs.covered_blocks_x() <= layers_.xsize()); +#endif + JXL_RETURN_IF_ERROR(SetNoBoundsCheck(x, y, type, /*check=*/false)); + return true; } - Status SetNoBoundsCheck(size_t x, size_t y, AcStrategy::Type type, + Status SetNoBoundsCheck(size_t x, size_t y, AcStrategyType type, bool check = true) { AcStrategy acs = AcStrategy::FromRawStrategy(type); for (size_t iy = 0; iy < acs.covered_blocks_y(); iy++) { @@ -244,7 +256,9 @@ class AcStrategyImage { size_t ysize() const { return layers_.ysize(); } // Count the number of blocks of a given type. - size_t CountBlocks(AcStrategy::Type type) const; + size_t CountBlocks(AcStrategyType type) const; + + JxlMemoryManager* memory_manager() const { return layers_.memory_manager(); } private: ImageB layers_; diff --git a/media/libjxl/src/lib/jxl/ac_strategy_test.cc b/media/libjxl/src/lib/jxl/ac_strategy_test.cc index d366aa3f82..e6405fc86b 100644 --- a/media/libjxl/src/lib/jxl/ac_strategy_test.cc +++ b/media/libjxl/src/lib/jxl/ac_strategy_test.cc @@ -5,19 +5,22 @@ #include "lib/jxl/ac_strategy.h" -#include +#include -#include -#include +#include +#include #include // HWY_ALIGN_MAX -#include -#include +#include #include "lib/jxl/base/random.h" -#include "lib/jxl/common.h" -#include "lib/jxl/dct_scales.h" +#include "lib/jxl/coeff_order_fwd.h" #include "lib/jxl/dec_transforms_testonly.h" #include "lib/jxl/enc_transforms.h" +#include "lib/jxl/memory_manager_internal.h" +#include "lib/jxl/simd_util.h" +#include "lib/jxl/test_memory_manager.h" +#include "lib/jxl/test_utils.h" +#include "lib/jxl/testing.h" namespace jxl { namespace { @@ -26,20 +29,27 @@ namespace { class AcStrategyRoundtrip : public ::hwy::TestWithParamTargetAndT { protected: void Run() { - const AcStrategy::Type type = static_cast(GetParam()); + JxlMemoryManager* memory_manager = test::MemoryManager(); + const AcStrategyType type = static_cast(GetParam()); const AcStrategy acs = AcStrategy::FromRawStrategy(type); + const size_t dct_scratch_size = + 3 * (MaxVectorSize() / sizeof(float)) * AcStrategy::kMaxBlockDim; - auto mem = hwy::AllocateAligned(4 * AcStrategy::kMaxCoeffArea); - float* scratch_space = mem.get(); - float* coeffs = scratch_space + AcStrategy::kMaxCoeffArea; + size_t mem_bytes = + (4 * AcStrategy::kMaxCoeffArea + dct_scratch_size) * sizeof(float); + JXL_TEST_ASSIGN_OR_DIE(AlignedMemory mem, + AlignedMemory::Create(memory_manager, mem_bytes)); + float* coeffs = mem.address(); float* idct = coeffs + AcStrategy::kMaxCoeffArea; - Rng rng(type * 65537 + 13); + float* input = idct + AcStrategy::kMaxCoeffArea; + float* scratch_space = input + AcStrategy::kMaxCoeffArea; + + Rng rng(static_cast(type) * 65537 + 13); for (size_t j = 0; j < 64; j++) { size_t i = (acs.log2_covered_blocks() ? rng.UniformU(0, 64u << acs.log2_covered_blocks()) : j); - float* input = idct + AcStrategy::kMaxCoeffArea; std::fill_n(input, AcStrategy::kMaxCoeffArea, 0); input[i] = 0.2f; TransformFromPixels(type, input, acs.covered_blocks_x() * 8, coeffs, @@ -50,7 +60,7 @@ class AcStrategyRoundtrip : public ::hwy::TestWithParamTargetAndT { scratch_space); for (size_t j = 0; j < 64u << acs.log2_covered_blocks(); j++) { ASSERT_NEAR(idct[j], j == i ? 0.2f : 0, 2e-6) - << "j = " << j << " i = " << i << " acs " << type; + << "j = " << j << " i = " << i << " acs " << static_cast(type); } } // Test DC. @@ -60,13 +70,15 @@ class AcStrategyRoundtrip : public ::hwy::TestWithParamTargetAndT { float* dc = idct + AcStrategy::kMaxCoeffArea; std::fill_n(dc, AcStrategy::kMaxCoeffArea, 0); dc[y * acs.covered_blocks_x() * 8 + x] = 0.2; - LowestFrequenciesFromDC(type, dc, acs.covered_blocks_x() * 8, coeffs); + LowestFrequenciesFromDC(type, dc, acs.covered_blocks_x() * 8, coeffs, + scratch_space); DCFromLowestFrequencies(type, coeffs, idct, acs.covered_blocks_x() * 8); std::fill_n(dc, AcStrategy::kMaxCoeffArea, 0); dc[y * acs.covered_blocks_x() * 8 + x] = 0.2; for (size_t j = 0; j < 64u << acs.log2_covered_blocks(); j++) { ASSERT_NEAR(idct[j], dc[j], 1e-6) - << "j = " << j << " x = " << x << " y = " << y << " acs " << type; + << "j = " << j << " x = " << x << " y = " << y << " acs " + << static_cast(type); } } } @@ -75,7 +87,7 @@ class AcStrategyRoundtrip : public ::hwy::TestWithParamTargetAndT { HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T( AcStrategyRoundtrip, - ::testing::Range(0, int(AcStrategy::Type::kNumValidStrategies))); + ::testing::Range(0, static_cast(AcStrategy::kNumValidStrategies))); TEST_P(AcStrategyRoundtrip, Test) { Run(); } @@ -84,25 +96,33 @@ class AcStrategyRoundtripDownsample : public ::hwy::TestWithParamTargetAndT { protected: void Run() { - const AcStrategy::Type type = static_cast(GetParam()); + JxlMemoryManager* memory_manager = test::MemoryManager(); + const AcStrategyType type = static_cast(GetParam()); const AcStrategy acs = AcStrategy::FromRawStrategy(type); + const size_t dct_scratch_size = + 3 * (MaxVectorSize() / sizeof(float)) * AcStrategy::kMaxBlockDim; - auto mem = hwy::AllocateAligned(4 * AcStrategy::kMaxCoeffArea); - float* scratch_space = mem.get(); - float* coeffs = scratch_space + AcStrategy::kMaxCoeffArea; - std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0.0f); + size_t mem_bytes = + (4 * AcStrategy::kMaxCoeffArea + dct_scratch_size) * sizeof(float); + JXL_TEST_ASSIGN_OR_DIE(AlignedMemory mem, + AlignedMemory::Create(memory_manager, mem_bytes)); + float* coeffs = mem.address(); float* idct = coeffs + AcStrategy::kMaxCoeffArea; - Rng rng(type * 65537 + 13); + float* dc = idct + AcStrategy::kMaxCoeffArea; + float* scratch_space = dc + AcStrategy::kMaxCoeffArea; + + std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0.0f); + Rng rng(static_cast(type) * 65537 + 13); for (size_t y = 0; y < acs.covered_blocks_y(); y++) { for (size_t x = 0; x < acs.covered_blocks_x(); x++) { if (x > 4 || y > 4) { if (rng.Bernoulli(0.9f)) continue; } - float* dc = idct + AcStrategy::kMaxCoeffArea; std::fill_n(dc, AcStrategy::kMaxCoeffArea, 0); dc[y * acs.covered_blocks_x() * 8 + x] = 0.2f; - LowestFrequenciesFromDC(type, dc, acs.covered_blocks_x() * 8, coeffs); + LowestFrequenciesFromDC(type, dc, acs.covered_blocks_x() * 8, coeffs, + scratch_space); TransformToPixels(type, coeffs, idct, acs.covered_blocks_x() * 8, scratch_space); std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0.0f); @@ -120,7 +140,7 @@ class AcStrategyRoundtripDownsample } sum /= 64.0f; ASSERT_NEAR(sum, dc[dy * 8 * acs.covered_blocks_x() + dx], 1e-6) - << "acs " << type; + << "acs " << static_cast(type); } } } @@ -130,7 +150,7 @@ class AcStrategyRoundtripDownsample HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T( AcStrategyRoundtripDownsample, - ::testing::Range(0, int(AcStrategy::Type::kNumValidStrategies))); + ::testing::Range(0, static_cast(AcStrategy::kNumValidStrategies))); TEST_P(AcStrategyRoundtripDownsample, Test) { Run(); } @@ -139,17 +159,25 @@ TEST_P(AcStrategyRoundtripDownsample, Test) { Run(); } class AcStrategyDownsample : public ::hwy::TestWithParamTargetAndT { protected: void Run() { - const AcStrategy::Type type = static_cast(GetParam()); + JxlMemoryManager* memory_manager = test::MemoryManager(); + const AcStrategyType type = static_cast(GetParam()); const AcStrategy acs = AcStrategy::FromRawStrategy(type); + const size_t dct_scratch_size = + 3 * (MaxVectorSize() / sizeof(float)) * AcStrategy::kMaxBlockDim; size_t cx = acs.covered_blocks_y(); size_t cy = acs.covered_blocks_x(); CoefficientLayout(&cy, &cx); - auto mem = hwy::AllocateAligned(4 * AcStrategy::kMaxCoeffArea); - float* scratch_space = mem.get(); - float* idct = scratch_space + AcStrategy::kMaxCoeffArea; + size_t mem_bytes = + (4 * AcStrategy::kMaxCoeffArea + dct_scratch_size) * sizeof(float); + JXL_TEST_ASSIGN_OR_DIE(AlignedMemory mem, + AlignedMemory::Create(memory_manager, mem_bytes)); + float* idct = mem.address(); float* idct_acs_downsampled = idct + AcStrategy::kMaxCoeffArea; - Rng rng(type * 65537 + 13); + float* coeffs = idct + AcStrategy::kMaxCoeffArea; + float* scratch_space = coeffs + AcStrategy::kMaxCoeffArea; + + Rng rng(static_cast(type) * 65537 + 13); for (size_t y = 0; y < cy; y++) { for (size_t x = 0; x < cx; x++) { @@ -179,7 +207,7 @@ class AcStrategyDownsample : public ::hwy::TestWithParamTargetAndT { ASSERT_NEAR( sum, idct_acs_downsampled[dy * 8 * acs.covered_blocks_x() + dx], 1e-6) - << " acs " << type; + << " acs " << static_cast(type); } } } @@ -189,7 +217,7 @@ class AcStrategyDownsample : public ::hwy::TestWithParamTargetAndT { HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T( AcStrategyDownsample, - ::testing::Range(0, int(AcStrategy::Type::kNumValidStrategies))); + ::testing::Range(0, static_cast(AcStrategy::kNumValidStrategies))); TEST_P(AcStrategyDownsample, Test) { Run(); } @@ -212,10 +240,16 @@ TEST_P(AcStrategyTargetTest, RoundtripAFVDCT) { } TEST_P(AcStrategyTargetTest, BenchmarkAFV) { - const AcStrategy::Type type = AcStrategy::Type::AFV0; + JxlMemoryManager* memory_manager = test::MemoryManager(); + const AcStrategyType type = AcStrategyType::AFV0; HWY_ALIGN_MAX float pixels[64] = {1}; HWY_ALIGN_MAX float coeffs[64] = {}; - HWY_ALIGN_MAX float scratch_space[64] = {}; + const size_t dct_scratch_size = + 3 * (MaxVectorSize() / sizeof(float)) * AcStrategy::kMaxBlockDim; + size_t mem_bytes = (64 + dct_scratch_size) * sizeof(float); + JXL_TEST_ASSIGN_OR_DIE(AlignedMemory mem, + AlignedMemory::Create(memory_manager, mem_bytes)); + float* scratch_space = mem.address(); for (size_t i = 0; i < 1 << 14; i++) { TransformToPixels(type, coeffs, pixels, 8, scratch_space); TransformFromPixels(type, pixels, 8, coeffs, scratch_space); diff --git a/media/libjxl/src/lib/jxl/alpha.cc b/media/libjxl/src/lib/jxl/alpha.cc index f0ab39ac08..48d7e7ee92 100644 --- a/media/libjxl/src/lib/jxl/alpha.cc +++ b/media/libjxl/src/lib/jxl/alpha.cc @@ -42,7 +42,7 @@ void PerformAlphaBlending(const float* bg, const float* bga, const float* fg, bool alpha_is_premultiplied, bool clamp) { if (bg == bga && fg == fga) { for (size_t x = 0; x < num_pixels; ++x) { - float fa = clamp ? fga[x] : std::min(std::max(0.0f, fga[x]), 1.0f); + float fa = clamp ? fga[x] : Clamp(fga[x]); out[x] = (1.f - (1.f - fa) * (1.f - bga[x])); } } else { @@ -66,10 +66,14 @@ void PerformAlphaWeightedAdd(const float* bg, const float* fg, const float* fga, float* out, size_t num_pixels, bool clamp) { if (fg == fga) { memcpy(out, bg, num_pixels * sizeof(*out)); - } else { + } else if (clamp) { for (size_t x = 0; x < num_pixels; ++x) { out[x] = bg[x] + fg[x] * Clamp(fga[x]); } + } else { + for (size_t x = 0; x < num_pixels; ++x) { + out[x] = bg[x] + fg[x] * fga[x]; + } } } @@ -108,13 +112,4 @@ void UnpremultiplyAlpha(float* JXL_RESTRICT r, float* JXL_RESTRICT g, } } -void UnpremultiplyAlpha(float* JXL_RESTRICT rgba, size_t num_pixels) { - for (size_t x = 0, ix = 0; x < num_pixels; ++x, ix += 4) { - const float multiplier = 1.f / std::max(kSmallAlpha, rgba[ix + 3]); - rgba[ix] *= multiplier; - rgba[ix + 1] *= multiplier; - rgba[ix + 2] *= multiplier; - } -} - } // namespace jxl diff --git a/media/libjxl/src/lib/jxl/alpha.h b/media/libjxl/src/lib/jxl/alpha.h index f49790b582..efb76c800f 100644 --- a/media/libjxl/src/lib/jxl/alpha.h +++ b/media/libjxl/src/lib/jxl/alpha.h @@ -60,7 +60,6 @@ void PremultiplyAlpha(float* JXL_RESTRICT r, float* JXL_RESTRICT g, void UnpremultiplyAlpha(float* JXL_RESTRICT r, float* JXL_RESTRICT g, float* JXL_RESTRICT b, const float* JXL_RESTRICT a, size_t num_pixels); -void UnpremultiplyAlpha(float* JXL_RESTRICT rgba, size_t num_pixels); } // namespace jxl diff --git a/media/libjxl/src/lib/jxl/alpha_test.cc b/media/libjxl/src/lib/jxl/alpha_test.cc index c643fbdc54..6e26e77be6 100644 --- a/media/libjxl/src/lib/jxl/alpha_test.cc +++ b/media/libjxl/src/lib/jxl/alpha_test.cc @@ -5,68 +5,69 @@ #include "lib/jxl/alpha.h" -#include "lib/jxl/test_utils.h" +#include + +#include "lib/jxl/base/common.h" +#include "lib/jxl/testing.h" namespace jxl { namespace { -using ::testing::_; -using ::testing::ElementsAre; -using ::testing::FloatNear; +AlphaBlendingInputLayer makeAbil(const Color& rgb, const float& a) { + const float* data = rgb.data(); + return {data, data + 1, data + 2, &a}; +} + +AlphaBlendingOutput makeAbo(Color& rgb, float& a) { + float* data = rgb.data(); + return {data, data + 1, data + 2, &a}; +} TEST(AlphaTest, BlendingWithNonPremultiplied) { - const float bg_rgb[3] = {100, 110, 120}; + const Color bg_rgb{100, 110, 120}; const float bg_a = 180.f / 255; - const float fg_rgb[3] = {25, 21, 23}; + const Color fg_rgb{25, 21, 23}; const float fg_a = 15420.f / 65535; const float fg_a2 = 2.0f; - float out_rgb[3]; + Color out_rgb; float out_a; PerformAlphaBlending( - /*bg=*/{&bg_rgb[0], &bg_rgb[1], &bg_rgb[2], &bg_a}, - /*fg=*/{&fg_rgb[0], &fg_rgb[1], &fg_rgb[2], &fg_a}, - /*out=*/{&out_rgb[0], &out_rgb[1], &out_rgb[2], &out_a}, 1, + /*bg=*/makeAbil(bg_rgb, bg_a), + /*fg=*/makeAbil(fg_rgb, fg_a), + /*out=*/makeAbo(out_rgb, out_a), 1, /*alpha_is_premultiplied=*/false, /*clamp=*/false); - EXPECT_THAT(out_rgb, - ElementsAre(FloatNear(77.2f, .05f), FloatNear(83.0f, .05f), - FloatNear(90.6f, .05f))); + EXPECT_ARRAY_NEAR(out_rgb, (Color{77.2f, 83.0f, 90.6f}), 0.05f); EXPECT_NEAR(out_a, 3174.f / 4095, 1e-5); PerformAlphaBlending( - /*bg=*/{&bg_rgb[0], &bg_rgb[1], &bg_rgb[2], &bg_a}, - /*fg=*/{&fg_rgb[0], &fg_rgb[1], &fg_rgb[2], &fg_a2}, - /*out=*/{&out_rgb[0], &out_rgb[1], &out_rgb[2], &out_a}, 1, + /*bg=*/makeAbil(bg_rgb, bg_a), + /*fg=*/makeAbil(fg_rgb, fg_a2), + /*out=*/makeAbo(out_rgb, out_a), 1, /*alpha_is_premultiplied=*/false, /*clamp=*/true); - EXPECT_THAT(out_rgb, ElementsAre(FloatNear(fg_rgb[0], .05f), - FloatNear(fg_rgb[1], .05f), - FloatNear(fg_rgb[2], .05f))); + EXPECT_ARRAY_NEAR(out_rgb, fg_rgb, 0.05f); EXPECT_NEAR(out_a, 1.0f, 1e-5); } TEST(AlphaTest, BlendingWithPremultiplied) { - const float bg_rgb[3] = {100, 110, 120}; + const Color bg_rgb{100, 110, 120}; const float bg_a = 180.f / 255; - const float fg_rgb[3] = {25, 21, 23}; + const Color fg_rgb{25, 21, 23}; const float fg_a = 15420.f / 65535; const float fg_a2 = 2.0f; - float out_rgb[3]; + Color out_rgb; float out_a; PerformAlphaBlending( - /*bg=*/{&bg_rgb[0], &bg_rgb[1], &bg_rgb[2], &bg_a}, - /*fg=*/{&fg_rgb[0], &fg_rgb[1], &fg_rgb[2], &fg_a}, - /*out=*/{&out_rgb[0], &out_rgb[1], &out_rgb[2], &out_a}, 1, + /*bg=*/makeAbil(bg_rgb, bg_a), + /*fg=*/makeAbil(fg_rgb, fg_a), + /*out=*/makeAbo(out_rgb, out_a), 1, /*alpha_is_premultiplied=*/true, /*clamp=*/false); - EXPECT_THAT(out_rgb, - ElementsAre(FloatNear(101.5f, .05f), FloatNear(105.1f, .05f), - FloatNear(114.8f, .05f))); + EXPECT_ARRAY_NEAR(out_rgb, (Color{101.5f, 105.1f, 114.8f}), 0.05f); EXPECT_NEAR(out_a, 3174.f / 4095, 1e-5); PerformAlphaBlending( - /*bg=*/{&bg_rgb[0], &bg_rgb[1], &bg_rgb[2], &bg_a}, - /*fg=*/{&fg_rgb[0], &fg_rgb[1], &fg_rgb[2], &fg_a2}, - /*out=*/{&out_rgb[0], &out_rgb[1], &out_rgb[2], &out_a}, 1, + /*bg=*/makeAbil(bg_rgb, bg_a), + /*fg=*/makeAbil(fg_rgb, fg_a2), + /*out=*/makeAbo(out_rgb, out_a), 1, /*alpha_is_premultiplied=*/true, /*clamp=*/true); - EXPECT_THAT(out_rgb, ElementsAre(FloatNear(fg_rgb[0], .05f), - FloatNear(fg_rgb[1], .05f), - FloatNear(fg_rgb[2], .05f))); + EXPECT_ARRAY_NEAR(out_rgb, fg_rgb, 0.05f); EXPECT_NEAR(out_a, 1.0f, 1e-5); } @@ -75,58 +76,51 @@ TEST(AlphaTest, Mul) { const float fg = 25; float out; PerformMulBlending(&bg, &fg, &out, 1, /*clamp=*/false); - EXPECT_THAT(out, FloatNear(fg * bg, .05f)); + EXPECT_NEAR(out, fg * bg, .05f); PerformMulBlending(&bg, &fg, &out, 1, /*clamp=*/true); - EXPECT_THAT(out, FloatNear(bg, .05f)); + EXPECT_NEAR(out, bg, .05f); } TEST(AlphaTest, PremultiplyAndUnpremultiply) { - const float alpha[] = {0.f, 63.f / 255, 127.f / 255, 1.f}; - float r[] = {120, 130, 140, 150}; - float g[] = {124, 134, 144, 154}; - float b[] = {127, 137, 147, 157}; + using F4 = std::array; + const F4 alpha{0.f, 63.f / 255, 127.f / 255, 1.f}; + F4 r{120, 130, 140, 150}; + F4 g{124, 134, 144, 154}; + F4 b{127, 137, 147, 157}; - PremultiplyAlpha(r, g, b, alpha, 4); - EXPECT_THAT( - r, ElementsAre(FloatNear(0.f, 1e-5f), FloatNear(130 * 63.f / 255, 1e-5f), - FloatNear(140 * 127.f / 255, 1e-5f), 150)); - EXPECT_THAT( - g, ElementsAre(FloatNear(0.f, 1e-5f), FloatNear(134 * 63.f / 255, 1e-5f), - FloatNear(144 * 127.f / 255, 1e-5f), 154)); - EXPECT_THAT( - b, ElementsAre(FloatNear(0.f, 1e-5f), FloatNear(137 * 63.f / 255, 1e-5f), - FloatNear(147 * 127.f / 255, 1e-5f), 157)); + PremultiplyAlpha(r.data(), g.data(), b.data(), alpha.data(), alpha.size()); + EXPECT_ARRAY_NEAR(r, (F4{0.0f, 130 * 63.f / 255, 140 * 127.f / 255, 150}), + 1e-5f); + EXPECT_ARRAY_NEAR(g, (F4{0.0f, 134 * 63.f / 255, 144 * 127.f / 255, 154}), + 1e-5f); + EXPECT_ARRAY_NEAR(b, (F4{0.0f, 137 * 63.f / 255, 147 * 127.f / 255, 157}), + 1e-5f); - UnpremultiplyAlpha(r, g, b, alpha, 4); - EXPECT_THAT(r, ElementsAre(FloatNear(120, 1e-4f), FloatNear(130, 1e-4f), - FloatNear(140, 1e-4f), FloatNear(150, 1e-4f))); - EXPECT_THAT(g, ElementsAre(FloatNear(124, 1e-4f), FloatNear(134, 1e-4f), - FloatNear(144, 1e-4f), FloatNear(154, 1e-4f))); - EXPECT_THAT(b, ElementsAre(FloatNear(127, 1e-4f), FloatNear(137, 1e-4f), - FloatNear(147, 1e-4f), FloatNear(157, 1e-4f))); + UnpremultiplyAlpha(r.data(), g.data(), b.data(), alpha.data(), alpha.size()); + EXPECT_ARRAY_NEAR(r, (F4{120, 130, 140, 150}), 1e-4f); + EXPECT_ARRAY_NEAR(g, (F4{124, 134, 144, 154}), 1e-4f); + EXPECT_ARRAY_NEAR(b, (F4{127, 137, 147, 157}), 1e-4f); } TEST(AlphaTest, UnpremultiplyAndPremultiply) { - const float alpha[] = {0.f, 63.f / 255, 127.f / 255, 1.f}; - float r[] = {50, 60, 70, 80}; - float g[] = {54, 64, 74, 84}; - float b[] = {57, 67, 77, 87}; + using F4 = std::array; + const F4 alpha{0.f, 63.f / 255, 127.f / 255, 1.f}; + F4 r{50, 60, 70, 80}; + F4 g{54, 64, 74, 84}; + F4 b{57, 67, 77, 87}; - UnpremultiplyAlpha(r, g, b, alpha, 4); - EXPECT_THAT(r, ElementsAre(_, FloatNear(60 * 255.f / 63, 1e-4f), - FloatNear(70 * 255.f / 127, 1e-4f), 80)); - EXPECT_THAT(g, ElementsAre(_, FloatNear(64 * 255.f / 63, 1e-4f), - FloatNear(74 * 255.f / 127, 1e-4f), 84)); - EXPECT_THAT(b, ElementsAre(_, FloatNear(67 * 255.f / 63, 1e-4f), - FloatNear(77 * 255.f / 127, 1e-4f), 87)); + UnpremultiplyAlpha(r.data(), g.data(), b.data(), alpha.data(), alpha.size()); + EXPECT_ARRAY_NEAR( + r, (F4{50.0f * (1 << 26), 60 * 255.f / 63, 70 * 255.f / 127, 80}), 1e-4f); + EXPECT_ARRAY_NEAR( + g, (F4{54.0f * (1 << 26), 64 * 255.f / 63, 74 * 255.f / 127, 84}), 1e-4f); + EXPECT_ARRAY_NEAR( + b, (F4{57.0f * (1 << 26), 67 * 255.f / 63, 77 * 255.f / 127, 87}), 1e-4f); - PremultiplyAlpha(r, g, b, alpha, 4); - EXPECT_THAT(r, ElementsAre(FloatNear(50, 1e-4f), FloatNear(60, 1e-4f), - FloatNear(70, 1e-4f), FloatNear(80, 1e-4f))); - EXPECT_THAT(g, ElementsAre(FloatNear(54, 1e-4f), FloatNear(64, 1e-4f), - FloatNear(74, 1e-4f), FloatNear(84, 1e-4f))); - EXPECT_THAT(b, ElementsAre(FloatNear(57, 1e-4f), FloatNear(67, 1e-4f), - FloatNear(77, 1e-4f), FloatNear(87, 1e-4f))); + PremultiplyAlpha(r.data(), g.data(), b.data(), alpha.data(), alpha.size()); + EXPECT_ARRAY_NEAR(r, (F4{50, 60, 70, 80}), 1e-4); + EXPECT_ARRAY_NEAR(g, (F4{54, 64, 74, 84}), 1e-4); + EXPECT_ARRAY_NEAR(b, (F4{57, 67, 77, 87}), 1e-4); } } // namespace diff --git a/media/libjxl/src/lib/jxl/ans_common.cc b/media/libjxl/src/lib/jxl/ans_common.cc index 32a658fb43..306ecb7276 100644 --- a/media/libjxl/src/lib/jxl/ans_common.cc +++ b/media/libjxl/src/lib/jxl/ans_common.cc @@ -5,7 +5,10 @@ #include "lib/jxl/ans_common.h" +#include +#include #include +#include #include "lib/jxl/ans_params.h" #include "lib/jxl/base/status.h" @@ -13,8 +16,8 @@ namespace jxl { std::vector CreateFlatHistogram(int length, int total_count) { - JXL_ASSERT(length > 0); - JXL_ASSERT(length <= total_count); + JXL_DASSERT(length > 0); + JXL_DASSERT(length <= total_count); const int count = total_count / length; std::vector result(length, count); const int rem_counts = total_count % length; @@ -24,10 +27,10 @@ std::vector CreateFlatHistogram(int length, int total_count) { return result; } -// First, all trailing non-occuring 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 +// First, all trailing non-occurring symbols are removed from the distribution; +// 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 @@ -48,8 +51,12 @@ std::vector CreateFlatHistogram(int length, int total_count) { // underfull nor overfull, and represents exactly two symbols. The overfull // entry might be either overfull or underfull, and is pushed into the // corresponding stack. -void InitAliasTable(std::vector distribution, uint32_t range, - size_t log_alpha_size, AliasTable::Entry* JXL_RESTRICT a) { +Status InitAliasTable(std::vector distribution, uint32_t log_range, + size_t log_alpha_size, + AliasTable::Entry* JXL_RESTRICT a) { + const uint32_t range = 1 << log_range; + const size_t table_size = 1 << log_alpha_size; + JXL_ENSURE(table_size <= range); while (!distribution.empty() && distribution.back() == 0) { distribution.pop_back(); } @@ -59,32 +66,36 @@ void InitAliasTable(std::vector distribution, uint32_t range, if (distribution.empty()) { distribution.emplace_back(range); } - const size_t table_size = 1 << log_alpha_size; -#if JXL_ENABLE_ASSERT - int sum = std::accumulate(distribution.begin(), distribution.end(), 0); -#endif // JXL_ENABLE_ASSERT - JXL_ASSERT(static_cast(sum) == range); - // range must be a power of two - JXL_ASSERT((range & (range - 1)) == 0); - JXL_ASSERT(distribution.size() <= table_size); - JXL_ASSERT(table_size <= range); + JXL_ENSURE(distribution.size() <= table_size); const uint32_t entry_size = range >> log_alpha_size; // this is exact + int single_symbol = -1; + int sum = 0; // Special case for single-symbol distributions, that ensures that the state // does not change when decoding from such a distribution. Note that, since we // hardcode offset0 == 0, it is not straightforward (if at all possible) to // fix the general case to produce this result. for (size_t sym = 0; sym < distribution.size(); sym++) { - if (distribution[sym] == ANS_TAB_SIZE) { - for (size_t i = 0; i < table_size; i++) { - a[i].right_value = sym; - a[i].cutoff = 0; - a[i].offsets1 = entry_size * i; - a[i].freq0 = 0; - a[i].freq1_xor_freq0 = ANS_TAB_SIZE; - } - return; + int32_t v = distribution[sym]; + sum += v; + if (v == ANS_TAB_SIZE) { + JXL_ENSURE(single_symbol == -1); + single_symbol = sym; } } + JXL_ENSURE(static_cast(sum) == range); + if (single_symbol != -1) { + uint8_t sym = single_symbol; + JXL_ENSURE(single_symbol == sym); + for (size_t i = 0; i < table_size; i++) { + a[i].right_value = sym; + a[i].cutoff = 0; + a[i].offsets1 = entry_size * i; + a[i].freq0 = 0; + a[i].freq1_xor_freq0 = ANS_TAB_SIZE; + } + return true; + } + std::vector underfull_posn; std::vector overfull_posn; std::vector cutoffs(1 << log_alpha_size); @@ -105,7 +116,7 @@ void InitAliasTable(std::vector distribution, uint32_t range, while (!overfull_posn.empty()) { uint32_t overfull_i = overfull_posn.back(); overfull_posn.pop_back(); - JXL_ASSERT(!underfull_posn.empty()); + JXL_ENSURE(!underfull_posn.empty()); uint32_t underfull_i = underfull_posn.back(); underfull_posn.pop_back(); uint32_t underfull_by = entry_size - cutoffs[underfull_i]; @@ -143,6 +154,7 @@ void InitAliasTable(std::vector distribution, uint32_t range, a[i].freq0 = static_cast(freq0); a[i].freq1_xor_freq0 = static_cast(freq1 ^ freq0); } + return true; } } // namespace jxl diff --git a/media/libjxl/src/lib/jxl/ans_common.h b/media/libjxl/src/lib/jxl/ans_common.h index fb5058e310..20897028f0 100644 --- a/media/libjxl/src/lib/jxl/ans_common.h +++ b/media/libjxl/src/lib/jxl/ans_common.h @@ -6,9 +6,11 @@ #ifndef LIB_JXL_ANS_COMMON_H_ #define LIB_JXL_ANS_COMMON_H_ -#include - #include +#include +#include +#include +#include #include // Prefetch #include @@ -21,10 +23,11 @@ namespace jxl { // Returns the precision (number of bits) that should be used to store // a histogram count such that Log2Floor(count) == logcount. -static JXL_INLINE uint32_t GetPopulationCountPrecision(uint32_t logcount, - uint32_t shift) { +static JXL_MAYBE_UNUSED JXL_INLINE uint32_t +GetPopulationCountPrecision(uint32_t logcount, uint32_t shift) { int32_t r = std::min( - logcount, int(shift) - int((ANS_LOG_TAB_SIZE - logcount) >> 1)); + logcount, static_cast(shift) - + static_cast((ANS_LOG_TAB_SIZE - logcount) >> 1)); if (r < 0) return 0; return r; } @@ -135,8 +138,8 @@ struct AliasTable { }; // Computes an alias table for a given distribution. -void InitAliasTable(std::vector distribution, uint32_t range, - size_t log_alpha_size, AliasTable::Entry* JXL_RESTRICT a); +Status InitAliasTable(std::vector distribution, uint32_t log_range, + size_t log_alpha_size, AliasTable::Entry* JXL_RESTRICT a); } // namespace jxl diff --git a/media/libjxl/src/lib/jxl/ans_common_test.cc b/media/libjxl/src/lib/jxl/ans_common_test.cc index 2c4ea8ea4b..b97f5186f9 100644 --- a/media/libjxl/src/lib/jxl/ans_common_test.cc +++ b/media/libjxl/src/lib/jxl/ans_common_test.cc @@ -7,17 +7,18 @@ #include -#include "gtest/gtest.h" #include "lib/jxl/ans_params.h" +#include "lib/jxl/testing.h" namespace jxl { namespace { void VerifyAliasDistribution(const std::vector& distribution, - uint32_t range) { + uint32_t log_range) { constexpr size_t log_alpha_size = 8; AliasTable::Entry table[1 << log_alpha_size]; - InitAliasTable(distribution, range, log_alpha_size, table); + ASSERT_TRUE(InitAliasTable(distribution, log_range, log_alpha_size, table)); + uint32_t range = 1 << log_range; std::vector> offsets(distribution.size()); for (uint32_t i = 0; i < range; i++) { AliasTable::Symbol s = AliasTable::Lookup( @@ -34,9 +35,10 @@ void VerifyAliasDistribution(const std::vector& distribution, } TEST(ANSCommonTest, AliasDistributionSmoke) { - VerifyAliasDistribution({ANS_TAB_SIZE / 2, ANS_TAB_SIZE / 2}, ANS_TAB_SIZE); - VerifyAliasDistribution({ANS_TAB_SIZE}, ANS_TAB_SIZE); - VerifyAliasDistribution({0, 0, 0, ANS_TAB_SIZE, 0}, ANS_TAB_SIZE); + VerifyAliasDistribution({ANS_TAB_SIZE / 2, ANS_TAB_SIZE / 2}, + ANS_LOG_TAB_SIZE); + VerifyAliasDistribution({ANS_TAB_SIZE}, ANS_LOG_TAB_SIZE); + VerifyAliasDistribution({0, 0, 0, ANS_TAB_SIZE, 0}, ANS_LOG_TAB_SIZE); } } // namespace diff --git a/media/libjxl/src/lib/jxl/ans_test.cc b/media/libjxl/src/lib/jxl/ans_test.cc index ca9883d372..8bed98895f 100644 --- a/media/libjxl/src/lib/jxl/ans_test.cc +++ b/media/libjxl/src/lib/jxl/ans_test.cc @@ -3,49 +3,60 @@ // 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 "gtest/gtest.h" #include "lib/jxl/ans_params.h" -#include "lib/jxl/aux_out_fwd.h" #include "lib/jxl/base/random.h" -#include "lib/jxl/base/span.h" +#include "lib/jxl/base/status.h" #include "lib/jxl/dec_ans.h" #include "lib/jxl/dec_bit_reader.h" #include "lib/jxl/enc_ans.h" +#include "lib/jxl/enc_aux_out.h" #include "lib/jxl/enc_bit_writer.h" +#include "lib/jxl/test_memory_manager.h" +#include "lib/jxl/test_utils.h" +#include "lib/jxl/testing.h" namespace jxl { namespace { void RoundtripTestcase(int n_histograms, int alphabet_size, const std::vector& input_values) { + JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); constexpr uint16_t kMagic1 = 0x9e33; constexpr uint16_t kMagic2 = 0x8b04; - BitWriter writer; + BitWriter writer{memory_manager}; // Space for magic bytes. - BitWriter::Allotment allotment_magic1(&writer, 16); - writer.Write(16, kMagic1); - ReclaimAndCharge(&writer, &allotment_magic1, 0, nullptr); + ASSERT_TRUE(writer.WithMaxBits(16, LayerType::Header, nullptr, [&] { + writer.Write(16, kMagic1); + return true; + })); std::vector context_map; EntropyEncodingData codes; std::vector> input_values_vec; input_values_vec.push_back(input_values); - BuildAndEncodeHistograms(HistogramParams(), n_histograms, input_values_vec, - &codes, &context_map, &writer, 0, nullptr); - WriteTokens(input_values_vec[0], codes, context_map, &writer, 0, nullptr); + JXL_TEST_ASSIGN_OR_DIE( + size_t cost, + BuildAndEncodeHistograms(memory_manager, HistogramParams(), n_histograms, + input_values_vec, &codes, &context_map, &writer, + LayerType::Header, nullptr)); + (void)cost; + ASSERT_TRUE(WriteTokens(input_values_vec[0], codes, context_map, 0, &writer, + LayerType::Header, nullptr)); // Magic bytes + padding - BitWriter::Allotment allotment_magic2(&writer, 24); - writer.Write(16, kMagic2); - writer.ZeroPadToByte(); - ReclaimAndCharge(&writer, &allotment_magic2, 0, nullptr); + ASSERT_TRUE(writer.WithMaxBits(24, LayerType::Header, nullptr, [&] { + writer.Write(16, kMagic2); + writer.ZeroPadToByte(); + return true; + })); // We do not truncate the output. Reading past the end reads out zeroes // anyway. @@ -55,10 +66,11 @@ void RoundtripTestcase(int n_histograms, int alphabet_size, std::vector dec_context_map; ANSCode decoded_codes; - ASSERT_TRUE( - DecodeHistograms(&br, n_histograms, &decoded_codes, &dec_context_map)); + ASSERT_TRUE(DecodeHistograms(memory_manager, &br, n_histograms, + &decoded_codes, &dec_context_map)); ASSERT_EQ(dec_context_map, context_map); - ANSSymbolReader reader(&decoded_codes, &br); + JXL_TEST_ASSIGN_OR_DIE(ANSSymbolReader reader, + ANSSymbolReader::Create(&decoded_codes, &br)); for (const Token& symbol : input_values) { uint32_t read_symbol = @@ -113,8 +125,8 @@ void RoundtripRandomUnbalancedStream(int alphabet_size) { Rng rng(0); for (size_t i = 0; i < kReps; i++) { std::vector distributions[kNumHistograms] = {}; - for (int j = 0; j < kNumHistograms; j++) { - distributions[j].resize(kPrecision); + for (auto& distr : distributions) { + distr.resize(kPrecision); int symbol = 0; int remaining = 1; for (int k = 0; k < kPrecision; k++) { @@ -126,7 +138,7 @@ void RoundtripRandomUnbalancedStream(int alphabet_size) { // sufficiently dissimilar. remaining = rng.UniformU(0, kPrecision - k + 1); } - distributions[j][k] = symbol; + distr[k] = symbol; remaining--; } } @@ -157,8 +169,10 @@ TEST(ANSTest, RandomUnbalancedStreamRoundtripBig) { } TEST(ANSTest, UintConfigRoundtrip) { + JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); for (size_t log_alpha_size = 5; log_alpha_size <= 8; log_alpha_size++) { - std::vector uint_config, uint_config_dec; + std::vector uint_config; + std::vector uint_config_dec; for (size_t i = 0; i < log_alpha_size; i++) { for (size_t j = 0; j <= i; j++) { for (size_t k = 0; k <= i - j; k++) { @@ -168,10 +182,12 @@ TEST(ANSTest, UintConfigRoundtrip) { } uint_config.emplace_back(log_alpha_size, 0, 0); uint_config_dec.resize(uint_config.size()); - BitWriter writer; - BitWriter::Allotment allotment(&writer, 10 * uint_config.size()); - EncodeUintConfigs(uint_config, &writer, log_alpha_size); - ReclaimAndCharge(&writer, &allotment, 0, nullptr); + BitWriter writer{memory_manager}; + ASSERT_TRUE(writer.WithMaxBits( + 10 * uint_config.size(), LayerType::Header, nullptr, [&] { + EncodeUintConfigs(uint_config, &writer, log_alpha_size); + return true; + })); writer.ZeroPadToByte(); BitReader br(writer.GetSpan()); EXPECT_TRUE(DecodeUintConfigs(log_alpha_size, &uint_config_dec, &br)); @@ -185,18 +201,19 @@ TEST(ANSTest, UintConfigRoundtrip) { } void TestCheckpointing(bool ans, bool lz77) { + JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); std::vector> input_values(1); for (size_t i = 0; i < 1024; i++) { - input_values[0].push_back(Token(0, i % 4)); + input_values[0].emplace_back(0, i % 4); } // up to lz77 window size. for (size_t i = 0; i < (1 << 20) - 1022; i++) { - input_values[0].push_back(Token(0, (i % 5) + 4)); + input_values[0].emplace_back(0, (i % 5) + 4); } // Ensure that when the window wraps around, new values are different. - input_values[0].push_back(Token(0, 0)); + input_values[0].emplace_back(0, 0); for (size_t i = 0; i < 1024; i++) { - input_values[0].push_back(Token(0, i % 4)); + input_values[0].emplace_back(0, i % 4); } std::vector context_map; @@ -206,12 +223,16 @@ void TestCheckpointing(bool ans, bool lz77) { : HistogramParams::LZ77Method::kNone; params.force_huffman = !ans; - BitWriter writer; + BitWriter writer{memory_manager}; { auto input_values_copy = input_values; - BuildAndEncodeHistograms(params, 1, input_values_copy, &codes, &context_map, - &writer, 0, nullptr); - WriteTokens(input_values_copy[0], codes, context_map, &writer, 0, nullptr); + JXL_TEST_ASSIGN_OR_DIE( + size_t cost, BuildAndEncodeHistograms( + memory_manager, params, 1, input_values_copy, &codes, + &context_map, &writer, LayerType::Header, nullptr)); + (void)cost; + ASSERT_TRUE(WriteTokens(input_values_copy[0], codes, context_map, 0, + &writer, LayerType::Header, nullptr)); writer.ZeroPadToByte(); } @@ -220,13 +241,15 @@ void TestCheckpointing(bool ans, bool lz77) { BitReader br(writer.GetSpan()); Status status = true; { - BitReaderScopedCloser bc(&br, &status); + BitReaderScopedCloser bc(br, status); std::vector dec_context_map; ANSCode decoded_codes; - ASSERT_TRUE(DecodeHistograms(&br, 1, &decoded_codes, &dec_context_map)); + ASSERT_TRUE(DecodeHistograms(memory_manager, &br, 1, &decoded_codes, + &dec_context_map)); ASSERT_EQ(dec_context_map, context_map); - ANSSymbolReader reader(&decoded_codes, &br); + JXL_TEST_ASSIGN_OR_DIE(ANSSymbolReader reader, + ANSSymbolReader::Create(&decoded_codes, &br)); ANSSymbolReader::Checkpoint checkpoint; size_t br_pos = 0; diff --git a/media/libjxl/src/lib/jxl/aux_out.cc b/media/libjxl/src/lib/jxl/aux_out.cc deleted file mode 100644 index d8ee9460f6..0000000000 --- a/media/libjxl/src/lib/jxl/aux_out.cc +++ /dev/null @@ -1,87 +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/aux_out.h" - -#include - -#include // accumulate - -#include "lib/jxl/aux_out_fwd.h" -#include "lib/jxl/base/printf_macros.h" -#include "lib/jxl/enc_bit_writer.h" - -namespace jxl { - -void AuxOut::Print(size_t num_inputs) const { - if (num_inputs == 0) return; - - LayerTotals all_layers; - for (size_t i = 0; i < layers.size(); ++i) { - all_layers.Assimilate(layers[i]); - } - - printf("Average butteraugli iters: %10.2f\n", - num_butteraugli_iters * 1.0 / num_inputs); - if (min_quant_rescale != 1.0 || max_quant_rescale != 1.0) { - printf("quant rescale range: %f .. %f\n", min_quant_rescale, - max_quant_rescale); - printf("bitrate error range: %.3f%% .. %.3f%%\n", - 100.0f * min_bitrate_error, 100.0f * max_bitrate_error); - } - - for (size_t i = 0; i < layers.size(); ++i) { - if (layers[i].total_bits != 0) { - printf("Total layer bits %-10s\t", LayerName(i)); - printf("%10f%%", 100.0 * layers[i].total_bits / all_layers.total_bits); - layers[i].Print(num_inputs); - } - } - printf("Total image size "); - all_layers.Print(num_inputs); - - const uint32_t dc_pred_total = - std::accumulate(dc_pred_usage.begin(), dc_pred_usage.end(), 0u); - const uint32_t dc_pred_total_xb = - std::accumulate(dc_pred_usage_xb.begin(), dc_pred_usage_xb.end(), 0u); - if (dc_pred_total + dc_pred_total_xb != 0) { - printf("\nDC pred Y XB:\n"); - for (size_t i = 0; i < dc_pred_usage.size(); ++i) { - printf(" %6u (%5.2f%%) %6u (%5.2f%%)\n", dc_pred_usage[i], - 100.0 * dc_pred_usage[i] / dc_pred_total, dc_pred_usage_xb[i], - 100.0 * dc_pred_usage_xb[i] / dc_pred_total_xb); - } - } - - size_t total_blocks = 0; - size_t total_positions = 0; - if (total_blocks != 0 && total_positions != 0) { - printf("\n\t\t Blocks\t\tPositions\t\t\tBlocks/Position\n"); - printf(" Total:\t\t %7" PRIuS "\t\t %7" PRIuS " \t\t\t%10f%%\n\n", - total_blocks, total_positions, - 100.0 * total_blocks / total_positions); - } -} - -void ReclaimAndCharge(BitWriter* JXL_RESTRICT writer, - BitWriter::Allotment* JXL_RESTRICT allotment, - size_t layer, AuxOut* JXL_RESTRICT aux_out) { - size_t used_bits, unused_bits; - allotment->PrivateReclaim(writer, &used_bits, &unused_bits); - -#if 0 - printf("Layer %s bits: max %" PRIuS " used %" PRIuS " unused %" PRIuS "\n", LayerName(layer), - allotment->MaxBits(), used_bits, unused_bits); -#endif - - // This may be a nested call with aux_out == null. Whenever we know that - // aux_out is null, we can call ReclaimUnused directly. - if (aux_out != nullptr) { - aux_out->layers[layer].total_bits += used_bits; - aux_out->layers[layer].histogram_bits += allotment->HistogramBits(); - } -} - -} // namespace jxl diff --git a/media/libjxl/src/lib/jxl/aux_out.h b/media/libjxl/src/lib/jxl/aux_out.h deleted file mode 100644 index 7076603125..0000000000 --- a/media/libjxl/src/lib/jxl/aux_out.h +++ /dev/null @@ -1,309 +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_AUX_OUT_H_ -#define LIB_JXL_AUX_OUT_H_ - -// Optional output information for debugging and analyzing size usage. - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "lib/jxl/aux_out_fwd.h" -#include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/status.h" -#include "lib/jxl/codec_in_out.h" -#include "lib/jxl/color_management.h" -#include "lib/jxl/common.h" -#include "lib/jxl/dec_xyb.h" -#include "lib/jxl/image.h" -#include "lib/jxl/image_bundle.h" -#include "lib/jxl/image_ops.h" -#include "lib/jxl/jxl_inspection.h" - -namespace jxl { - -// For LayerName and AuxOut::layers[] index. Order does not matter. -enum { - kLayerHeader = 0, - kLayerTOC, - kLayerDictionary, - kLayerSplines, - kLayerNoise, - kLayerQuant, - kLayerModularTree, - kLayerModularGlobal, - kLayerDC, - kLayerModularDcGroup, - kLayerControlFields, - kLayerOrder, - kLayerAC, - kLayerACTokens, - kLayerModularAcGroup, - kNumImageLayers -}; - -static inline const char* LayerName(size_t layer) { - switch (layer) { - case kLayerHeader: - return "Headers"; - case kLayerTOC: - return "TOC"; - case kLayerDictionary: - return "Patches"; - case kLayerSplines: - return "Splines"; - case kLayerNoise: - return "Noise"; - case kLayerQuant: - return "Quantizer"; - case kLayerModularTree: - return "ModularTree"; - case kLayerModularGlobal: - return "ModularGlobal"; - case kLayerDC: - return "DC"; - case kLayerModularDcGroup: - return "ModularDcGroup"; - case kLayerControlFields: - return "ControlFields"; - case kLayerOrder: - return "CoeffOrder"; - case kLayerAC: - return "ACHistograms"; - case kLayerACTokens: - return "ACTokens"; - case kLayerModularAcGroup: - return "ModularAcGroup"; - default: - JXL_ABORT("Invalid layer %d\n", static_cast(layer)); - } -} - -// Statistics gathered during compression or decompression. -struct AuxOut { - private: - struct LayerTotals { - void Assimilate(const LayerTotals& victim) { - num_clustered_histograms += victim.num_clustered_histograms; - histogram_bits += victim.histogram_bits; - extra_bits += victim.extra_bits; - total_bits += victim.total_bits; - clustered_entropy += victim.clustered_entropy; - } - void Print(size_t num_inputs) const { - printf("%10" PRId64, static_cast(total_bits)); - if (histogram_bits != 0) { - printf(" [c/i:%6.2f | hst:%8" PRId64 " | ex:%8" PRId64 - " | h+c+e:%12.3f", - num_clustered_histograms * 1.0 / num_inputs, - static_cast(histogram_bits >> 3), - static_cast(extra_bits >> 3), - (histogram_bits + clustered_entropy + extra_bits) / 8.0); - printf("]"); - } - printf("\n"); - } - size_t num_clustered_histograms = 0; - size_t extra_bits = 0; - - // Set via BitsWritten below - size_t histogram_bits = 0; - size_t total_bits = 0; - - double clustered_entropy = 0.0; - }; - - public: - AuxOut() = default; - AuxOut(const AuxOut&) = default; - - void Assimilate(const AuxOut& victim) { - for (size_t i = 0; i < layers.size(); ++i) { - layers[i].Assimilate(victim.layers[i]); - } - num_blocks += victim.num_blocks; - num_small_blocks += victim.num_small_blocks; - num_dct4x8_blocks += victim.num_dct4x8_blocks; - num_afv_blocks += victim.num_afv_blocks; - num_dct8_blocks += victim.num_dct8_blocks; - num_dct8x16_blocks += victim.num_dct8x16_blocks; - num_dct8x32_blocks += victim.num_dct8x32_blocks; - num_dct16_blocks += victim.num_dct16_blocks; - num_dct16x32_blocks += victim.num_dct16x32_blocks; - num_dct32_blocks += victim.num_dct32_blocks; - num_dct32x64_blocks += victim.num_dct32x64_blocks; - num_dct64_blocks += victim.num_dct64_blocks; - num_butteraugli_iters += victim.num_butteraugli_iters; - for (size_t i = 0; i < dc_pred_usage.size(); ++i) { - dc_pred_usage[i] += victim.dc_pred_usage[i]; - dc_pred_usage_xb[i] += victim.dc_pred_usage_xb[i]; - } - max_quant_rescale = std::max(max_quant_rescale, victim.max_quant_rescale); - min_quant_rescale = std::min(min_quant_rescale, victim.min_quant_rescale); - max_bitrate_error = std::max(max_bitrate_error, victim.max_bitrate_error); - min_bitrate_error = std::min(min_bitrate_error, victim.min_bitrate_error); - } - - void Print(size_t num_inputs) const; - - size_t TotalBits() const { - size_t total = 0; - for (const auto& layer : layers) { - total += layer.total_bits; - } - return total; - } - - template - void DumpImage(const char* label, const Image3& image) const { - if (!dump_image) return; - if (debug_prefix.empty()) return; - std::ostringstream pathname; - pathname << debug_prefix << label << ".png"; - CodecInOut io; - // Always save to 16-bit png. - io.metadata.m.SetUintSamples(16); - io.metadata.m.color_encoding = ColorEncoding::SRGB(); - io.SetFromImage(ConvertToFloat(image), io.metadata.m.color_encoding); - (void)dump_image(io, pathname.str()); - } - template - void DumpImage(const char* label, const Plane& image) { - DumpImage(label, - Image3(CopyImage(image), CopyImage(image), CopyImage(image))); - } - - template - void DumpXybImage(const char* label, const Image3& image) const { - if (!dump_image) return; - if (debug_prefix.empty()) return; - std::ostringstream pathname; - pathname << debug_prefix << label << ".png"; - - Image3F linear(image.xsize(), image.ysize()); - OpsinParams opsin_params; - opsin_params.Init(kDefaultIntensityTarget); - OpsinToLinear(image, Rect(linear), nullptr, &linear, opsin_params); - - CodecInOut io; - io.metadata.m.SetUintSamples(16); - io.metadata.m.color_encoding = ColorEncoding::LinearSRGB(); - io.SetFromImage(std::move(linear), io.metadata.m.color_encoding); - - (void)dump_image(io, pathname.str()); - } - - // Normalizes all the channels to range 0-1, creating a false-color image - // which allows seeing the information from non-RGB channels in an RGB debug - // image. - template - void DumpImageNormalized(const char* label, const Image3& image) const { - std::array min; - std::array max; - Image3MinMax(image, &min, &max); - Image3B normalized(image.xsize(), image.ysize()); - for (size_t c = 0; c < 3; ++c) { - float mul = min[c] == max[c] ? 0 : (255.0f / (max[c] - min[c])); - for (size_t y = 0; y < image.ysize(); ++y) { - const T* JXL_RESTRICT row_in = image.ConstPlaneRow(c, y); - uint8_t* JXL_RESTRICT row_out = normalized.PlaneRow(c, y); - for (size_t x = 0; x < image.xsize(); ++x) { - row_out[x] = static_cast((row_in[x] - min[c]) * mul); - } - } - } - DumpImage(label, normalized); - } - - template - void DumpPlaneNormalized(const char* label, const Plane& image) const { - T min; - T max; - ImageMinMax(image, &min, &max); - Image3B normalized(image.xsize(), image.ysize()); - for (size_t c = 0; c < 3; ++c) { - float mul = min == max ? 0 : (255.0f / (max - min)); - for (size_t y = 0; y < image.ysize(); ++y) { - const T* JXL_RESTRICT row_in = image.ConstRow(y); - uint8_t* JXL_RESTRICT row_out = normalized.PlaneRow(c, y); - for (size_t x = 0; x < image.xsize(); ++x) { - row_out[x] = static_cast((row_in[x] - min) * mul); - } - } - } - DumpImage(label, normalized); - } - - void SetInspectorImage3F(const jxl::InspectorImage3F& inspector) { - inspector_image3f_ = inspector; - } - - // Allows hooking intermediate data inspection into various places of the - // processing pipeline. Returns true iff processing should proceed. - bool InspectImage3F(const char* label, const Image3F& image) { - if (inspector_image3f_ != nullptr) { - return inspector_image3f_(label, image); - } - return true; - } - - std::array layers; - size_t num_blocks = 0; - - // Number of blocks that use larger DCT (set by ac_strategy). - size_t num_small_blocks = 0; - size_t num_dct4x8_blocks = 0; - size_t num_afv_blocks = 0; - size_t num_dct8_blocks = 0; - size_t num_dct8x16_blocks = 0; - size_t num_dct8x32_blocks = 0; - size_t num_dct16_blocks = 0; - size_t num_dct16x32_blocks = 0; - size_t num_dct32_blocks = 0; - size_t num_dct32x64_blocks = 0; - size_t num_dct64_blocks = 0; - - std::array dc_pred_usage = {{0}}; - std::array dc_pred_usage_xb = {{0}}; - - int num_butteraugli_iters = 0; - - float max_quant_rescale = 1.0f; - float min_quant_rescale = 1.0f; - float min_bitrate_error = 0.0f; - float max_bitrate_error = 0.0f; - - // If not empty, additional debugging information (e.g. debug images) is - // saved in files with this prefix. - std::string debug_prefix; - - // By how much the decoded image was downsampled relative to the encoded - // image. - size_t downsampling = 1; - - jxl::InspectorImage3F inspector_image3f_; - - std::function dump_image = - nullptr; -}; - -// Used to skip image creation if they won't be written to debug directory. -static inline bool WantDebugOutput(const AuxOut* aux_out) { - // Need valid pointer and filename. - return aux_out != nullptr && !aux_out->debug_prefix.empty(); -} - -} // namespace jxl - -#endif // LIB_JXL_AUX_OUT_H_ diff --git a/media/libjxl/src/lib/jxl/aux_out_fwd.h b/media/libjxl/src/lib/jxl/aux_out_fwd.h deleted file mode 100644 index 29b31ad87a..0000000000 --- a/media/libjxl/src/lib/jxl/aux_out_fwd.h +++ /dev/null @@ -1,28 +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_AUX_OUT_FWD_H_ -#define LIB_JXL_AUX_OUT_FWD_H_ - -#include - -#include "lib/jxl/enc_bit_writer.h" - -namespace jxl { - -struct AuxOut; - -// Helper function that ensures the `bits_written` are charged to `layer` in -// `aux_out`. Example usage: -// BitWriter::Allotment allotment(&writer, max_bits); -// writer.Write(..); writer.Write(..); -// ReclaimAndCharge(&writer, &allotment, layer, aux_out); -void ReclaimAndCharge(BitWriter* JXL_RESTRICT writer, - BitWriter::Allotment* JXL_RESTRICT allotment, - size_t layer, AuxOut* JXL_RESTRICT aux_out); - -} // namespace jxl - -#endif // LIB_JXL_AUX_OUT_FWD_H_ diff --git a/media/libjxl/src/lib/jxl/base/bits.h b/media/libjxl/src/lib/jxl/base/bits.h index 9f86118e72..a79fdc2c99 100644 --- a/media/libjxl/src/lib/jxl/base/bits.h +++ b/media/libjxl/src/lib/jxl/base/bits.h @@ -26,7 +26,8 @@ struct SizeTag {}; template constexpr bool IsSigned() { - return T(0) > T(-1); + // TODO(eustas): remove dupes + return static_cast(0) > static_cast(-1); } // Undefined results for x == 0. diff --git a/media/libjxl/src/lib/jxl/base/byte_order.h b/media/libjxl/src/lib/jxl/base/byte_order.h index ccf1a5e715..cf8d7db082 100644 --- a/media/libjxl/src/lib/jxl/base/byte_order.h +++ b/media/libjxl/src/lib/jxl/base/byte_order.h @@ -6,6 +6,7 @@ #ifndef LIB_JXL_BASE_BYTE_ORDER_H_ #define LIB_JXL_BASE_BYTE_ORDER_H_ +#include #include #include // memcpy @@ -36,10 +37,17 @@ static inline bool IsLittleEndian() { } #endif +static inline bool SwapEndianness(JxlEndianness endianness) { + return ((endianness == JXL_BIG_ENDIAN && IsLittleEndian()) || + (endianness == JXL_LITTLE_ENDIAN && !IsLittleEndian())); +} + #if JXL_COMPILER_MSVC +#define JXL_BSWAP16(x) _byteswap_ushort(x) #define JXL_BSWAP32(x) _byteswap_ulong(x) #define JXL_BSWAP64(x) _byteswap_uint64(x) #else +#define JXL_BSWAP16(x) __builtin_bswap16(x) #define JXL_BSWAP32(x) __builtin_bswap32(x) #define JXL_BSWAP64(x) __builtin_bswap64(x) #endif @@ -127,6 +135,22 @@ static JXL_INLINE uint64_t LoadLE64(const uint8_t* p) { #endif } +// Loads a Big-Endian float +static JXL_INLINE float LoadBEFloat(const uint8_t* p) { + uint32_t u = LoadBE32(p); + float result; + memcpy(&result, &u, 4); + return result; +} + +// Loads a Little-Endian float +static JXL_INLINE float LoadLEFloat(const uint8_t* p) { + uint32_t u = LoadLE32(p); + float result; + memcpy(&result, &u, 4); + return result; +} + static JXL_INLINE void StoreBE16(const uint32_t native, uint8_t* p) { p[0] = (native >> 8) & 0xFF; p[1] = native & 0xFF; @@ -197,6 +221,15 @@ static JXL_INLINE void StoreLE64(const uint64_t native, uint8_t* p) { #endif } +static JXL_INLINE float BSwapFloat(float x) { + uint32_t u; + memcpy(&u, &x, 4); + uint32_t uswap = JXL_BSWAP32(u); + float xswap; + memcpy(&xswap, &uswap, 4); + return xswap; +} + // Big/Little Endian order. struct OrderBE {}; struct OrderLE {}; @@ -204,22 +237,22 @@ struct OrderLE {}; // Wrappers for calling from generic code. static JXL_INLINE void Store16(OrderBE /*tag*/, const uint32_t native, uint8_t* p) { - return StoreBE16(native, p); + StoreBE16(native, p); } static JXL_INLINE void Store16(OrderLE /*tag*/, const uint32_t native, uint8_t* p) { - return StoreLE16(native, p); + StoreLE16(native, p); } static JXL_INLINE void Store32(OrderBE /*tag*/, const uint32_t native, uint8_t* p) { - return StoreBE32(native, p); + StoreBE32(native, p); } static JXL_INLINE void Store32(OrderLE /*tag*/, const uint32_t native, uint8_t* p) { - return StoreLE32(native, p); + StoreLE32(native, p); } static JXL_INLINE uint32_t Load16(OrderBE /*tag*/, const uint8_t* p) { diff --git a/media/libjxl/src/lib/jxl/base/c_callback_support.h b/media/libjxl/src/lib/jxl/base/c_callback_support.h new file mode 100644 index 0000000000..aee0ce5346 --- /dev/null +++ b/media/libjxl/src/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/media/libjxl/src/lib/jxl/base/cache_aligned.cc b/media/libjxl/src/lib/jxl/base/cache_aligned.cc deleted file mode 100644 index 9a9cc585a1..0000000000 --- a/media/libjxl/src/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/media/libjxl/src/lib/jxl/base/cache_aligned.h b/media/libjxl/src/lib/jxl/base/cache_aligned.h deleted file mode 100644 index e57df14837..0000000000 --- a/media/libjxl/src/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/media/libjxl/src/lib/jxl/base/common.h b/media/libjxl/src/lib/jxl/base/common.h new file mode 100644 index 0000000000..0893ef26b5 --- /dev/null +++ b/media/libjxl/src/lib/jxl/base/common.h @@ -0,0 +1,131 @@ +// 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_COMMON_H_ +#define LIB_JXL_BASE_COMMON_H_ + +// Shared constants and helper functions. + +#include +#include +#include +#include +#include +#include +#include + +#include "lib/jxl/base/compiler_specific.h" + +namespace jxl { +// Some enums and typedefs used by more than one header file. + +constexpr size_t kBitsPerByte = 8; // more clear than CHAR_BIT + +constexpr inline size_t RoundUpBitsToByteMultiple(size_t bits) { + return (bits + 7) & ~static_cast(7); +} + +constexpr inline size_t RoundUpToBlockDim(size_t dim) { + return (dim + 7) & ~static_cast(7); +} + +static inline bool JXL_MAYBE_UNUSED SafeAdd(const uint64_t a, const uint64_t b, + uint64_t& sum) { + sum = a + b; + return sum >= a; // no need to check b - either sum >= both or < both. +} + +template +constexpr inline T1 DivCeil(T1 a, T2 b) { + return (a + b - 1) / b; +} + +// Works for any `align`; if a power of two, compiler emits ADD+AND. +constexpr inline size_t RoundUpTo(size_t what, size_t align) { + return DivCeil(what, align) * align; +} + +constexpr double kPi = 3.14159265358979323846264338327950288; + +// Reasonable default for sRGB, matches common monitors. We map white to this +// many nits (cd/m^2) by default. Butteraugli was tuned for 250 nits, which is +// very close. +// NB: This constant is not very "base", but it is shared between modules. +static constexpr float kDefaultIntensityTarget = 255; + +template +constexpr T Pi(T multiplier) { + return static_cast(multiplier * kPi); +} + +// Prior to C++14 (i.e. C++11): provide our own make_unique +#if __cplusplus < 201402L +template +std::unique_ptr make_unique(Args&&... args) { + return std::unique_ptr(new T(std::forward(args)...)); +} +#else +using std::make_unique; +#endif + +typedef std::array Color; + +// Backported std::experimental::to_array + +template +using remove_cv_t = typename std::remove_cv::type; + +template +struct index_sequence {}; + +template +struct make_index_sequence : make_index_sequence {}; + +template +struct make_index_sequence<0, I...> : index_sequence {}; + +namespace detail { + +template +constexpr auto to_array(T (&&arr)[N], index_sequence _) + -> std::array, N> { + return {{std::move(arr[I])...}}; +} + +} // namespace detail + +template +constexpr auto to_array(T (&&arr)[N]) -> std::array, N> { + return detail::to_array(std::move(arr), make_index_sequence()); +} + +template +JXL_INLINE T Clamp1(T val, T low, T hi) { + return val < low ? low : val > hi ? hi : val; +} + +// conversion from integer to string. +template +std::string ToString(T n) { + char data[32] = {}; + if (std::is_floating_point::value) { + // float + snprintf(data, sizeof(data), "%g", static_cast(n)); + } else if (std::is_unsigned::value) { + // unsigned + snprintf(data, sizeof(data), "%llu", static_cast(n)); + } else { + // signed + snprintf(data, sizeof(data), "%lld", static_cast(n)); + } + return data; +} + +#define JXL_JOIN(x, y) JXL_DO_JOIN(x, y) +#define JXL_DO_JOIN(x, y) x##y + +} // namespace jxl + +#endif // LIB_JXL_BASE_COMMON_H_ diff --git a/media/libjxl/src/lib/jxl/base/compiler_specific.h b/media/libjxl/src/lib/jxl/base/compiler_specific.h index 7aa8b99151..cb1a8a0386 100644 --- a/media/libjxl/src/lib/jxl/base/compiler_specific.h +++ b/media/libjxl/src/lib/jxl/base/compiler_specific.h @@ -8,10 +8,14 @@ // Macros for compiler version + nonstandard keywords, e.g. __builtin_expect. -#include +#include #include "lib/jxl/base/sanitizer_definitions.h" +#if JXL_ADDRESS_SANITIZER || JXL_MEMORY_SANITIZER || JXL_THREAD_SANITIZER +#include "sanitizer/common_interface_defs.h" // __sanitizer_print_stack_trace +#endif // defined(*_SANITIZER) + // #if is shorter and safer than #ifdef. *_VERSION are zero if not detected, // otherwise 100 * major + minor version. Note that other packages check for // #ifdef COMPILER_MSVC, so we cannot use that same name. @@ -61,14 +65,6 @@ #define JXL_NORETURN #endif -#if JXL_COMPILER_MSVC -#define JXL_UNREACHABLE __assume(false) -#elif JXL_COMPILER_CLANG || JXL_COMPILER_GCC >= 405 -#define JXL_UNREACHABLE __builtin_unreachable() -#else -#define JXL_UNREACHABLE -#endif - #if JXL_COMPILER_MSVC #define JXL_MAYBE_UNUSED #else @@ -96,6 +92,11 @@ #define JXL_UNLIKELY(expr) __builtin_expect(!!(expr), 0) #endif +#if JXL_COMPILER_MSVC +#include +using ssize_t = intptr_t; +#endif + // Returns a void* pointer which the compiler then assumes is N-byte aligned. // Example: float* JXL_RESTRICT aligned = (float*)JXL_ASSUME_ALIGNED(in, 32); // @@ -149,8 +150,71 @@ #define JXL_FORMAT(idx_fmt, idx_arg) #endif +// C++ standard. +#if defined(_MSC_VER) && !defined(__clang__) && defined(_MSVC_LANG) && \ + _MSVC_LANG > __cplusplus +#define JXL_CXX_LANG _MSVC_LANG +#else +#define JXL_CXX_LANG __cplusplus +#endif + +// Known / distinguished C++ standards. +#define JXL_CXX_17 201703 + +// In most cases we consider build as "debug". Use `NDEBUG` for release build. +#if defined(JXL_IS_DEBUG_BUILD) +#undef JXL_IS_DEBUG_BUILD +#define JXL_IS_DEBUG_BUILD 1 +#elif defined(NDEBUG) +#define JXL_IS_DEBUG_BUILD 0 +#else +#define JXL_IS_DEBUG_BUILD 1 +#endif + +#if defined(JXL_CRASH_ON_ERROR) +#undef JXL_CRASH_ON_ERROR +#define JXL_CRASH_ON_ERROR 1 +#else +#define JXL_CRASH_ON_ERROR 0 +#endif + +#if JXL_CRASH_ON_ERROR && !JXL_IS_DEBUG_BUILD +#error "JXL_CRASH_ON_ERROR requires JXL_IS_DEBUG_BUILD" +#endif + +// Pass -DJXL_DEBUG_ON_ALL_ERROR at compile time to print debug messages on +// all error (fatal and non-fatal) status. +#if defined(JXL_DEBUG_ON_ALL_ERROR) +#undef JXL_DEBUG_ON_ALL_ERROR +#define JXL_DEBUG_ON_ALL_ERROR 1 +#else +#define JXL_DEBUG_ON_ALL_ERROR 0 +#endif + +#if JXL_DEBUG_ON_ALL_ERROR && !JXL_IS_DEBUG_BUILD +#error "JXL_DEBUG_ON_ALL_ERROR requires JXL_IS_DEBUG_BUILD" +#endif + +// Pass -DJXL_DEBUG_ON_ABORT={0} to disable the debug messages on +// (debug) JXL_ENSURE and JXL_DASSERT. +#if !defined(JXL_DEBUG_ON_ABORT) +#define JXL_DEBUG_ON_ABORT JXL_IS_DEBUG_BUILD +#endif // JXL_DEBUG_ON_ABORT + +#if JXL_DEBUG_ON_ABORT && !JXL_IS_DEBUG_BUILD +#error "JXL_DEBUG_ON_ABORT requires JXL_IS_DEBUG_BUILD" +#endif + +#if JXL_ADDRESS_SANITIZER || JXL_MEMORY_SANITIZER || JXL_THREAD_SANITIZER +#define JXL_PRINT_STACK_TRACE() __sanitizer_print_stack_trace(); +#else +#define JXL_PRINT_STACK_TRACE() +#endif + #if JXL_COMPILER_MSVC -using ssize_t = intptr_t; +#define JXL_CRASH() __debugbreak(), (void)abort() +#else +#define JXL_CRASH() (void)__builtin_trap() #endif #endif // LIB_JXL_BASE_COMPILER_SPECIFIC_H_ diff --git a/media/libjxl/src/lib/jxl/base/data_parallel.cc b/media/libjxl/src/lib/jxl/base/data_parallel.cc deleted file mode 100644 index 20a911255c..0000000000 --- a/media/libjxl/src/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/media/libjxl/src/lib/jxl/base/data_parallel.h b/media/libjxl/src/lib/jxl/base/data_parallel.h index 666925aea4..e39f46c75b 100644 --- a/media/libjxl/src/lib/jxl/base/data_parallel.h +++ b/media/libjxl/src/lib/jxl/base/data_parallel.h @@ -9,11 +9,13 @@ // Portable, low-overhead C++11 ThreadPool alternative to OpenMP for // data-parallel computations. -#include -#include +#include -#include "jxl/parallel_runner.h" -#include "lib/jxl/base/bits.h" +#include +#include +#include + +#include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/status.h" #if JXL_COMPILER_MSVC // suppress warnings about the const & applied to function types @@ -25,7 +27,7 @@ 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; @@ -44,15 +46,34 @@ class ThreadPool { // Precondition: begin <= end. template Status Run(uint32_t begin, uint32_t end, const InitFunc& init_func, - const DataFunc& data_func, const char* caller = "") { - JXL_ASSERT(begin <= end); + const DataFunc& data_func, const char* caller) { + JXL_ENSURE(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. - return (*runner_)(runner_opaque_, static_cast(&call_state), - &call_state.CallInitFunc, &call_state.CallDataFunc, begin, - end) == 0; + if (!runner_) { + void* jpegxl_opaque = static_cast(&call_state); + if (call_state.CallInitFunc(jpegxl_opaque, 1) != + JXL_PARALLEL_RET_SUCCESS) { + return JXL_FAILURE("Failed to initialize thread"); + } + for (uint32_t i = begin; i < end; i++) { + call_state.CallDataFunc(jpegxl_opaque, i, 0); + } + if (call_state.HasError()) { + return JXL_FAILURE("[%s] failed", caller); + } + return true; + } + JxlParallelRetCode ret = (*runner_)( + runner_opaque_, static_cast(&call_state), + &call_state.CallInitFunc, &call_state.CallDataFunc, begin, end); + + if (ret != JXL_PARALLEL_RET_SUCCESS || call_state.HasError()) { + return JXL_FAILURE("[%s] failed", caller); + } + return true; } // Use this as init_func when no initialization is needed. @@ -69,32 +90,36 @@ class ThreadPool { // JxlParallelRunInit interface. static int CallInitFunc(void* jpegxl_opaque, size_t num_threads) { - const auto* self = + auto* self = static_cast*>(jpegxl_opaque); // Returns -1 when the internal init function returns false Status to // indicate an error. - return self->init_func_(num_threads) ? 0 : -1; + if (!self->init_func_(num_threads)) { + self->has_error_ = true; + return JXL_PARALLEL_RET_RUNNER_ERROR; + } + return JXL_PARALLEL_RET_SUCCESS; } // JxlParallelRunFunction interface. static void CallDataFunc(void* jpegxl_opaque, uint32_t value, size_t thread_id) { - const auto* self = + auto* self = static_cast*>(jpegxl_opaque); - return self->data_func_(value, thread_id); + if (self->has_error_) return; + if (!self->data_func_(value, thread_id)) { + self->has_error_ = true; + } } + bool HasError() const { return has_error_; } + private: const InitFunc& init_func_; const DataFunc& data_func_; + std::atomic has_error_{false}; }; - // 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/media/libjxl/src/lib/jxl/exif.h b/media/libjxl/src/lib/jxl/base/exif.h similarity index 72% rename from media/libjxl/src/lib/jxl/exif.h rename to media/libjxl/src/lib/jxl/base/exif.h index 06652fc730..acaa1a1ce4 100644 --- a/media/libjxl/src/lib/jxl/exif.h +++ b/media/libjxl/src/lib/jxl/base/exif.h @@ -3,15 +3,20 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -#ifndef LIB_JXL_EXIF_H_ -#define LIB_JXL_EXIF_H_ +#ifndef LIB_JXL_BASE_EXIF_H_ +#define LIB_JXL_BASE_EXIF_H_ // Basic parsing of Exif (just enough for the render-impacting things // like orientation) -#include "jxl/codestream_header.h" -#include "lib/jxl/base/padded_bytes.h" -#include "lib/jxl/image_metadata.h" +#include + +#include +#include +#include + +#include "lib/jxl/base/byte_order.h" +#include "lib/jxl/base/compiler_specific.h" namespace jxl { @@ -19,7 +24,7 @@ constexpr uint16_t kExifOrientationTag = 274; // Checks if a blob looks like Exif, and if so, sets bigendian // according to the tiff endianness -inline bool IsExif(const std::vector& exif, bool* bigendian) { +JXL_INLINE bool IsExif(const std::vector& exif, bool* bigendian) { if (exif.size() < 12) return false; // not enough bytes for a valid exif blob const uint8_t* t = exif.data(); if (LoadLE32(t) == 0x2A004D4D) { @@ -33,14 +38,15 @@ inline bool IsExif(const std::vector& exif, bool* bigendian) { } // Finds the position of an Exif tag, or 0 if it is not found -inline size_t FindExifTagPosition(const std::vector& exif, - uint16_t tagname) { +JXL_INLINE size_t FindExifTagPosition(const std::vector& exif, + uint16_t tagname) { bool bigendian; if (!IsExif(exif, &bigendian)) return 0; const uint8_t* t = exif.data() + 4; - uint32_t offset = (bigendian ? LoadBE32(t) : LoadLE32(t)); + uint64_t offset = (bigendian ? LoadBE32(t) : LoadLE32(t)); if (exif.size() < 12 + offset + 2 || offset < 8) return 0; t += offset - 4; + if (offset + 2 >= exif.size()) return 0; uint16_t nb_tags = (bigendian ? LoadBE16(t) : LoadLE16(t)); t += 2; while (nb_tags > 0) { @@ -54,15 +60,15 @@ inline size_t FindExifTagPosition(const std::vector& exif, return 0; } -// TODO (jon): tag 1 can be used to represent Adobe RGB 1998 if it has value +// TODO(jon): tag 1 can be used to represent Adobe RGB 1998 if it has value // "R03" -// TODO (jon): set intrinsic dimensions according to +// TODO(jon): set intrinsic dimensions according to // https://discourse.wicg.io/t/proposal-exif-image-resolution-auto-and-from-image/4326/24 // Parses the Exif data just enough to extract any render-impacting info. // If the Exif data is invalid or could not be parsed, then it is treated // as a no-op. -inline void InterpretExif(const std::vector& exif, - JxlOrientation* orientation) { +JXL_INLINE void InterpretExif(const std::vector& exif, + JxlOrientation* orientation) { bool bigendian; if (!IsExif(exif, &bigendian)) return; size_t o_pos = FindExifTagPosition(exif, kExifOrientationTag); @@ -73,7 +79,6 @@ inline void InterpretExif(const std::vector& exif, uint32_t count = (bigendian ? LoadBE32(t) : LoadLE32(t)); t += 4; uint16_t value = (bigendian ? LoadBE16(t) : LoadLE16(t)); - t += 4; if (type == 3 && count == 1 && value >= 1 && value <= 8) { *orientation = static_cast(value); } @@ -82,4 +87,4 @@ inline void InterpretExif(const std::vector& exif, } // namespace jxl -#endif // LIB_JXL_EXIF_H_ +#endif // LIB_JXL_BASE_EXIF_H_ diff --git a/media/libjxl/src/lib/jxl/fast_math-inl.h b/media/libjxl/src/lib/jxl/base/fast_math-inl.h similarity index 94% rename from media/libjxl/src/lib/jxl/fast_math-inl.h rename to media/libjxl/src/lib/jxl/base/fast_math-inl.h index 5c48034290..fa749cc257 100644 --- a/media/libjxl/src/lib/jxl/fast_math-inl.h +++ b/media/libjxl/src/lib/jxl/base/fast_math-inl.h @@ -5,17 +5,17 @@ // Fast SIMD math ops (log2, encoder only, cos, erf for splines) -#if defined(LIB_JXL_FAST_MATH_INL_H_) == defined(HWY_TARGET_TOGGLE) -#ifdef LIB_JXL_FAST_MATH_INL_H_ -#undef LIB_JXL_FAST_MATH_INL_H_ +#if defined(LIB_JXL_BASE_FAST_MATH_INL_H_) == defined(HWY_TARGET_TOGGLE) +#ifdef LIB_JXL_BASE_FAST_MATH_INL_H_ +#undef LIB_JXL_BASE_FAST_MATH_INL_H_ #else -#define LIB_JXL_FAST_MATH_INL_H_ +#define LIB_JXL_BASE_FAST_MATH_INL_H_ #endif #include -#include "lib/jxl/common.h" -#include "lib/jxl/rational_polynomial-inl.h" +#include "lib/jxl/base/common.h" +#include "lib/jxl/base/rational_polynomial-inl.h" HWY_BEFORE_NAMESPACE(); namespace jxl { namespace HWY_NAMESPACE { @@ -216,11 +216,11 @@ V CubeRootAndAdd(const V x, const V add) { } // namespace jxl HWY_AFTER_NAMESPACE(); -#endif // LIB_JXL_FAST_MATH_INL_H_ +#endif // LIB_JXL_BASE_FAST_MATH_INL_H_ #if HWY_ONCE -#ifndef FAST_MATH_ONCE -#define FAST_MATH_ONCE +#ifndef LIB_JXL_BASE_FAST_MATH_ONCE +#define LIB_JXL_BASE_FAST_MATH_ONCE namespace jxl { inline float FastLog2f(float f) { return HWY_STATIC_DISPATCH(FastLog2f)(f); } @@ -232,5 +232,5 @@ inline float FastCosf(float f) { return HWY_STATIC_DISPATCH(FastCosf)(f); } inline float FastErff(float f) { return HWY_STATIC_DISPATCH(FastErff)(f); } } // namespace jxl -#endif // FAST_MATH_ONCE +#endif // LIB_JXL_BASE_FAST_MATH_ONCE #endif // HWY_ONCE diff --git a/media/libjxl/src/lib/jxl/base/file_io.h b/media/libjxl/src/lib/jxl/base/file_io.h deleted file mode 100644 index 8c7777c945..0000000000 --- a/media/libjxl/src/lib/jxl/base/file_io.h +++ /dev/null @@ -1,152 +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_FILE_IO_H_ -#define LIB_JXL_BASE_FILE_IO_H_ - -// Helper functions for reading/writing files. - -#include -#include - -#include -#include -#include - -#include "lib/jxl/base/compiler_specific.h" -#include "lib/jxl/base/padded_bytes.h" -#include "lib/jxl/base/status.h" - -namespace jxl { - -// Returns extension including the dot, or empty string if none. Assumes -// filename is not a hidden file (e.g. ".bashrc"). May be called with a pathname -// if the filename contains a dot and/or no other path component does. -static inline std::string Extension(const std::string& filename) { - const size_t pos = filename.rfind('.'); - if (pos == std::string::npos) return std::string(); - return filename.substr(pos); -} - -// RAII, ensures files are closed even when returning early. -class FileWrapper { - public: - FileWrapper(const FileWrapper& other) = delete; - FileWrapper& operator=(const FileWrapper& other) = delete; - - explicit FileWrapper(const std::string& pathname, const char* mode) - : file_(pathname == "-" ? (mode[0] == 'r' ? stdin : stdout) - : fopen(pathname.c_str(), mode)), - close_on_delete_(pathname != "-") { -#ifdef _WIN32 - struct __stat64 s = {}; - const int err = _stat64(pathname.c_str(), &s); - const bool is_file = (s.st_mode & S_IFREG) != 0; -#else - struct stat s = {}; - const int err = stat(pathname.c_str(), &s); - const bool is_file = S_ISREG(s.st_mode); -#endif - if (err == 0 && is_file) { - size_ = s.st_size; - } - } - - ~FileWrapper() { - if (file_ != nullptr && close_on_delete_) { - const int err = fclose(file_); - JXL_CHECK(err == 0); - } - } - - // We intend to use FileWrapper as a replacement of FILE. - // NOLINTNEXTLINE(google-explicit-constructor) - operator FILE*() const { return file_; } - - int64_t size() { return size_; } - - private: - FILE* const file_; - bool close_on_delete_ = true; - int64_t size_ = -1; -}; - -template -static inline Status ReadFile(const std::string& pathname, - ContainerType* JXL_RESTRICT bytes) { - FileWrapper f(pathname, "rb"); - if (f == nullptr) - return JXL_FAILURE("Failed to open file for reading: %s", pathname.c_str()); - - // Get size of file in bytes - const int64_t size = f.size(); - if (size < 0) { - // Size is unknown, loop reading chunks until EOF. - bytes->clear(); - std::list> chunks; - - size_t total_size = 0; - while (true) { - std::vector chunk(16 * 1024); - const size_t bytes_read = fread(chunk.data(), 1, chunk.size(), f); - if (ferror(f) || bytes_read > chunk.size()) { - return JXL_FAILURE("Error reading %s", pathname.c_str()); - } - - chunk.resize(bytes_read); - total_size += bytes_read; - if (bytes_read != 0) { - chunks.emplace_back(std::move(chunk)); - } - if (feof(f)) { - break; - } - } - bytes->resize(total_size); - size_t pos = 0; - for (const auto& chunk : chunks) { - // Needed in case ContainerType is std::string, whose data() is const. - char* bytes_writable = reinterpret_cast(&(*bytes)[0]); - memcpy(bytes_writable + pos, chunk.data(), chunk.size()); - pos += chunk.size(); - } - } else { - // Size is known, read the file directly. - bytes->resize(static_cast(size)); - size_t pos = 0; - while (pos < bytes->size()) { - // Needed in case ContainerType is std::string, whose data() is const. - char* bytes_writable = reinterpret_cast(&(*bytes)[0]); - const size_t bytes_read = - fread(bytes_writable + pos, 1, bytes->size() - pos, f); - if (bytes_read == 0) return JXL_FAILURE("Failed to read"); - pos += bytes_read; - } - JXL_ASSERT(pos == bytes->size()); - } - return true; -} - -template -static inline Status WriteFile(const ContainerType& bytes, - const std::string& pathname) { - FileWrapper f(pathname, "wb"); - if (f == nullptr) return JXL_FAILURE("Failed to open file for writing"); - - size_t pos = 0; - while (pos < bytes.size()) { - const size_t bytes_written = - fwrite(bytes.data() + pos, 1, bytes.size() - pos, f); - if (bytes_written == 0) return JXL_FAILURE("Failed to write"); - pos += bytes_written; - } - JXL_ASSERT(pos == bytes.size()); - - return true; -} - -} // namespace jxl - -#endif // LIB_JXL_BASE_FILE_IO_H_ diff --git a/media/libjxl/src/lib/jxl/base/float.h b/media/libjxl/src/lib/jxl/base/float.h new file mode 100644 index 0000000000..50af582b6f --- /dev/null +++ b/media/libjxl/src/lib/jxl/base/float.h @@ -0,0 +1,100 @@ +// 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_FLOAT_H_ +#define LIB_JXL_BASE_FLOAT_H_ + +#include +#include +#include +#include + +#include "lib/jxl/base/byte_order.h" +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/status.h" + +namespace jxl { + +namespace detail { +// Based on highway scalar implementation, for testing +static JXL_INLINE float LoadFloat16(uint16_t bits16) { + const uint32_t sign = bits16 >> 15; + const uint32_t biased_exp = (bits16 >> 10) & 0x1F; + const uint32_t mantissa = bits16 & 0x3FF; + + // Subnormal or zero + if (biased_exp == 0) { + const float subnormal = + (1.0f / 16384) * (static_cast(mantissa) * (1.0f / 1024)); + return sign ? -subnormal : subnormal; + } + + // Normalized: convert the representation directly (faster than ldexp/tables). + const uint32_t biased_exp32 = biased_exp + (127 - 15); + const uint32_t mantissa32 = mantissa << (23 - 10); + const uint32_t bits32 = (sign << 31) | (biased_exp32 << 23) | mantissa32; + + float result; + memcpy(&result, &bits32, 4); + return result; +} +} // namespace detail + +template +static Status JXL_INLINE LoadFloatRow(const uint8_t* src, size_t count, + size_t stride, JxlDataType type, + bool little_endian, float scale, + SaveFloatAtFn callback) { + switch (type) { + case JXL_TYPE_FLOAT: + if (little_endian) { + for (size_t i = 0; i < count; ++i) { + callback(i, LoadLEFloat(src + stride * i)); + } + } else { + for (size_t i = 0; i < count; ++i) { + callback(i, LoadBEFloat(src + stride * i)); + } + } + return true; + + case JXL_TYPE_UINT8: + for (size_t i = 0; i < count; ++i) { + callback(i, src[stride * i] * scale); + } + return true; + + case JXL_TYPE_UINT16: + if (little_endian) { + for (size_t i = 0; i < count; ++i) { + callback(i, LoadLE16(src + stride * i) * scale); + } + } else { + for (size_t i = 0; i < count; ++i) { + callback(i, LoadBE16(src + stride * i) * scale); + } + } + return true; + + case JXL_TYPE_FLOAT16: + if (little_endian) { + for (size_t i = 0; i < count; ++i) { + callback(i, detail::LoadFloat16(LoadLE16(src + stride * i))); + } + } else { + for (size_t i = 0; i < count; ++i) { + callback(i, detail::LoadFloat16(LoadBE16(src + stride * i))); + } + } + return true; + + default: + return JXL_FAILURE("Unsupported sample format"); + } +} + +} // namespace jxl + +#endif // LIB_JXL_BASE_FLOAT_H_ diff --git a/media/libjxl/src/lib/jxl/base/include_jpeglib.h b/media/libjxl/src/lib/jxl/base/include_jpeglib.h new file mode 100644 index 0000000000..f72d13d04b --- /dev/null +++ b/media/libjxl/src/lib/jxl/base/include_jpeglib.h @@ -0,0 +1,20 @@ +// Copyright (c) the JPEG XL Project Authors. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#ifndef LIB_JXL_BASE_INCLUDE_JPEGLIB_H_ +#define LIB_JXL_BASE_INCLUDE_JPEGLIB_H_ + +// Using this header ensures that includes go in the right order, +// not alphabetically sorted. + +// NOLINTBEGIN +/* clang-format off */ +#include // IWYU pragma: keep +#include // IWYU pragma: keep +#include // IWYU pragma: keep +/* clang-format on */ +// NOLINTEND + +#endif // LIB_JXL_BASE_INCLUDE_JPEGLIB_H_ diff --git a/media/libjxl/src/lib/jxl/base/matrix_ops.h b/media/libjxl/src/lib/jxl/base/matrix_ops.h new file mode 100644 index 0000000000..e1f8753932 --- /dev/null +++ b/media/libjxl/src/lib/jxl/base/matrix_ops.h @@ -0,0 +1,86 @@ +// 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_MATRIX_OPS_H_ +#define LIB_JXL_BASE_MATRIX_OPS_H_ + +// 3x3 matrix operations. + +#include +#include // abs +#include + +#include "lib/jxl/base/status.h" + +namespace jxl { + +typedef std::array Vector3; +typedef std::array Vector3d; +typedef std::array Matrix3x3; +typedef std::array Matrix3x3d; + +// Computes C = A * B, where A, B, C are 3x3 matrices. +template +void Mul3x3Matrix(const Matrix& a, const Matrix& b, Matrix& c) { + for (size_t x = 0; x < 3; x++) { + alignas(16) Vector3d temp{b[0][x], b[1][x], b[2][x]}; // transpose + for (size_t y = 0; y < 3; y++) { + c[y][x] = a[y][0] * temp[0] + a[y][1] * temp[1] + a[y][2] * temp[2]; + } + } +} + +// Computes C = A * B, where A is 3x3 matrix and B is vector. +template +void Mul3x3Vector(const Matrix& a, const Vector& b, Vector& c) { + for (size_t y = 0; y < 3; y++) { + double e = 0; + for (size_t x = 0; x < 3; x++) { + e += a[y][x] * b[x]; + } + c[y] = e; + } +} + +// Inverts a 3x3 matrix in place. +template +Status Inv3x3Matrix(Matrix& matrix) { + // Intermediate computation is done in double precision. + Matrix3x3d temp; + temp[0][0] = static_cast(matrix[1][1]) * matrix[2][2] - + static_cast(matrix[1][2]) * matrix[2][1]; + temp[0][1] = static_cast(matrix[0][2]) * matrix[2][1] - + static_cast(matrix[0][1]) * matrix[2][2]; + temp[0][2] = static_cast(matrix[0][1]) * matrix[1][2] - + static_cast(matrix[0][2]) * matrix[1][1]; + temp[1][0] = static_cast(matrix[1][2]) * matrix[2][0] - + static_cast(matrix[1][0]) * matrix[2][2]; + temp[1][1] = static_cast(matrix[0][0]) * matrix[2][2] - + static_cast(matrix[0][2]) * matrix[2][0]; + temp[1][2] = static_cast(matrix[0][2]) * matrix[1][0] - + static_cast(matrix[0][0]) * matrix[1][2]; + temp[2][0] = static_cast(matrix[1][0]) * matrix[2][1] - + static_cast(matrix[1][1]) * matrix[2][0]; + temp[2][1] = static_cast(matrix[0][1]) * matrix[2][0] - + static_cast(matrix[0][0]) * matrix[2][1]; + temp[2][2] = static_cast(matrix[0][0]) * matrix[1][1] - + static_cast(matrix[0][1]) * matrix[1][0]; + double det = matrix[0][0] * temp[0][0] + matrix[0][1] * temp[1][0] + + matrix[0][2] * temp[2][0]; + if (std::abs(det) < 1e-10) { + return JXL_FAILURE("Matrix determinant is too close to 0"); + } + double idet = 1.0 / det; + for (size_t j = 0; j < 3; j++) { + for (size_t i = 0; i < 3; i++) { + matrix[j][i] = temp[j][i] * idet; + } + } + return true; +} + +} // namespace jxl + +#endif // LIB_JXL_BASE_MATRIX_OPS_H_ diff --git a/media/libjxl/src/lib/jxl/base/override.h b/media/libjxl/src/lib/jxl/base/override.h index 1f8b657974..da070e6e62 100644 --- a/media/libjxl/src/lib/jxl/base/override.h +++ b/media/libjxl/src/lib/jxl/base/override.h @@ -6,13 +6,15 @@ #ifndef LIB_JXL_BASE_OVERRIDE_H_ #define LIB_JXL_BASE_OVERRIDE_H_ +#include + // 'Trool' for command line arguments: force enable/disable, or use default. namespace jxl { // No effect if kDefault, otherwise forces a feature (typically a FrameHeader // flag) on or off. -enum class Override : int { kOn = 1, kOff = 0, kDefault = -1 }; +enum class Override : int8_t { kOn = 1, kOff = 0, kDefault = -1 }; static inline Override OverrideFromBool(bool flag) { return flag ? Override::kOn : Override::kOff; diff --git a/media/libjxl/src/lib/jxl/base/padded_bytes.cc b/media/libjxl/src/lib/jxl/base/padded_bytes.cc deleted file mode 100644 index 11e4bff6fe..0000000000 --- a/media/libjxl/src/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/media/libjxl/src/lib/jxl/base/padded_bytes.h b/media/libjxl/src/lib/jxl/base/padded_bytes.h deleted file mode 100644 index 4534ddf863..0000000000 --- a/media/libjxl/src/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/media/libjxl/src/lib/jxl/base/profiler.h b/media/libjxl/src/lib/jxl/base/profiler.h deleted file mode 100644 index 13f95d2b7a..0000000000 --- a/media/libjxl/src/lib/jxl/base/profiler.h +++ /dev/null @@ -1,32 +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_PROFILER_H_ -#define LIB_JXL_BASE_PROFILER_H_ - -// High precision, low overhead time measurements. Returns exact call counts and -// total elapsed time for user-defined 'zones' (code regions, i.e. C++ scopes). -// -// To use the profiler you must set the JPEGXL_ENABLE_PROFILER CMake flag, which -// defines PROFILER_ENABLED and links against the libjxl_profiler library. - -// If zero, this file has no effect and no measurements will be recorded. -#ifndef PROFILER_ENABLED -#define PROFILER_ENABLED 0 -#endif // PROFILER_ENABLED - -#if PROFILER_ENABLED - -#include "lib/profiler/profiler.h" - -#else // !PROFILER_ENABLED - -#define PROFILER_ZONE(name) -#define PROFILER_FUNC -#define PROFILER_PRINT_RESULTS() - -#endif // PROFILER_ENABLED - -#endif // LIB_JXL_BASE_PROFILER_H_ diff --git a/media/libjxl/src/lib/jxl/base/random.cc b/media/libjxl/src/lib/jxl/base/random.cc deleted file mode 100644 index 0fbe758068..0000000000 --- a/media/libjxl/src/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 "lib/jxl/fast_math-inl.h" - -namespace jxl { - -Rng::GeometricDistribution::GeometricDistribution(float p) - : inv_log_1mp(1.0 / FastLog2f(1 - p)) {} - -uint32_t Rng::Geometric(const GeometricDistribution& dist) { - float f = UniformF(0, 1); - float log = FastLog2f(1 - f) * dist.inv_log_1mp; - return static_cast(log); -} - -} // namespace jxl diff --git a/media/libjxl/src/lib/jxl/base/random.h b/media/libjxl/src/lib/jxl/base/random.h index 663b88c95d..6922e282e8 100644 --- a/media/libjxl/src/lib/jxl/base/random.h +++ b/media/libjxl/src/lib/jxl/base/random.h @@ -14,12 +14,13 @@ #include #include +#include #include "lib/jxl/base/status.h" namespace jxl { struct Rng { - explicit Rng(size_t seed) + explicit Rng(uint64_t seed) : s{static_cast(0x94D049BB133111EBull), static_cast(0xBF58476D1CE4E5B9ull) + seed} {} @@ -69,15 +70,18 @@ struct Rng { 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) { diff --git a/media/libjxl/src/lib/jxl/rational_polynomial-inl.h b/media/libjxl/src/lib/jxl/base/rational_polynomial-inl.h similarity index 88% rename from media/libjxl/src/lib/jxl/rational_polynomial-inl.h rename to media/libjxl/src/lib/jxl/base/rational_polynomial-inl.h index 176e24092c..7a89c0bac1 100644 --- a/media/libjxl/src/lib/jxl/rational_polynomial-inl.h +++ b/media/libjxl/src/lib/jxl/base/rational_polynomial-inl.h @@ -5,13 +5,15 @@ // Fast SIMD evaluation of rational polynomials for approximating functions. -#if defined(LIB_JXL_RATIONAL_POLYNOMIAL_INL_H_) == defined(HWY_TARGET_TOGGLE) -#ifdef LIB_JXL_RATIONAL_POLYNOMIAL_INL_H_ -#undef LIB_JXL_RATIONAL_POLYNOMIAL_INL_H_ +#if defined(LIB_JXL_BASE_RATIONAL_POLYNOMIAL_INL_H_) == \ + defined(HWY_TARGET_TOGGLE) +#ifdef LIB_JXL_BASE_RATIONAL_POLYNOMIAL_INL_H_ +#undef LIB_JXL_BASE_RATIONAL_POLYNOMIAL_INL_H_ #else -#define LIB_JXL_RATIONAL_POLYNOMIAL_INL_H_ +#define LIB_JXL_BASE_RATIONAL_POLYNOMIAL_INL_H_ #endif +#include #include #include @@ -41,7 +43,7 @@ struct FastDivision { } V operator()(const V n, const V d) const { -#if 1 // Faster on SKX +#if JXL_TRUE // Faster on SKX return Div(n, d); #else return n * ReciprocalNR(d); @@ -87,6 +89,9 @@ HWY_INLINE HWY_MAYBE_UNUSED V EvalRationalPolynomial(const D d, const V x, if (kDegP >= 7) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 7) * 4))); if (kDegQ >= 7) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 7) * 4))); + static_assert(kDegP < 8, "Polynomial degree is too high"); + static_assert(kDegQ < 8, "Polynomial degree is too high"); + return FastDivision()(yp, yq); } @@ -95,4 +100,4 @@ HWY_INLINE HWY_MAYBE_UNUSED V EvalRationalPolynomial(const D d, const V x, } // namespace HWY_NAMESPACE } // namespace jxl HWY_AFTER_NAMESPACE(); -#endif // LIB_JXL_RATIONAL_POLYNOMIAL_INL_H_ +#endif // LIB_JXL_BASE_RATIONAL_POLYNOMIAL_INL_H_ diff --git a/media/libjxl/src/lib/jxl/base/rect.h b/media/libjxl/src/lib/jxl/base/rect.h new file mode 100644 index 0000000000..31fe12dfb9 --- /dev/null +++ b/media/libjxl/src/lib/jxl/base/rect.h @@ -0,0 +1,186 @@ +// 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_RECT_H_ +#define LIB_JXL_BASE_RECT_H_ + +#include +#include +#include +#include +#include +#include +#include // std::move + +#include "lib/jxl/base/compiler_specific.h" +#include "lib/jxl/base/status.h" + +namespace jxl { + +// Rectangular region in image(s). Factoring this out of Image instead of +// shifting the pointer by x0/y0 allows this to apply to multiple images with +// different resolutions (e.g. color transform and quantization field). +// Can compare using SameSize(rect1, rect2). +template +class RectT { + public: + // Most windows are xsize_max * ysize_max, except those on the borders where + // begin + size_max > end. + constexpr RectT(T xbegin, T ybegin, size_t xsize_max, size_t ysize_max, + T xend, T yend) + : x0_(xbegin), + y0_(ybegin), + xsize_(ClampedSize(xbegin, xsize_max, xend)), + ysize_(ClampedSize(ybegin, ysize_max, yend)) {} + + // Construct with origin and known size (typically from another Rect). + constexpr RectT(T xbegin, T ybegin, size_t xsize, size_t ysize) + : x0_(xbegin), y0_(ybegin), xsize_(xsize), ysize_(ysize) {} + + // Construct a rect that covers a whole image/plane/ImageBundle etc. + template + explicit RectT(const ImageT& image) + : RectT(0, 0, image.xsize(), image.ysize()) {} + + RectT() : RectT(0, 0, 0, 0) {} + + RectT(const RectT&) = default; + RectT& operator=(const RectT&) = default; + + // Construct a subrect that resides in an image/plane/ImageBundle etc. + template + RectT Crop(const ImageT& image) const { + return Intersection(RectT(image)); + } + + // Construct a subrect that resides in the [0, ysize) x [0, xsize) region of + // the current rect. + RectT Crop(size_t area_xsize, size_t area_ysize) const { + return Intersection(RectT(0, 0, area_xsize, area_ysize)); + } + + JXL_MUST_USE_RESULT RectT Intersection(const RectT& other) const { + return RectT(std::max(x0_, other.x0_), std::max(y0_, other.y0_), xsize_, + ysize_, std::min(x1(), other.x1()), + std::min(y1(), other.y1())); + } + + JXL_MUST_USE_RESULT RectT Translate(int64_t x_offset, + int64_t y_offset) const { + return RectT(x0_ + x_offset, y0_ + y_offset, xsize_, ysize_); + } + + template