pax_global_header00006660000000000000000000000064147213433530014517gustar00rootroot0000000000000052 comment=794a5dcf0d54f9f0b20d288a12e87afb91d20dfc libjxl-0.11.1/000077500000000000000000000000001472134335300130635ustar00rootroot00000000000000libjxl-0.11.1/.bazelignore000066400000000000000000000000141472134335300153600ustar00rootroot00000000000000third_party libjxl-0.11.1/.clang-format000066400000000000000000000001351472134335300154350ustar00rootroot00000000000000BasedOnStyle: Google IncludeCategories: - Regex: '^ # instead . # - modernize-return-braced-init-list: this often doesn't improve readability. # - modernize-use-auto: is too aggressive towards using auto. # - modernize-use-default-member-init: with a mix of constructors and default # member initialization this can be confusing if enforced. # - modernize-use-trailing-return-type: does not improve readability when used # systematically. # - modernize-use-using: typedefs are ok. # # - readability-else-after-return: It doesn't always improve readability. # - readability-static-accessed-through-instance # It is often more useful and readable to access a constant of a passed # variable (like d.N) instead of using the type of the variable that could be # long and complex. # - readability-uppercase-literal-suffix: we write 1.0f, not 1.0F. Checks: >- bugprone-*, clang-*, -clang-diagnostic-unused-command-line-argument, google-*, modernize-*, performance-*, readability-*, -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: >- bugprone-argument-comment, bugprone-macro-parentheses, bugprone-suspicious-string-compare, bugprone-use-after-move, clang-*, clang-analyzer-*, -clang-diagnostic-unused-command-line-argument, google-build-using-namespace, google-explicit-constructor, google-readability-braces-around-statements, google-readability-namespace-comments, modernize-use-override, readability-inconsistent-declaration-parameter-name # We are only interested in the headers from this projects, excluding # third_party/ and build/. 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: bugprone-signed-char-misuse.CharTypdefsToIgnore value: 'int8_t' libjxl-0.11.1/.github/000077500000000000000000000000001472134335300144235ustar00rootroot00000000000000libjxl-0.11.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001472134335300166065ustar00rootroot00000000000000libjxl-0.11.1/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000017251472134335300213050ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots or example input/output images to help explain your problem. **Environment** - OS: [e.g. Windows] - Compiler version: [e.g. clang 11.0.1] - CPU type: [e.g. x86_64] - cjxl/djxl version string: [e.g. cjxl [v0.3.7 | SIMD supported: SSE4,Scalar]] **Additional context** Add any other context about the problem here. libjxl-0.11.1/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011231472134335300223300ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. libjxl-0.11.1/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000015521472134335300202270ustar00rootroot00000000000000 ### Description ### Pull Request Checklist - [ ] **CLA Signed**: Have you signed the [Contributor License Agreement](https://code.google.com/legal/individual-cla-v1.0.html) (individual or corporate, as appropriate)? Only contributions from signed contributors can be accepted. - [ ] **Authors**: Have you considered adding your name to the [AUTHORS](AUTHORS) file? - [ ] **Code Style**: Have you ensured your code adheres to the project's coding style guidelines? You can use `./ci.sh lint` for automatic code formatting. Please review the full [contributing guidelines](https://github.com/libjxl/libjxl/blob/main/CONTRIBUTING.md) for more details. libjxl-0.11.1/.github/dependabot.yml000066400000000000000000000012771472134335300172620ustar00rootroot00000000000000# 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. # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" - package-ecosystem: pip directory: /doc/sphinx schedule: interval: daily libjxl-0.11.1/.github/workflows/000077500000000000000000000000001472134335300164605ustar00rootroot00000000000000libjxl-0.11.1/.github/workflows/build_test.yml000066400000000000000000000315321472134335300213450ustar00rootroot00000000000000# 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. # Workflow for building and running tests. name: Build/Test *nix on: merge_group: push: branches: - main - v*.*.x pull_request: types: [opened, reopened, labeled, unlabeled, synchronize] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: build_test: name: ${{ startsWith(matrix.os, 'macos-') && 'MacOS' || 'Ubuntu' }} Build ${{ matrix.name }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: ${{ matrix.os || 'ubuntu-latest' }} strategy: fail-fast: false matrix: # We have one job per "name" in the matrix. Attributes are set on the # specific job names. name: [release, debug, asan, msan, tsan, scalar] include: - name: release mode: release run_bench: true test_in_pr: true cmake_args: >- -DJPEGXL_TEST_TOOLS=ON -DJPEGLI_LIBJPEG_LIBRARY_VERSION="8.2.2" -DJPEGLI_LIBJPEG_LIBRARY_SOVERSION="8" # Track static stack size on build and check it doesn't exceed 3 kB. env_stack_size: 1 max_stack: 2400 # Conformance tooling test requires numpy. apt_pkgs: doxygen graphviz python3-numpy - name: lowprecision mode: release run_bench: true test_in_pr: true cmake_args: -DCMAKE_CXX_FLAGS=-DJXL_HIGH_PRECISION=0 - name: debug # Runs on AVX3 CPUs require more stack than others. Make sure to # test on AVX3-enabled CPUs when changing this value. env_test_stack_size: 4000 # Build scalar-only hwy instructions. - name: scalar mode: release cxxflags: -DHWY_COMPILE_ONLY_SCALAR -DFJXL_ENABLE_AVX2=0 -DFJXL_ENABLE_AVX512=0 # Disabling optional features to speed up MSAN build a little bit. - name: msan os: ubuntu-24.04 skip_install: true cmake_args: >- -DJPEGXL_ENABLE_DEVTOOLS=OFF -DJPEGXL_ENABLE_PLUGINS=OFF -DJPEGXL_ENABLE_VIEWERS=OFF apt_pkgs: clang-18 cc: clang-18 cxx: clang++-18 - name: asan skip_install: true - name: tsan skip_install: true - name: coverage env_test_stack_size: 2048 skip_install: true # TODO: understand why this does not work ctest_args: -E 'test_jpegli_jni_wrapper' # Build with support for decoding to JPEG bytes disabled. Produces a # smaller build if only decoding to pixels is needed. - name: release-nojpeg mode: release cmake_args: >- -DJPEGXL_ENABLE_TRANSCODE_JPEG=OFF -DJPEGXL_ENABLE_PLUGINS=OFF -DJPEGXL_ENABLE_VIEWERS=OFF # Build with jxl_cms based on lcms2 library. - name: release-lcms2 mode: release cmake_args: >- -DJPEGXL_ENABLE_SKCMS=OFF - name: release-system-lcms2 mode: release cmake_args: >- -DJPEGXL_ENABLE_SKCMS=OFF -DJPEGXL_FORCE_SYSTEM_LCMS2=ON apt_pkgs: liblcms2-dev # static build is impossible skip_install: true # Build optimized for binary size, all features not needed for # reconstructing pixels is disabled. - name: release:minimal mode: release cmake_args: >- -DJPEGXL_ENABLE_TRANSCODE_JPEG=OFF -DJPEGXL_ENABLE_BOXES=OFF -DJPEGXL_ENABLE_PLUGINS=OFF -DJPEGXL_ENABLE_VIEWERS=OFF # Builds with gcc in release mode - name: release:gcc8 os: ubuntu-20.04 mode: release apt_pkgs: gcc-8 g++-8 cmake_args: >- -DCMAKE_C_COMPILER=gcc-8 -DCMAKE_CXX_COMPILER=g++-8 # Builds with clang-7 in release mode - name: release:clang-7 os: ubuntu-20.04 mode: release skip_install: true apt_pkgs: clang-7 cc: clang-7 cxx: clang++-7 - name: release:osx os: macos-latest mode: release skip_install: true cmake_args: >- -DCMAKE_FIND_FRAMEWORK=NEVER env: CCACHE_DIR: ${{ github.workspace }}/.ccache # Whether we track the stack size. STACK_SIZE: ${{ matrix.env_stack_size }} TEST_STACK_LIMIT: ${{ matrix.env_test_stack_size }} WILL_TEST: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && matrix.name != 'coverage' && (matrix.test_in_pr || contains(github.event.pull_request.labels.*.name, 'CI:full'))) }} WILL_BUILD: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && matrix.name != 'coverage') }} WILL_BENCH: ${{ github.event_name != 'merge_group' && matrix.run_bench }} # Temporarily disable doc-building, re-enable when sphinx is working again. # WILL_DOC: ${{ github.event_name != 'merge_group' && matrix.name == 'release' }} WILL_DOC: false WILL_COV: ${{ github.event_name == 'push' && matrix.name == 'coverage' }} JPEGXL_OPT_DBG: true FASTER_MSAN_BUILD: 1 steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Install build deps Ubuntu if: startsWith(matrix.os, 'macos-') == false run: | sudo rm -f /var/lib/man-db/auto-update sudo apt update sudo apt install -y \ ccache \ clang \ cmake \ graphviz \ imagemagick \ libbenchmark-dev \ libbenchmark-tools \ libbrotli-dev \ libgdk-pixbuf2.0-dev \ libgif-dev \ libgtest-dev \ libgtk2.0-dev \ libjpeg-dev \ libjpeg-turbo-progs \ libopenexr-dev \ libpng-dev \ libwebp-dev \ ninja-build \ pkg-config \ xvfb \ ${{ matrix.apt_pkgs }} \ # echo "CC=${{ matrix.cc || 'clang' }}" >> $GITHUB_ENV echo "CXX=${{ matrix.cxx || 'clang++' }}" >> $GITHUB_ENV - name: Install build deps MacOS if: startsWith(matrix.os, 'macos-') run: | # Should be already installed: # brew install brotli giflib jpeg-turbo libpng zlib # Not required, since we skip building documentation # brew install doxygen brew install binutils ccache coreutils google-benchmark googletest ninja sdl2 - name: Checkout the source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: true fetch-depth: 2 - name: Setup the Homebrew prefixes if: startsWith(matrix.os, 'macos-') run: | CMAKE_PREFIX_PATH=`brew --prefix brotli`:`brew --prefix giflib`:`brew --prefix google-benchmark`:`brew --prefix jpeg-turbo`:`brew --prefix libpng`:`brew --prefix sdl2`:`brew --prefix zlib` echo "CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" >> $GITHUB_ENV - name: Suppress doxygen target if: matrix.name != 'release' run: | echo "TARGETS=all" >> $GITHUB_ENV - name: Setup the LLVM source path if: matrix.name == 'msan' && env.WILL_BUILD == 'true' run: | LLVM_ROOT=${GITHUB_WORKSPACE}/llvm_root mkdir -p ${LLVM_ROOT} echo "LLVM_ROOT=${LLVM_ROOT}" >> $GITHUB_ENV - name: Cache LLVM sources if: matrix.name == 'msan' && env.WILL_BUILD == 'true' uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 with: path: ${{ env.LLVM_ROOT }} key: llvm - name: Checkout the LLVM source if: matrix.name == 'msan' && env.WILL_BUILD == 'true' uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: false repository: llvm/llvm-project ref: llvmorg-18.1.3 # NB: 15.0.0 does not build ¯\_(ツ)_/¯ path: llvm_root - name: Sphinx dependencies # Dependencies for sphinx HTML documentation if: env.WILL_DOC == 'true' run: | pip3 install -r doc/sphinx/requirements.txt - name: Install gcovr if: env.WILL_COV == 'true' run: pip install gcovr - name: Git environment id: git-env run: | echo "parent=$(git rev-parse ${{ github.sha }}^)" >> $GITHUB_OUTPUT shell: bash - name: ccache uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 with: path: ${{ env.CCACHE_DIR }} # When the cache hits the key it is not updated, so if this is a rebuild # of the same Pull Request it will reuse the cache if still around. For # either Pull Requests or new pushes to main, this will use the parent # hash as the starting point from the restore-keys entry. key: build-${{ runner.os }}-${{ github.sha }}-${{ matrix.name }} restore-keys: | build-${{ runner.os }}-${{ steps.git-env.outputs.parent }}-${{ matrix.name }} - name: Build if: env.WILL_BUILD == 'true' run: | mkdir -p ${CCACHE_DIR} echo "max_size = 200M" > ${CCACHE_DIR}/ccache.conf mode="${{ matrix.mode }}" build_tests=$([ "$WILL_TEST" == "true" ] && echo "ON" || echo "OFF") [[ -n "${mode}" ]] || mode="${{ matrix.name }}" ./ci.sh ${mode} -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DBUILD_TESTING=${build_tests} \ ${{ matrix.cmake_args }} env: SKIP_TEST: 1 CMAKE_CXX_FLAGS: ${{ matrix.cxxflags }} - name: Build stats if: env.WILL_BUILD == 'true' run: | awk '!/^#/ {total[$4]+=($2-$1);cntr[$4]+=1} END {for (key in total) print total[key]/cntr[key] " " key}' build/.ninja_log | sort -n | tail -n 25 - name: ccache stats run: ccache --show-stats - name: Build stats ${{ matrix.name }} if: env.WILL_BUILD == 'true' && matrix.mode == 'release' run: | SHARED_LIB_EXT="${{ startsWith(matrix.os, 'macos-') && 'dylib' || 'so' }}" SELECT_BINUTILS="${{ startsWith(matrix.os, 'macos-') && '--binutils `brew --prefix binutils`/bin/' || '' }}" tools/scripts/build_stats.py --save build/stats.json \ --max-stack ${{ matrix.max_stack || '0' }} ${SELECT_BINUTILS} \ cjxl djxl libjxl.${SHARED_LIB_EXT} libjxl_dec.${SHARED_LIB_EXT} # Check that we can build the example project against the installed libs. - name: Install and build examples if: env.WILL_BUILD == 'true' && matrix.mode == 'release' && !matrix.skip_install run: | set -x sudo cmake --build build -- install cmake -Bbuild-example -Hexamples -G Ninja cmake --build build-example # Test that the built binaries run. echo -e -n "PF\n1 1\n-1.0\n\0\0\x80\x3f\0\0\x80\x3f\0\0\x80\x3f" > test.pfm build-example/encode_oneshot test.pfm test.jxl build-example/decode_oneshot test.jxl dec.pfm dec.icc # Run the tests on push and when requested in pull_request. - name: Test ${{ matrix.mode }} if: env.WILL_TEST == 'true' run: | ./ci.sh test ${{ matrix.ctest_args }} # Print the running time summary for the slowest tests. - name: Test runtime stats if: env.WILL_TEST == 'true' run: | sort build/Testing/Temporary/CTestCostData.txt -k 3 -n | tail -n 20 || true - name: Build HTML documentation (sphinx/readthetdocs) if: env.WILL_DOC == 'true' run: | cmake --build build -- rtd-html - name: Coverage report if: env.WILL_COV == 'true' run: | ./ci.sh coverage_report - name: Coverage upload to Codecov if: env.WILL_COV == 'true' uses: codecov/codecov-action@5c47607acb93fed5485fdbf7232e8a31425f672a # v5.0.2 with: flags: unittests files: build/coverage.xml - name: Fast benchmark ${{ matrix.mode }} if: env.WILL_BENCH == 'true' run: | cat /proc/cpuinfo | grep MHz | sort | uniq lscpu BENCHMARK_NUM_THREADS=3 STORE_IMAGES=0 ./ci.sh fast_benchmark # Run gbench once, just to make sure it runs, not for actual benchmarking. # This doesn't work on MSAN because we use gbench library from the system # which is not instrumented by MSAN. - name: gbench check if: env.WILL_BENCH == 'true' run: | ./ci.sh gbench --benchmark_min_time=0 libjxl-0.11.1/.github/workflows/build_test_bazel.yml000066400000000000000000000032471472134335300225240ustar00rootroot00000000000000# 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. # Workflow for building and running tests. name: Build/Test Bazel on: merge_group: push: branches: - main - v*.*.x pull_request: types: [opened, reopened, labeled, unlabeled, synchronize] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: build_test: name: Bazel if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: ubuntu-latest strategy: fail-fast: false steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: true fetch-depth: 1 - name: Patch run: | cd third_party/highway git fetch origin 31fbbd7ce1e4179a32d86688cd67316556f582bf git checkout 31fbbd7ce1e4179a32d86688cd67316556f582bf git apply ${{ github.workspace }}/.github/workflows/highway.patch - name: Build run: bazel build -c opt ...:all - name: Test if: | github.event_name == 'push' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'CI:full')) run: bazel test -c opt --test_output=errors ...:all libjxl-0.11.1/.github/workflows/build_test_cross.yml000066400000000000000000000236571472134335300225670ustar00rootroot00000000000000# 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. # Workflow for building and running tests. name: Build/Test Cross on: merge_group: push: branches: - main - v*.*.x pull_request: types: [opened, reopened, labeled, unlabeled, synchronize] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: compile: name: Cross-compiling ${{ matrix.identifier }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: [ubuntu-latest] container: image: debian:bookworm strategy: fail-fast: false matrix: identifier: [arm64, arm64-sve, arm64-lowprecision, armhf, i386] include: - arch: arm64 identifier: arm64 build_target: aarch64-linux-gnu cmake_args: - -DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/qemu-aarch64-static - arch: arm64 identifier: arm64-sve build_target: aarch64-linux-gnu cmake_args: - -DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/qemu-aarch64-static - -DJPEGXL_ENABLE_OPENEXR=off - -DJPEGXL_ENABLE_SIZELESS_VECTORS=on - -DJPEGXL_WARNINGS_AS_ERRORS=off cmake_flags: -march=armv8-a+sve c_compiler: aarch64-linux-gnu-gcc cxx_compiler: aarch64-linux-gnu-g++ disable_tests: true - arch: arm64 identifier: arm64-lowprecision build_target: aarch64-linux-gnu cmake_args: - -DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/qemu-aarch64-static - -DCMAKE_CXX_FLAGS=-DJXL_HIGH_PRECISION=0 - arch: armhf identifier: armhf build_target: arm-linux-gnueabihf cmake_args: [-DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/qemu-arm-static] - arch: i386 identifier: i386 test_in_pr: true build_target: i686-linux-gnu env: BUILD_DIR: build WILL_RUN_TESTS: ${{ (github.event_name == 'push' || (github.event_name == 'pull_request' && (matrix.test_in_pr || contains(github.event.pull_request.labels.*.name, 'CI:full')))) }} steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Warmup apt shell: bash run: | set -x rm -f /var/lib/man-db/auto-update apt-get update -y apt-get install -y ca-certificates debian-ports-archive-keyring git python3 - name: Checkout the source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: true fetch-depth: 1 - name: Setup apt shell: bash run: | set -x dpkg --add-architecture "${{ matrix.arch }}" python3 ./tools/scripts/transform_sources_list.py "amd64,${{ matrix.arch }}" - name: Install build deps shell: bash run: | set -x apt update pkgs=( # Build dependencies cmake doxygen graphviz ninja-build pkg-config qemu-user-static unzip xdg-utils xvfb # Toolchain for cross-compiling. clang-14 g++-aarch64-linux-gnu libc6-dev-${{ matrix.arch }}-cross libstdc++-12-dev-${{ matrix.arch }}-cross libstdc++-12-dev:${{ matrix.arch }} # Dependencies libbrotli-dev:${{ matrix.arch }} libgif-dev:${{ matrix.arch }} libjpeg-dev:${{ matrix.arch }} libpng-dev:${{ matrix.arch }} libwebp-dev:${{ matrix.arch }} # For OpenEXR: libilmbase-dev:${{ matrix.arch }} libopenexr-dev:${{ matrix.arch }} # GTK plugins libgdk-pixbuf2.0-dev:${{ matrix.arch }} libgtk2.0-dev:${{ matrix.arch }} ) if [[ "${{ matrix.build_target }}" != "x86_64-linux-gnu" ]]; then pkgs+=( binutils-${{ matrix.build_target }} gcc-${{ matrix.build_target }} ) fi if [[ "${{ matrix.arch }}" != "i386" ]]; then pkgs+=( # TCMalloc libgoogle-perftools-dev:${{ matrix.arch }} libgoogle-perftools4:${{ matrix.arch }} libtcmalloc-minimal4:${{ matrix.arch }} libunwind-dev:${{ matrix.arch }} ) fi DEBIAN_FRONTEND=noninteractive apt install -y "${pkgs[@]}" echo "CC=${{ matrix.c_compiler || 'clang-14' }}" >> $GITHUB_ENV echo "CXX=${{ matrix.cxx_compiler || 'clang++-14' }}" >> $GITHUB_ENV - name: Build run: | CMAKE_FLAGS="${{ matrix.cmake_flags }}" ./ci.sh release \ -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DJPEGXL_ENABLE_JNI=OFF \ ${{ join(matrix.cmake_args, ' ') }} env: SKIP_TEST: 1 BUILD_TARGET: ${{ matrix.build_target }} TARGETS: ${{ env.WILL_RUN_TESTS == 'true' && 'all_tests cjxl djxl libjxl.so libjxl_dec.so' || 'all' }} - name: Build stats run: | tools/scripts/build_stats.py --save build/stats.json \ --binutils ${{ matrix.build_target }}- \ --max-stack ${{ matrix.max_stack || '0' }} \ cjxl djxl libjxl.so libjxl_dec.so - name: Prepare artefacts if: env.WILL_RUN_TESTS == 'true' run: | find ./build -regextype egrep -type f -regex '.*\.(a|h|jar|log|o)' find ./build -type f -executable > executable.lst cp /etc/apt/sources.list.d/debian.sources ./ - name: Test ranging if: ${{ !matrix.disable_tests }} run: | mkdir -p ./build/Testing/Temporary unzip ./tools/scripts/test_cost-${{ matrix.identifier }}.zip -d ./build/Testing/Temporary - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 if: env.WILL_RUN_TESTS == 'true' with: name: cross_binary-${{ matrix.identifier }} path: | build/ ci.sh debian.sources executable.lst testdata/ retention-days: 1 test: name: Testing ${{ matrix.identifier }} shard ${{ matrix.shard_number }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} needs: compile runs-on: [ubuntu-latest] container: image: debian:bookworm strategy: fail-fast: false matrix: shard_number: [0, 1, 2, 3, 4, 5, 6, 7] identifier: [arm64, arm64-lowprecision, armhf, i386] include: - arch: arm64 - identifier: arm64 last_shard: 8 #- arch: arm64 #- identifier: arm64-sve # last_shard: 8 - arch: arm64 identifier: arm64-lowprecision last_shard: 8 - arch: armhf identifier: armhf last_shard: 8 - arch: i386 identifier: i386 test_in_pr: true last_shard: 4 env: BUILD_DIR: build UPLOAD_TEST_COST: false LAST_SHARD: ${{ false && 1 || matrix.last_shard}} # Run the tests on push and when requested in pull_request. WILL_RUN_TESTS: ${{ (github.event_name == 'push' || (github.event_name == 'pull_request' && (matrix.test_in_pr || contains(github.event.pull_request.labels.*.name, 'CI:full')))) }} steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 if: (matrix.shard_number < env.LAST_SHARD) && (env.WILL_RUN_TESTS == 'true') with: name: cross_binary-${{ matrix.identifier }} - name: Setup apt if: (matrix.shard_number < env.LAST_SHARD) && (env.WILL_RUN_TESTS == 'true') shell: bash run: | set -x rm -f /var/lib/man-db/auto-update apt-get update -y apt-get install -y ca-certificates debian-ports-archive-keyring dpkg --add-architecture "${{ matrix.arch }}" cp ./debian.sources /etc/apt/sources.list.d/ - name: Install build deps if: (matrix.shard_number < env.LAST_SHARD) && (env.WILL_RUN_TESTS == 'true') shell: bash run: | set -x apt update pkgs=( # Build dependencies cmake qemu-user-static # Dependencies libbrotli-dev:${{ matrix.arch }} libgif-dev:${{ matrix.arch }} libjpeg-dev:${{ matrix.arch }} libpng-dev:${{ matrix.arch }} libwebp-dev:${{ matrix.arch }} # For OpenEXR: libilmbase-dev:${{ matrix.arch }} libopenexr-dev:${{ matrix.arch }} ) DEBIAN_FRONTEND=noninteractive apt install -y "${pkgs[@]}" - name: Prepare if: (env.UPLOAD_TEST_COST == 'true') && (matrix.shard_number == 0) && (env.WILL_RUN_TESTS == 'true') run: | rm build/Testing/Temporary/CTestCostData.txt - name: Test if: (matrix.shard_number < env.LAST_SHARD) && (env.WILL_RUN_TESTS == 'true') run: | chmod +x ./ci.sh chmod +x `cat executable.lst` ./ci.sh test \ -I ${{ matrix.shard_number }},,${{ env.LAST_SHARD }} \ -E '(bash_test|conformance_tooling_test|test_jxl_jni_wrapper|test_jpegli_jni_wrapper)' - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 name: Upload test cost if: (env.UPLOAD_TEST_COST == 'true') && (matrix.shard_number == 0) && (env.WILL_RUN_TESTS == 'true') with: name: test_cost-${{ matrix.identifier }} path: | build/Testing/Temporary/CTestCostData.txt retention-days: 1 libjxl-0.11.1/.github/workflows/build_test_md.yml000066400000000000000000000033771472134335300220330ustar00rootroot00000000000000# 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. # Workflow for building and running tests. name: Build/Test MD on: pull_request: types: [opened, reopened, labeled, unlabeled, synchronize] paths: - '**.md' permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: build_test: name: Ubuntu Build ${{ matrix.name }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} # Include all names of required jobs here strategy: matrix: include: - name: release - name: debug - name: scalar - name: asan - name: release-nojpeg - name: release-lcms2 - name: release:gcc8 runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - run: 'echo "markdown only changes: no build required"' windows_msys: name: Windows MSYS2 / ${{ matrix.msystem }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} # Include all msystem of required jobs here strategy: matrix: include: - msystem: clang64 # - msystem: clang32 runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - run: 'echo "markdown only changes: no build required"' libjxl-0.11.1/.github/workflows/build_test_msys2.yml000066400000000000000000000065451472134335300225100ustar00rootroot00000000000000# 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. # Workflow for building and running tests. name: Build/Test MSYS2 on: merge_group: push: branches: - main - v*.*.x pull_request: types: [opened, reopened, labeled, unlabeled, synchronize] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: build_test: name: Windows MSYS2 / ${{ matrix.msystem }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: windows-latest continue-on-error: ${{ matrix.faulty || false }} strategy: fail-fast: false matrix: include: - msystem: mingw64 - msystem: clang64 - msystem: mingw32 disable_tests: - ButteraugliTest.Lossless - ButteraugliTest.Distmap disable_benchmark: true # "Legacy" toolchains are being "phased-out": # https://www.msys2.org/news/#2023-12-13-starting-to-drop-some-32-bit-packages # clang32 is already missing required "gtest" and "libavif" # - msystem: clang32 # disable_benchmark: true defaults: run: shell: msys2 {0} steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: true fetch-depth: 1 - uses: msys2/setup-msys2@07aeda7763550b267746a772dcea5e5ac3340b36 # v2 with: msystem: ${{ matrix.msystem }} update: true path-type: inherit install: >- base-devel git procps pacboy: >- brotli:p cmake:p giflib:p gtest:p libavif:p libjpeg-turbo:p libpng:p libwebp:p ninja:p toolchain:p - name: CMake configure run: | cmake \ -DCMAKE_BUILD_TYPE=Release \ -DJPEGXL_ENABLE_JNI=OFF \ -DJPEGXL_ENABLE_MANPAGES=OFF \ -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DJPEGXL_FORCE_SYSTEM_GTEST=ON \ -B build \ -G Ninja - name: CMake build run: cmake --build build - name: Test if: | github.event_name == 'push' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'CI:full')) run: ctest --test-dir build --parallel 2 --output-on-failure -E "${{ join(matrix.disable_tests, '|') }}" - name: Fast benchmark ${{ matrix.msystem }} if: github.event_name != 'merge_group' && !(matrix.disable_benchmark || false) run: | systeminfo | grep -A 1 Processor wmic cpu get Name, NumberofCores, NumberOfLogicalProcessors, MaxClockSpeed mkdir tmp export TMPDIR=`pwd`/tmp export PATH=$PATH:`pwd`/build/lib BENCHMARK_NUM_THREADS=2 STORE_IMAGES=0 ./ci.sh fast_benchmark libjxl-0.11.1/.github/workflows/build_test_wasm.yml000066400000000000000000000100351472134335300223670ustar00rootroot00000000000000# 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. # Workflow for building and running tests. name: Build/Test WASM on: merge_group: push: branches: - main - v*.*.x pull_request: types: [opened, reopened, labeled, unlabeled, synchronize] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: build_test: name: WASM wasm32/${{ matrix.variant }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: ubuntu-latest env: CCACHE_DIR: ${{ github.workspace }}/.ccache BUILD_TARGET: wasm32 EM_VERSION: 3.1.51 NODE_VERSION: 21 strategy: matrix: include: - variant: scalar - variant: simd-128 - variant: simd-256 steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: true fetch-depth: 1 - name: Install build deps shell: bash run: | set -x sudo rm -f /var/lib/man-db/auto-update sudo apt update pkgs=( # Build dependencies ccache cmake doxygen graphviz ninja-build pkg-config ) DEBIAN_FRONTEND=noninteractive sudo apt install -y "${pkgs[@]}" - name: Git environment id: git-env run: | echo "parent=$(git rev-parse ${{ github.sha }}^)" >> $GITHUB_OUTPUT shell: bash - name: ccache uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 with: path: ${{ env.CCACHE_DIR }} key: build-wasm-${{ runner.os }}-${{ github.sha }}-${{ matrix.variant }} restore-keys: | build-wasm-${{ runner.os }}-${{ steps.git-env.outputs.parent }}-${{ matrix.variant }} - name: Install node uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: node-version: ${{env.NODE_VERSION}} - name: Get non-EMSDK node path run: which node >> $HOME/.base_node_path - name: Install emsdk uses: mymindstorm/setup-emsdk@6ab9eb1bda2574c4ddb79809fc9247783eaf9021 # v14 # TODO(deymo): We could cache this action but it doesn't work when running # in a matrix. with: version: ${{env.EM_VERSION}} no-cache: true - name: Set EMSDK node version run: | echo "NODE_JS='$(cat $HOME/.base_node_path)'" >> $EMSDK/.emscripten emsdk construct_env # TODO(deymo): Build and install other dependencies like libpng, libjpeg, # etc. - name: Build run: | mkdir -p ${CCACHE_DIR} echo "max_size = 200M" > ${CCACHE_DIR}/ccache.conf if [[ "${{ matrix.variant }}" == "simd-128" ]]; then export ENABLE_WASM_SIMD=1 fi if [[ "${{ matrix.variant }}" == "simd-256" ]]; then export ENABLE_WASM_SIMD=2 fi ./ci.sh release \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DJPEGXL_ENABLE_BENCHMARK=OFF \ -DJPEGXL_ENABLE_DEVTOOLS=OFF \ -DJPEGXL_ENABLE_DOXYGEN=OFF \ -DJPEGXL_ENABLE_EXAMPLES=OFF \ -DJPEGXL_ENABLE_JNI=OFF \ -DJPEGXL_ENABLE_MANPAGES=OFF \ -DJPEGXL_ENABLE_PLUGINS=OFF \ -DJPEGXL_ENABLE_TOOLS=OFF \ -DJPEGXL_ENABLE_VIEWERS=OFF env: SKIP_TEST: 1 TARGETS: all - name: ccache stats run: ccache --show-stats - name: Test if: | github.event_name == 'push' || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'CI:full')) run: | ./ci.sh test libjxl-0.11.1/.github/workflows/codeql.yml000066400000000000000000000071521472134335300204570ustar00rootroot00000000000000# 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. # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: ["main"] pull_request: # The branches below must be a subset of the branches above branches: ["main"] schedule: - cron: "0 0 * * 1" permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: analyze: name: Analyze if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: ["cpp"] # CodeQL supports [ $supported-codeql-languages ] # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. - name: Install build deps run: | sudo rm -f /var/lib/man-db/auto-update sudo apt update sudo apt install -y \ ccache \ clang \ cmake \ doxygen \ graphviz \ imagemagick \ libbenchmark-dev \ libbenchmark-tools \ libbrotli-dev \ libgdk-pixbuf2.0-dev \ libgif-dev \ libgtest-dev \ libgtk2.0-dev \ libjpeg-dev \ libjpeg-turbo-progs \ libopenexr-dev \ libpng-dev \ libwebp-dev \ ninja-build \ pkg-config \ xvfb \ ${{ matrix.apt_pkgs }} \ # echo "CC=${{ matrix.cc || 'clang' }}" >> $GITHUB_ENV echo "CXX=${{ matrix.cxx || 'clang++' }}" >> $GITHUB_ENV - name: Checkout the source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: true fetch-depth: 2 - name: Build run: | ./ci.sh opt -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DBUILD_TESTING=OFF env: SKIP_TEST: 1 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 with: category: "/language:${{matrix.language}}" libjxl-0.11.1/.github/workflows/conformance.yml000066400000000000000000000171231472134335300215010ustar00rootroot00000000000000# 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. # Workflow for running conformance tests. name: Conformance on: merge_group: push: branches: - main - v*.*.x pull_request: types: [opened, reopened, labeled, unlabeled, synchronize] permissions: contents: read env: CONFORMANCE_REPO_HASH: a3ee00672dd1b58c2b5a7d5e8b9e4a7b9e53ec1e LIBJXL_VERSION: "0.11.1" LIBJXL_ABI_VERSION: "0.11" concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: warmup: # If necessary, fetch files just once, before tests are run. name: Warmup caches if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Checkout the conformance source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: libjxl/conformance ref: ${{ env.CONFORMANCE_REPO_HASH }} path: conformance - name: Cache uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 with: path: ${{ github.workspace }}/conformance/.objects key: conformance-refs - name: Download and link conformance files run: | ${{ github.workspace }}/conformance/scripts/download_and_symlink.sh build: name: Conformance Build ${{ matrix.name }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - name: AVX3 cflags: -DHWY_DISABLED_TARGETS=HWY_AVX3-1 - name: AVX2 cflags: -DHWY_DISABLED_TARGETS=HWY_AVX2-1 - name: SSE4 cflags: -DHWY_DISABLED_TARGETS=HWY_SSE4-1 - name: SSSE3 cflags: -DHWY_DISABLED_TARGETS=HWY_SSSE3-1 - name: EMU128 cflags: -DHWY_COMPILE_ONLY_EMU128=1 - name: SCALAR cflags: -DHWY_COMPILE_ONLY_SCALAR=1 - name: SCALAR_ASAN cflags: -DHWY_COMPILE_ONLY_SCALAR=1 build_type: asan env: CCACHE_DIR: ${{ github.workspace }}/.ccache steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Install build deps run: | sudo rm -f /var/lib/man-db/auto-update sudo apt update sudo apt install -y \ ccache \ clang \ cmake \ doxygen \ graphviz \ libbenchmark-dev \ libbenchmark-tools \ libbrotli-dev \ libgdk-pixbuf2.0-dev \ libgif-dev \ libgtest-dev \ libgtk2.0-dev \ libjpeg-dev \ libopenexr-dev \ libpng-dev \ libwebp-dev \ ninja-build \ pkg-config \ xvfb \ ${{ matrix.apt_pkgs }} \ # echo "CC=clang" >> $GITHUB_ENV echo "CXX=clang++" >> $GITHUB_ENV - name: Checkout the jxl source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: true fetch-depth: 2 - name: Git environment id: git-env run: | echo "parent=$(git rev-parse ${{ github.sha }}^)" >> $GITHUB_OUTPUT shell: bash - name: ccache uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 with: path: ${{ env.CCACHE_DIR }} # When the cache hits the key it is not updated, so if this is a rebuild # of the same Pull Request it will reuse the cache if still around. For # either Pull Requests or new pushes to main, this will use the parent # hash as the starting point from the restore-keys entry. key: conformance-${{ runner.os }}-${{ github.sha }}-${{ matrix.name }} restore-keys: | conformance-${{ runner.os }}-${{ steps.git-env.outputs.parent }}-${{ matrix.name }} - name: Build run: | mkdir -p ${CCACHE_DIR} echo "max_size = 200M" > ${CCACHE_DIR}/ccache.conf CMAKE_FLAGS="${{ matrix.cflags }}" \ TARGETS="tools/djxl" \ ./ci.sh ${{ matrix.build_type || 'release' }} -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DBUILD_TESTING=OFF # Flatten the artifacts directory structure cp tools/conformance/conformance.py build/tools/conformance cp tools/conformance/lcms2.py build/tools/conformance cp build/tools/djxl build/tools/conformance cp build/lib/libjxl.so.${{ env.LIBJXL_VERSION }} build/tools/conformance cp build/lib/libjxl_cms.so.${{ env.LIBJXL_VERSION }} build/tools/conformance cp build/lib/libjxl_threads.so.${{ env.LIBJXL_VERSION }} build/tools/conformance env: SKIP_TEST: 1 - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: conformance_binary-${{ matrix.name }} path: | build/tools/conformance/conformance.py build/tools/conformance/lcms2.py build/tools/conformance/djxl build/tools/conformance/libjxl.so.${{ env.LIBJXL_VERSION }} build/tools/conformance/libjxl_cms.so.${{ env.LIBJXL_VERSION }} build/tools/conformance/libjxl_threads.so.${{ env.LIBJXL_VERSION }} - name: ccache stats run: ccache --show-stats run: name: Conformance Test ${{ matrix.name }} on ${{ matrix.target }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} needs: [warmup, build] runs-on: ubuntu-latest strategy: fail-fast: false matrix: name: [main_level5, main_level10] target: [AVX3, AVX2, SSE4, SSSE3, EMU128, SCALAR, SCALAR_ASAN] steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Install deps run: | pip install numpy - name: Checkout the conformance source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: repository: libjxl/conformance ref: ${{ env.CONFORMANCE_REPO_HASH }} path: conformance - name: Cache uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 with: path: ${{ github.workspace }}/conformance/.objects key: conformance-refs - name: Download and link conformance files run: | ${{ github.workspace }}/conformance/scripts/download_and_symlink.sh - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: conformance_binary-${{ matrix.target }} - name: Run conformance tests run: | chmod +x djxl ln -s libjxl.so.${{ env.LIBJXL_VERSION }} libjxl.so.${{ env.LIBJXL_ABI_VERSION }} ln -s libjxl_cms.so.${{ env.LIBJXL_VERSION }} libjxl_cms.so.${{ env.LIBJXL_ABI_VERSION }} ln -s libjxl_threads.so.${{ env.LIBJXL_VERSION }} libjxl_threads.so.${{ env.LIBJXL_ABI_VERSION }} export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:`pwd` python conformance.py \ --decoder=`pwd`/djxl \ --corpus=`pwd`/conformance/testcases/${{ matrix.name }}.txt libjxl-0.11.1/.github/workflows/debug_ci.yml000066400000000000000000000101521472134335300207430ustar00rootroot00000000000000# 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. # Workflow for building and then debugging on a specific commit. name: Build and Test debugging on: push: branches: - ci-*-debug permissions: contents: read jobs: cross_compile_ubuntu: name: Cross-compiling ${{ matrix.build_target }} ${{ matrix.variant }} runs-on: [ubuntu-latest] container: image: debian:bookworm strategy: fail-fast: false matrix: include: - arch: i386 build_target: i686-linux-gnu env: BUILD_DIR: build steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Setup apt shell: bash run: | set -x rm -f /var/lib/man-db/auto-update apt-get update -y apt-get install -y ca-certificates debian-ports-archive-keyring dpkg --add-architecture "${{ matrix.arch }}" # Update the sources.list with the split of supported architectures. bkplist="/etc/apt/sources.list.bkp" mv /etc/apt/sources.list "${bkplist}" newlist="/etc/apt/sources.list" rm -f "${newlist}" main_list="amd64,${{ matrix.arch }}" port_list="" if [[ "${{ matrix.arch }}" == "i386" ]]; then main_list="amd64,i386" else port_list="${{ matrix.arch }}" fi grep -v -E '^#' "${bkplist}" | sed -E "s;^deb (http[^ ]+) (.*)\$;deb [arch=${main_list}] \\1 \\2\ndeb-src [arch=${main_list}] \\1 \\2;" \ | tee -a "${newlist}" - name: Install build deps shell: bash run: | set -x rm -f /var/lib/man-db/auto-update apt update pkgs=( # Build dependencies cmake doxygen git graphviz ninja-build pkg-config qemu-user-static xdg-utils xvfb # Toolchain for cross-compiling. clang-11 g++-aarch64-linux-gnu libc6-dev-${{ matrix.arch }}-cross libstdc++-10-dev-${{ matrix.arch }}-cross libstdc++-10-dev:${{ matrix.arch }} # Dependencies libbrotli-dev:${{ matrix.arch }} libgif-dev:${{ matrix.arch }} libjpeg-dev:${{ matrix.arch }} libpng-dev:${{ matrix.arch }} libwebp-dev:${{ matrix.arch }} # For OpenEXR: libilmbase-dev:${{ matrix.arch }} libopenexr-dev:${{ matrix.arch }} # GTK plugins libgdk-pixbuf2.0-dev:${{ matrix.arch }} libgtk2.0-dev:${{ matrix.arch }} ) if [[ "${{ matrix.build_target }}" != "x86_64-linux-gnu" ]]; then pkgs+=( binutils-${{ matrix.build_target }} gcc-${{ matrix.build_target }} ) fi if [[ "${{ matrix.arch }}" != "i386" ]]; then pkgs+=( # TCMalloc libgoogle-perftools-dev:${{ matrix.arch }} libgoogle-perftools4:${{ matrix.arch }} libtcmalloc-minimal4:${{ matrix.arch }} libunwind-dev:${{ matrix.arch }} ) fi DEBIAN_FRONTEND=noninteractive apt install -y "${pkgs[@]}" echo "CC=${{ matrix.c_compiler || 'clang-11' }}" >> $GITHUB_ENV echo "CXX=${{ matrix.cxx_compiler || 'clang++-11' }}" >> $GITHUB_ENV - name: Checkout the source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: true fetch-depth: 1 - name: Configure run: | CMAKE_FLAGS="${{ matrix.cmake_flags }}" ./ci.sh release \ -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DJPEGXL_ENABLE_JNI=OFF \ ${{ join(matrix.cmake_args, ' ') }} env: SKIP_BUILD: 1 BUILD_TARGET: ${{ matrix.build_target }} - name: Setup tmate session uses: mxschmitt/action-tmate@e5c7151931ca95bad1c6f4190c730ecf8c7dde48 # v3.19 timeout-minutes: 15 libjxl-0.11.1/.github/workflows/dependency-review.yml000066400000000000000000000025351472134335300226250ustar00rootroot00000000000000# 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. # Dependency Review Action # # This Action will scan dependency manifest files that change as part of a Pull Request, # surfacing known-vulnerable versions of the packages declared or updated in the PR. # Once installed, if the workflow run is marked as required, # PRs introducing known-vulnerable packages will be blocked from merging. # # Source repository: https://github.com/actions/dependency-review-action name: 'Dependency Review' on: [pull_request] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: dependency-review: if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: 'Checkout Repository' uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: 'Dependency Review' uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0 libjxl-0.11.1/.github/workflows/fuzz.yml000066400000000000000000000041341472134335300202030ustar00rootroot00000000000000# 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. # CI on pull-requests to run the fuzzer from oss-fuzz. See: # # https://google.github.io/oss-fuzz/getting-started/continuous-integration/ name: CIFuzz on: merge_group: pull_request: types: [opened, reopened, labeled, unlabeled, synchronize] paths: - '**.c' - '**.cc' - '**.cmake' - '**.h' - '**CMakeLists.txt' - .github/workflows/fuzz.yml permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: fuzzing: if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Checkout source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 id: checkout with: # The build_fuzzers action checks out the code to the storage/libjxl # directory already, but doesn't check out the submodules. This step # is a workaround for checking out the submodules. path: storage/libjxl submodules: true - name: Build Fuzzers id: build uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@71ecd5d4e4bf9a6edc19c9fa6d2422fb528bca4f # master with: oss-fuzz-project-name: 'libjxl' language: c++ - name: Run Fuzzers uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@71ecd5d4e4bf9a6edc19c9fa6d2422fb528bca4f # master with: oss-fuzz-project-name: 'libjxl' language: c++ fuzz-seconds: 600 - name: Upload Crash uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts libjxl-0.11.1/.github/workflows/gitlab_mirror.yml000066400000000000000000000025521472134335300220430ustar00rootroot00000000000000# 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. # Workflow for building and running tests. name: Mirror to GitLab on: push: branches: - main - v*.*.x env: BRANCH_NAME: ${{ github.head_ref || github.ref_name }} permissions: contents: read jobs: mirror: permissions: contents: write # for Git to git push if: github.repository_owner == 'libjxl' runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Checkout source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 # Disable shallow clone - name: Set up SSH run: | mkdir -p ~/.ssh/ chmod 700 ~/.ssh/ echo "${{ secrets.GITLAB_DEPLOY_KEY }}" > ~/.ssh/id_ed25519 chmod 400 ~/.ssh/id_ed25519 ssh-keyscan gitlab.com >> ~/.ssh/known_hosts - name: Push to GitLab env: GIT_SSH_COMMAND: ssh -v -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes -o StrictHostKeyChecking=no run: | git remote add gitlab git@gitlab.com:wg1/jpeg-xl.git git push gitlab $BRANCH_NAME:$BRANCH_NAME libjxl-0.11.1/.github/workflows/highway.patch000066400000000000000000000004411472134335300211400ustar00rootroot00000000000000diff --git a/BUILD b/BUILD index 438b671..d2777b2 100644 --- a/BUILD +++ b/BUILD @@ -153,6 +153,7 @@ cc_library( "hwy/detect_compiler_arch.h", # private "hwy/print.h", ], + includes = ["."], compatible_with = [], copts = COPTS, defines = DEFINES, libjxl-0.11.1/.github/workflows/pages.yml000066400000000000000000000040611472134335300203030ustar00rootroot00000000000000# 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. # Simple workflow for deploying static content to GitHub Pages name: Deploy static content to Pages on: # Runs on pushes targeting the default branch push: branches: ["main"] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: group: "pages" cancel-in-progress: false jobs: # Single deploy job since we're just deploying deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: true fetch-depth: 2 - name: Install build deps run: | sudo rm -f /var/lib/man-db/auto-update sudo apt update sudo apt install -y \ cmake \ doxygen \ graphviz - name: Sphinx dependencies run: | pip3 install -r doc/sphinx/requirements.txt - name: Build run: | cmake -B build . \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DBUILD_TESTING=OFF cmake --build build -- rtd-html - name: Setup Pages uses: actions/configure-pages@v5 - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: # Upload entire repository path: './build/rtd' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 libjxl-0.11.1/.github/workflows/pull_request.yml000066400000000000000000000046471472134335300217420ustar00rootroot00000000000000# 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. # Workflow to run pull-requests specific checks. name: PR on: merge_group: pull_request: types: [opened, reopened, labeled, unlabeled, synchronize] permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: # Checks that the AUTHORS files is updated with new contributors. authors: if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: [ubuntu-latest] steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Check AUTHORS file # This is an optional check continue-on-error: True run: ./ci.sh authors format: if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: [ubuntu-latest] steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Install build deps run: | sudo rm -f /var/lib/man-db/auto-update sudo apt update sudo apt install -y \ clang-format \ clang-format-14 \ clang-format-15 \ # - name: Checkout the source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install tools run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" brew install buildifier typos-cli - name: lint run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" ./ci.sh lint >&2 bash_test: if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: [ubuntu-latest] steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: bash_test run: | ./bash_test.sh libjxl-0.11.1/.github/workflows/release.yaml000066400000000000000000000303301472134335300207630ustar00rootroot00000000000000# 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. # Workflow for building the release binaries. # # This workflow runs as a post-submit step, when pushing to main or the release # branches (v*.*.x), and when creating a release in GitHub. # # In the GitHub release case, in addition to build the release binaries it also # uploads the binaries to the given release automatically. name: Release build / deploy on: merge_group: push: branches: - main - v*.*.x pull_request: types: [opened, reopened, labeled, unlabeled, synchronize] paths-ignore: - '**.md' - 'AUTHORS' release: types: [ published ] workflow_dispatch: permissions: contents: write concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: ubuntu_static_x86_64: name: Release linux x86_64 static if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: [ubuntu-latest] steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Install build deps run: | sudo rm -f /var/lib/man-db/auto-update sudo apt update sudo apt install -y \ asciidoc \ clang \ cmake \ doxygen \ graphviz \ libbrotli-dev \ libgdk-pixbuf2.0-dev \ libgif-dev \ libgtest-dev \ libgtk2.0-dev \ libjpeg-dev \ libopenexr-dev \ libpng-dev \ libwebp-dev \ ninja-build \ pkg-config \ # echo "CC=clang" >> $GITHUB_ENV echo "CXX=clang++" >> $GITHUB_ENV - name: Checkout the source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: true fetch-depth: 1 - name: Build env: SKIP_TEST: 1 run: | ./ci.sh release \ -DJPEGXL_DEP_LICENSE_DIR=/usr/share/doc \ -DJPEGXL_STATIC=ON \ -DBUILD_TESTING=OFF \ -DJPEGXL_ENABLE_JPEGLI_LIBJPEG=OFF \ -DJPEGXL_ENABLE_VIEWERS=OFF \ -DJPEGXL_ENABLE_PLUGINS=OFF \ -DJPEGXL_ENABLE_OPENEXR=OFF \ -DJPEGXL_ENABLE_DEVTOOLS=ON \ - name: Package release tarball run: | cd build tar -zcvf ${{ runner.workspace }}/release_file.tar.gz \ LICENSE* tools/{cjxl,djxl,benchmark_xl,cjpegli,djpegli,jxlinfo,butteraugli_main,ssimulacra2} ln -s ${{ runner.workspace }}/release_file.tar.gz \ ${{ runner.workspace }}/jxl-linux-x86_64-static-${{ github.event.release.tag_name }}.tar.gz - name: Upload artifacts uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: jxl-linux-x86_64-static path: ${{ runner.workspace }}/release_file.tar.gz - name: Upload binaries to release if: github.event_name == 'release' uses: AButler/upload-release-assets@3d6774fae0ed91407dc5ae29d576b166536d1777 # v3.0 with: files: ${{ runner.workspace }}/jxl-linux-x86_64-static-${{ github.event.release.tag_name }}.tar.gz repo-token: ${{ secrets.GITHUB_TOKEN }} # Build .deb packages Ubuntu/Debian release_ubuntu_pkg: name: .deb packages / ${{ matrix.os }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: os: - ubuntu:24.04 - ubuntu:22.04 - ubuntu:20.04 - debian:bullseye - debian:bookworm # GIMP package is removed: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1078402 # - debian:trixie - debian:sid container: image: ${{ matrix.os }} steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Set env shell: 'bash' id: 'env' run: | artifact_name="jxl-debs-amd64-${matrix_os/:/-}" echo ${artifact_name} echo "artifact_name=${artifact_name}" >> $GITHUB_OUTPUT env: matrix_os: ${{ matrix.os }} - name: Install build deps run: | rm -f /var/lib/man-db/auto-update apt update DEBIAN_FRONTEND=noninteractive apt install -y \ build-essential \ devscripts \ # - name: Set git safe dir run: | export GIT_CEILING_DIRECTORIES=/__w # only work before git v2.35.2 git config --global --add safe.directory /__w/libjxl/libjxl - name: Checkout the source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: true fetch-depth: 1 - name: Stamp non-release versions # Stamps the built package with the commit date as part of the version # after the version number so newer release candidates can override older # ones. if: github.event_name != 'release' shell: 'bash' run: | # Committer timestamp. set -x commit_timestamp=$(git show -s --format=%ct) commit_datetime=$(date --utc "--date=@${commit_timestamp}" '+%Y%m%d%H%M%S') commit_ref=$(git rev-parse --short HEAD) sem_version=$(dpkg-parsechangelog --show-field Version) sem_version="${sem_version%%-*}" deb_version="${sem_version}~alpha${commit_datetime}-0+git${commit_ref}" dch -M --distribution unstable -b --newversion "${deb_version}" \ "Stamping build with version ${deb_version}" - name: Stamp release versions # Mark the version as released if: github.event_name == 'release' shell: 'bash' run: | if head -n1 debian/changelog | grep UNRELEASED; then dch -M --distribution unstable --release '' fi - name: Configure hwy if: ${{ github.event_name == 'schedule' || github.event_name == 'release' || contains(github.event.pull_request.labels.*.name, 'CI:full') }} run: | echo "HWY_PKG_OPTIONS=" >> $GITHUB_ENV - name: Build hwy run: | apt build-dep -y ./third_party/highway ./ci.sh debian_build highway dpkg -i build/debs/libhwy-dev_*_amd64.deb - name: Build libjxl run: | apt build-dep -y . ./ci.sh debian_build jpeg-xl - name: Stats run: | ./ci.sh debian_stats - name: Upload artifacts uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: ${{ steps.env.outputs.artifact_name }} path: | build/debs/*jxl*.* - name: Package release tarball if: github.event_name == 'release' run: | (cd build/debs/; find -maxdepth 1 -name '*jxl*.*') | \ tar -zcvf release_file.tar.gz -C build/debs/ -T - ln -s release_file.tar.gz \ ${{ steps.env.outputs.artifact_name }}-${{ github.event.release.tag_name }}.tar.gz - name: Upload binaries to release if: github.event_name == 'release' uses: AButler/upload-release-assets@3d6774fae0ed91407dc5ae29d576b166536d1777 # v3.0 with: files: ${{ steps.env.outputs.artifact_name }}-${{ github.event.release.tag_name }}.tar.gz repo-token: ${{ secrets.GITHUB_TOKEN }} windows_build: name: Windows Build (vcpkg / ${{ matrix.triplet }}) if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: [windows-2022] strategy: fail-fast: false matrix: include: - triplet: x86-windows-static arch: '-A Win32' - triplet: x64-windows-static arch: '-A x64' run_benchmark: true - triplet: x86-windows arch: '-A Win32' static: 'OFF' tools: 'OFF' - triplet: x64-windows arch: '-A x64' static: 'OFF' tools: 'OFF' env: VCPKG_VERSION: '2023.12.12' VCPKG_ROOT: vcpkg VCPKG_DISABLE_METRICS: 1 steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: true fetch-depth: 2 - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 id: cache-vcpkg with: path: vcpkg key: release-${{ runner.os }}-vcpkg-${{ env.VCPKG_VERSION }}-${{ matrix.triplet }} - name: Download vcpkg if: steps.cache-vcpkg.outputs.cache-hit != 'true' # wget doesn't seem to work under bash. shell: 'powershell' run: | C:\msys64\usr\bin\wget.exe -nv ` https://github.com/microsoft/vcpkg/archive/refs/tags/${{ env.VCPKG_VERSION }}.zip ` -O vcpkg.zip - name: Bootstrap vcpkg if: steps.cache-vcpkg.outputs.cache-hit != 'true' shell: 'bash' run: | set -x unzip -q vcpkg.zip rm -rf ${VCPKG_ROOT} mv vcpkg-${VCPKG_VERSION} ${VCPKG_ROOT} ${VCPKG_ROOT}/bootstrap-vcpkg.sh - name: Install libraries with vcpkg shell: 'bash' run: | set -x ${VCPKG_ROOT}/vcpkg --triplet ${{ matrix.triplet }} install \ giflib \ libjpeg-turbo \ libpng \ libwebp \ pkgconf \ # - name: Configure shell: 'bash' run: | set -x mkdir build cmake -Bbuild -H. ${{ matrix.arch }} \ -DJPEGXL_STATIC=${{ matrix.static || 'ON'}} \ -DJPEGXL_ENABLE_TOOLS=${{ matrix.tools || 'ON'}} \ -DBUILD_TESTING=OFF \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=`pwd`/prefix \ -DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake \ -DJPEGXL_ENABLE_JPEGLI_LIBJPEG=OFF \ -DJPEGXL_ENABLE_OPENEXR=OFF \ -DJPEGXL_ENABLE_PLUGINS=OFF \ -DJPEGXL_ENABLE_TCMALLOC=OFF \ -DJPEGXL_ENABLE_VIEWERS=OFF \ -DJPEGXL_ENABLE_DEVTOOLS=${{ matrix.tools || 'ON'}} \ -DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} \ # - name: Build shell: 'bash' run: | set -x cmake --build build --config Release - name: Install shell: 'bash' run: | set -x cmake --build build --config Release --target install for pkg in giflib libjpeg-turbo libpng libwebp zlib; do cp vcpkg/installed/${{matrix.triplet}}/share/${pkg}/copyright \ prefix/bin/LICENSE.${pkg} done cp third_party/sjpeg/COPYING prefix/bin/LICENSE.sjpeg cp third_party/skcms/LICENSE prefix/bin/LICENSE.skcms cp third_party/highway/LICENSE prefix/bin/LICENSE.highway cp third_party/brotli/LICENSE prefix/bin/LICENSE.brotli cp LICENSE prefix/bin/LICENSE.libjxl - name: Fast benchmark ${{ matrix.triplet }} shell: 'bash' if: matrix.run_benchmark && true run: | systeminfo | grep -A 1 Processor wmic cpu get Name, NumberofCores, NumberOfLogicalProcessors, MaxClockSpeed mkdir tmp export TMPDIR=`pwd`/tmp export PATH=$PATH:`pwd`/build/lib BUILD_CONFIG=Release/ BENCHMARK_NUM_THREADS=2 STORE_IMAGES=0 ./ci.sh fast_benchmark - name: Upload artifacts uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: jxl-${{matrix.triplet}} path: | prefix/bin/* prefix/lib/* - name: Package release zip if: github.event_name == 'release' shell: 'powershell' run: | Compress-Archive -Path prefix\bin\*, prefix\lib\* ` -DestinationPath jxl-${{matrix.triplet}}.zip - name: Upload binaries to release if: github.event_name == 'release' uses: AButler/upload-release-assets@3d6774fae0ed91407dc5ae29d576b166536d1777 # v3.0 with: files: jxl-${{matrix.triplet}}.zip repo-token: ${{ secrets.GITHUB_TOKEN }} libjxl-0.11.1/.github/workflows/scorecard.yml000066400000000000000000000056071472134335300211600ustar00rootroot00000000000000# 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 workflow uses actions that are not certified by GitHub. They are provided # by a third-party and are governed by separate terms of service, privacy # policy, and support documentation. name: Scorecard supply-chain security on: # For Branch-Protection check. Only the default branch is supported. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection branch_protection_rule: # To guarantee Maintained check is occasionally updated. See # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained schedule: - cron: '13 2 * * 3' push: branches: [ "main" ] # Declare default permissions as read only. permissions: read-all jobs: analysis: name: Scorecard analysis runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. security-events: write # Needed to publish results and get a badge (see publish_results below). id-token: write steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: "Checkout code" uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: # you want to enable the Branch-Protection check on a *public* repository # To create the PAT, follow the steps in # https://github.com/ossf/scorecard-action#authentication-with-fine-grained-pat-optional repo_token: ${{ secrets.SCORECARD_TOKEN }} # - Publish results to OpenSSF REST API for easy access by consumers # - Allows the repository to include the Scorecard badge. # - See https://github.com/ossf/scorecard-action#publishing-results. publish_results: true # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 with: name: SARIF file path: results.sarif retention-days: 5 # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" uses: github/codeql-action/upload-sarif@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5 with: sarif_file: results.sarif libjxl-0.11.1/.github/workflows/test_new_highway.yml000066400000000000000000000042401472134335300225530ustar00rootroot00000000000000# 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. # Workflow for building and running tests. name: Update branches with updated submodule on libjxl on: workflow_dispatch: schedule: - cron: '37 2 * * *' # Daily on 02:37 UTC permissions: contents: read jobs: update: if: github.repository_owner == 'libjxl' runs-on: ubuntu-latest strategy: matrix: include: - branch: 'test_highway' tag: 'origin/master' - branch: 'test_highway_1.0.7' tag: '1.0.7' - branch: 'test_highway_1.1.0' tag: '1.1.0' - branch: 'test_highway_1.2.0' tag: '1.2.0' steps: - name: Harden Runner uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2 with: egress-policy: audit - name: 'Cloning libjxl' uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: recursive persist-credentials: false # otherwise, the wrong authentication is used in the push fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. - name: Pull & update highway working-directory: ./third_party/highway run: | git fetch origin git checkout ${{ matrix.tag }} - name: Update deps.sh run: | NEWHASH=`git submodule status third_party/highway | cut -d' ' -f1 | cut -c2-` sed -i "s/\(THIRD_PARTY_HIGHWAY=\"\)[^\"]*/\1$NEWHASH/" deps.sh - name: Commit run: | git config user.email "firsching@google.com" git config user.name "GitHub Actions - update submodules" git add --all git commit -m "Update highway submodule" || echo "No changes to commit" - name: Push changes uses: ad-m/github-push-action@d91a481090679876dfc4178fef17f286781251df # v0.8.0 with: github_token: ${{ secrets.TOKEN }} branch: 'refs/heads/${{ matrix.branch }}' force: true libjxl-0.11.1/.gitignore000066400000000000000000000005441472134335300150560ustar00rootroot00000000000000# Build output directories /build /build* /bazel* # clangd .cache # The downloaded corpora files for benchmark. /third_party/corpora # hdrvdp source code third_party/hdrvdp-2.2.2 third_party/hdrvdp-2.2.2.zip third_party/hdrvdp-2.2.2.zip.tmp # Output plots tools/benchmark/metrics/plots tools/benchmark/metrics/results.csv tools/conformance/__pycache__ libjxl-0.11.1/.gitmodules000066400000000000000000000020301472134335300152330ustar00rootroot00000000000000[submodule "third_party/brotli"] path = third_party/brotli url = https://github.com/google/brotli [submodule "third_party/lcms"] path = third_party/lcms url = https://github.com/mm2/Little-CMS [submodule "third_party/googletest"] path = third_party/googletest url = https://github.com/google/googletest [submodule "third_party/sjpeg"] path = third_party/sjpeg url = https://github.com/webmproject/sjpeg.git [submodule "third_party/skcms"] path = third_party/skcms url = https://skia.googlesource.com/skcms [submodule "third_party/highway"] path = third_party/highway url = https://github.com/google/highway [submodule "third_party/libpng"] path = third_party/libpng url = https://github.com/glennrp/libpng.git [submodule "third_party/zlib"] path = third_party/zlib url = https://github.com/madler/zlib.git [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 libjxl-0.11.1/.pre-commit-config.yaml000066400000000000000000000012511472134335300173430ustar00rootroot00000000000000repos: - 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 libjxl-0.11.1/.readthedocs.yaml000066400000000000000000000007741472134335300163220ustar00rootroot00000000000000# 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. # # readthedocs.io configuration file. See: # https://docs.readthedocs.io/en/stable/config-file/v2.html version: 2 sphinx: configuration: doc/sphinx/conf.py build: os: ubuntu-22.04 tools: python: "3" apt_packages: - doxygen - graphviz python: install: - requirements: doc/sphinx/requirements.txt libjxl-0.11.1/AUTHORS000066400000000000000000000064651472134335300141460ustar00rootroot00000000000000# 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. # # # Please keep each list sorted. If you wish to change your email address please # send a pull request. # Organizations: # - 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 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 Marcin Konicki 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> libjxl-0.11.1/BUILD.bazel000066400000000000000000000013421472134335300147410ustar00rootroot00000000000000# 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. package(default_visibility = ["//:__subpackages__"]) filegroup( name = "testdata", srcs = glob([ "testdata/**/*.icc", "testdata/**/*.pam", "testdata/**/*.pfm", "testdata/**/*.pgm", "testdata/**/*.pnm", "testdata/**/*.ppm", "testdata/**/*.png", "testdata/**/*.jpg", "testdata/**/*.jxl", "testdata/**/*.gif", "testdata/**/*.y4m", "testdata/**/*.jxl", "testdata/**/*.png", "testdata/**/*.jpg", "testdata/position_encoding/*.txt", ]), ) libjxl-0.11.1/BUILDING.md000066400000000000000000000046721472134335300146130ustar00rootroot00000000000000# Compilation For more details and other workflows see the "Advanced guide" below. ## Checking out the code ```bash git clone https://github.com/libjxl/libjxl.git --recursive --shallow-submodules ``` This repository uses git submodules to handle some third party dependencies under `third_party`, that's why it is important to pass `--recursive`. If you didn't check out with `--recursive`, or any submodule has changed, run: ```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 . ``` ## 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) libjxl-0.11.1/BUILDING_Haiku.md000066400000000000000000000014211472134335300157210ustar00rootroot00000000000000## 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 "[BUILDING](BUILDING.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 ``` libjxl-0.11.1/BUILDING_OSX.md000066400000000000000000000022441472134335300153350ustar00rootroot00000000000000## 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 "[BUILDING](BUILDING.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` ```libjxl-0.11.1/CHANGELOG.md000066400000000000000000000432431472134335300147020ustar00rootroot00000000000000# Changelog 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). ## [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 ### Added - Export version information in headers. - decoder API: Ability to decode the content of metadata boxes: `JXL_DEC_BOX`, `JXL_DEC_BOX_NEED_MORE_OUTPUT`, `JxlDecoderSetBoxBuffer`, `JxlDecoderGetBoxType`, `JxlDecoderGetBoxSizeRaw` and `JxlDecoderSetDecompressBoxes`. - decoder API: ability to mark the input is finished: `JxlDecoderCloseInput`. - decoder API: ability to request updates on different progressive events using `JxlDecoderSetProgressiveDetail`; currently supported events are `kDC`, `kLastPasses` and `kPasses`. - decoder API: ability to specify desired intensity target using `JxlDecoderSetDesiredIntensityTarget` - decoder API: new function `JxlDecoderSetCoalesced` to allow decoding non-coalesced (unblended) frames, e.g. layers of a composite still image or the cropped frames of a recompressed GIF/APNG. - decoder API: new function `JxlDecoderSetUnpremultiplyAlpha` to set preference for getting an associated alpha channel with premultiplied or unpremultiplied colors. - decoder API: field added to `JxlFrameHeader`: a `JxlLayerInfo` struct that contains crop dimensions and offsets and blending information for the non-coalesced case. - decoder API: new function `JxlDecoderGetExtraChannelBlendInfo` to get the blending information for extra channels in the non-coalesced case. - decoder API: new function `JxlDecoderSetMultithreadedImageOutCallback`, allowing output callbacks to receive more information about the number of threads on which they are running. - decoder API: new function `JxlDecoderSkipCurrentFrame` to skip processing the current frame after a progressive detail is reached. - decoder API: new function `JxlDecoderGetIntendedDownsamplingRatio` to get the intended downsampling ratio of progressive steps, based on the information in the frame header. - decoder API: new function `JxlDecoderSetRenderSpotcolors` to allow disabling rendering of spot colors. - decoder/encoder API: add two fields to `JXLBasicInfo`: `intrinsic_xsize` and `intrinsic_ysize` to signal the intrinsic size. - encoder API: ability to add metadata boxes, added new functions `JxlEncoderAddBox`, `JxlEncoderUseBoxes`, `JxlEncoderCloseBoxes` and `JxlEncoderCloseFrames`. - encoder API: added ability to set several encoder options / extra fields to frames using `JxlEncoderSetFrameName`, `JxlEncoderFrameSettingsSetOption`, `JxlEncoderFrameSettingsSetFloatOption`. - encoder API: added ability to check required codestream compatibility level and force specified using `JxlEncoderGetRequiredCodestreamLevel` and `JxlEncoderSetCodestreamLevel`. - encoder API: added ability to force emitting box-based container format using `JxlEncoderUseContainer`. - encoder API: added ability to store JPEG metadata for lossless reconstruction using `JxlEncoderStoreJPEGMetadata` - encoder API: new functions `JxlEncoderSetFrameHeader` and `JxlEncoderSetExtraChannelBlendInfo` to set animation and blending parameters of the frame, and `JxlEncoderInitFrameHeader` and `JxlEncoderInitBlendInfo` to initialize the structs to set. - encoder API: ability to encode arbitrary extra channels: `JxlEncoderInitExtraChannelInfo`, `JxlEncoderSetExtraChannelInfo`, `JxlEncoderSetExtraChannelName` and `JxlEncoderSetExtraChannelBuffer`. - encoder API: ability to plug custom CMS implementation using `JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms)` - encoder API: added `JxlEncoderGetError` to retrieve last encoder error. ### Changed - decoder API: using `JxlDecoderCloseInput` at the end of all input is required when using JXL_DEC_BOX, and is now also encouraged in other cases, but not required in those other cases for backwards compatibility. - encoder API: `JxlEncoderCloseInput` now closes both frames and boxes input. - CLI: `cjxl` and `djxl` have been reimplemented on the base of public decoder and encoder API; dropped dependency on `gflags` for argument parsing. ### Deprecated - decoder API: `JXL_DEC_EXTENSIONS` event: use `JXL_DEC_BASIC_INFO` - decoder / encoder API: pixel types `JXL_TYPE_BOOLEAN` and `JXL_TYPE_UINT32`: consider using `JXL_TYPE_UINT8` and `JXL_TYPE_FLOAT` correspondingly. - decoder API: pixel format parameter for `JxlDecoderGetColorAsEncodedProfile` and `JxlDecoderGetICCProfileSize`: pass `NULL`. - decoder API: `JxlDecoderDefaultPixelFormat` - encoder API: `JxlEncoderOptions`: use `JxlEncoderFrameSettings` instead. - encoder API: `JxlEncoderOptionsCreate`: use `JxlEncoderFrameSettingsCreate` instead. - encoder API: `JxlEncoderOptionsSetDistance`: use `JxlEncoderSetFrameDistance` instead. - encoder API: `JxlEncoderOptionsSetLossless`: use `JxlEncoderSetFrameLossless` instead. - encoder API: `JxlEncoderOptionsSetEffort`: use `JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, effort)` instead. - encoder API: `JxlEncoderOptionsSetDecodingSpeed`: use `JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, tier)` instead. - encoder API: deprecated `JXL_ENC_NOT_SUPPORTED`, the encoder returns `JXL_ENC_ERROR` instead and there is no need to handle `JXL_ENC_NOT_SUPPORTED`. ## [0.6.1] - 2021-10-29 ### Changed - Security: Fix OOB read in splines rendering (#735 - [CVE-2021-22563](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22563)) - Security: Fix OOB copy (read/write) in out-of-order/multi-threaded decoding (#708 - [CVE-2021-22564](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22564)) - Fix segfault in `djxl` tool with `--allow_partial_files` flag (#781). - Fix border in extra channels when using upsampling (#796) ## [0.6] - 2021-10-04 ### Added - API: New functions to decode extra channels: `JxlDecoderExtraChannelBufferSize` and `JxlDecoderSetExtraChannelBuffer`. - API: New function `JxlEncoderInitBasicInfo` to initialize `JxlBasicInfo` (only needed when encoding). NOTE: it is now required to call this function when using the encoder. Padding was added to the struct for forward compatibility. - API: Support for encoding oriented images. - API: FLOAT16 support in the encoder API. - Rewrite of the GDK pixbuf loader plugin. Added proper color management and animation support. - Rewrite of GIMP plugin. Added compression parameters dialog and switched to using the public C API. - Debian packages for GDK pixbuf loader (`libjxl-gdk-pixbuf`) and GIMP (`libjxl-gimp-plugin`) plugins. - `cjxl`/`djxl` support for `stdin` and `stdout`. ### Changed - API: Renamed the field `alpha_associated` in `JxlExtraChannelInfo` to `alpha_premultiplied`, to match the corresponding name in `JxlBasicInfo`. - Improved the 2x2 downscaling method in the encoder for the optional color channel resampling for low bit rates. - Fixed: the combination of floating point original data, XYB color encoding, and Modular mode was broken (in both encoder and decoder). It now works. NOTE: this can cause the current encoder to write jxl bitstreams that do not decode with the old decoder. In particular this will happen when using cjxl with PFM, EXR, or floating point PSD input, and a combination of XYB and modular mode is used (which caused an encoder error before), e.g. using options like `-m -q 80` (lossy modular), `-d 4.5` or `--progressive_dc=1` (modular DC frame), or default lossy encoding on an image where patches end up being used. There is no problem when using cjxl with PNG, JPEG, GIF, APNG, PPM, PGM, PGX, or integer (8-bit or 16-bit) PSD input. - `libjxl` static library now bundles skcms, fixing static linking in downstream projects when skcms is used. - Spline rendering performance improvements. - Butteraugli changes for less visual masking. ## [0.5] - 2021-08-02 ### Added - API: New function to decode the image using a callback outputting a part of a row per call. - API: 16-bit float output support. - API: `JxlDecoderRewind` and `JxlDecoderSkipFrames` functions to skip more efficiently to earlier animation frames. - API: `JxlDecoderSetPreferredColorProfile` function to choose color profile in certain circumstances. - encoder: Adding `center_x` and `center_y` flags for more control of the tile order. - New encoder speeds `lightning` (1) and `thunder` (2). ### Changed - Re-licensed the project under a BSD 3-Clause license. See the [LICENSE](LICENSE) and [PATENTS](PATENTS) files for details. - Full JPEG XL part 1 specification support: Implemented all the spec required to decode files to pixels, including cases that are not used by the encoder yet. Part 2 of the spec (container format) is final but not fully implemented here. - Butteraugli metric improvements. Exact numbers are different from previous versions. - Memory reductions during decoding. - Reduce the size of the jxl_dec library by removing dependencies. - A few encoding speedups. - Clarify the security policy. - Significant encoding improvements (~5 %) and less ringing. - Butteraugli metric to have some less masking. - `cjxl` flag `--speed` is deprecated and replaced by the `--effort` synonym. ### Removed - API for returning a downsampled DC was deprecated (`JxlDecoderDCOutBufferSize` and `JxlDecoderSetDCOutBuffer`) and will be removed in the next release. ## [0.3.7] - 2021-03-29 ### Changed - Fix a rounding issue in 8-bit decoding. ## [0.3.6] - 2021-03-25 ### Changed - Fix a bug that could result in the generation of invalid codestreams as well as failure to decode valid streams. ## [0.3.5] - 2021-03-23 ### Added - New encode-time options for faster decoding at the cost of quality. - Man pages for cjxl and djxl. ### Changed - Memory usage improvements. - Faster decoding to 8-bit output with the C API. - GIMP plugin: avoid the sRGB conversion dialog for sRGB images, do not show a console window on Windows. - Various bug fixes. ## [0.3.4] - 2021-03-16 ### Changed - Improved box parsing. - Improved metadata handling. - Performance and memory usage improvements. ## [0.3.3] - 2021-03-05 ### Changed - Performance improvements for small images. - Add a (flag-protected) non-high-precision mode with better speed. - Significantly speed up the PQ EOTF. - Allow optional HDR tone mapping in djxl (--tone_map, --display_nits). - Change the behavior of djxl -j to make it consistent with cjxl (#153). - Improve image quality. - Improve EXIF handling. ## [0.3.2] - 2021-02-12 ### Changed - Fix embedded ICC encoding regression [#149](https://gitlab.com/wg1/jpeg-xl/-/issues/149). ## [0.3.1] - 2021-02-10 ### Changed - New experimental Butteraugli API (`jxl/butteraugli.h`). - Encoder improvements to low quality settings. - Bug fixes, including fuzzer-found potential security bug fixes. - Fixed `-q 100` and `-d 0` not triggering lossless modes. ## [0.3] - 2021-01-29 ### Changed - Minor change to the Decoder C API to accommodate future work for other ways to provide input. - Future decoder C API changes will be backwards compatible. - Lots of bug fixes since the previous version. ## [0.2] - 2020-12-24 ### Added - JPEG XL bitstream format is frozen. Files encoded with 0.2 will be supported by future versions. ### Changed - Files encoded with previous versions are not supported. ## [0.1.1] - 2020-12-01 ## [0.1] - 2020-11-14 ### Added - Initial release of an encoder (`cjxl`) and decoder (`djxl`) that work together as well as a benchmark tool for comparison with other codecs (`benchmark_xl`). - Note: JPEG XL format is in the final stages of standardization, minor changes to the codestream format are still possible but we are not expecting any changes beyond what is required by bug fixing. - API: new decoder API in C, check the `examples/` directory for its example usage. The C API is a work in progress and likely to change both in API and ABI in future releases. libjxl-0.11.1/CMakeLists.txt000066400000000000000000000510361472134335300156300ustar00rootroot00000000000000# 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. # 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") project(LIBJXL LANGUAGES C CXX) # 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("-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) check_pie_supported(LANGUAGES CXX) if(CMAKE_CXX_LINK_PIE_SUPPORTED) set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) 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. set(ENABLE_FUZZERS_DEFAULT true) endif() find_package(PkgConfig) if(NOT APPLE AND NOT WIN32 AND NOT HAIKU AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64") pkg_check_modules(TCMallocMinimalVersionCheck QUIET IMPORTED_TARGET libtcmalloc_minimal) if(TCMallocMinimalVersionCheck_FOUND AND NOT TCMallocMinimalVersionCheck_VERSION VERSION_EQUAL 2.8.0) # Enabled by default except on Windows and Apple builds for # tcmalloc != 2.8.0. tcmalloc 2.8.1 already has a fix for this issue. set(ENABLE_TCMALLOC_DEFAULT true) else() message(STATUS "tcmalloc version ${TCMallocMinimalVersionCheck_VERSION} -- " "tcmalloc 2.8.0 disabled due to " "https://github.com/gperftools/gperftools/issues/1204") endif() endif() check_cxx_source_compiles( "int main() { #if !defined(HWY_DISABLED_TARGETS) static_assert(false, \"HWY_DISABLED_TARGETS is not defined\"); #endif return 0; }" JXL_HWY_DISABLED_TARGETS_FORCED ) 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}) set(JPEGXL_ENABLE_FUZZERS ${ENABLE_FUZZERS_DEFAULT} CACHE BOOL "Build JPEGXL fuzzer targets.") set(JPEGXL_ENABLE_DEVTOOLS false CACHE BOOL "Build JPEGXL developer tools.") set(JPEGXL_ENABLE_TOOLS true CACHE BOOL "Build JPEGXL user tools: cjxl and djxl.") set(JPEGXL_ENABLE_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 "Build and install man pages for the command-line tools.") set(JPEGXL_ENABLE_BENCHMARK true CACHE BOOL "Build JPEGXL benchmark tools.") set(JPEGXL_ENABLE_EXAMPLES true CACHE BOOL "Build JPEGXL library usage examples.") set(JPEGXL_BUNDLE_LIBPNG ${BUNDLE_LIBPNG_DEFAULT} CACHE BOOL "Build libpng from source and link it statically.") set(JPEGXL_ENABLE_JNI true CACHE BOOL "Build JPEGXL JNI Java wrapper, if Java dependencies are installed.") 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 ${ENABLE_SKCMS_DEFAULT} CACHE BOOL "Build with skcms instead of lcms2.") set(JPEGXL_ENABLE_VIEWERS false CACHE BOOL "Build JPEGXL viewer tools for evaluation.") set(JPEGXL_ENABLE_TCMALLOC ${ENABLE_TCMALLOC_DEFAULT} CACHE BOOL "Build JPEGXL using gperftools (tcmalloc) allocator.") set(JPEGXL_ENABLE_PLUGINS false CACHE BOOL "Build third-party 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_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 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) 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 "Force using system installed highway (libhwy-dev) instead of third_party/highway source.") # Check minimum compiler versions. Older compilers are not supported and fail # with hard to understand errors. 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() 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) string(APPEND CMAKE_EXE_LINKER_FLAGS " -static") endif() if ((NOT WIN32 AND NOT APPLE) OR CYGWIN OR MINGW) set(CMAKE_FIND_LIBRARY_SUFFIXES .a) string(APPEND CMAKE_EXE_LINKER_FLAGS " -static-libgcc -static-libstdc++") endif() endif() # JPEGXL_STATIC # Threads set(THREADS_PREFER_PTHREAD_FLAG YES) find_package(Threads REQUIRED) # These settings are important to drive check_cxx_source_compiles # See CMP0067 (min cmake version is 3.10 anyway) 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) # Atomics find_package(Atomics REQUIRED) if(JPEGXL_STATIC) if (MINGW) # In MINGW libstdc++ uses pthreads directly. When building statically a # program (regardless of whether the source code uses pthread or not) the # toolchain will add stdc++ and pthread to the linking step but stdc++ will # be linked statically while pthread will be linked dynamically. # To avoid this and have pthread statically linked with need to pass it in # the command line with "-Wl,-Bstatic -lpthread -Wl,-Bdynamic" but the # linker will discard it if not used by anything else up to that point in # the linker command line. If the program or any dependency don't use # pthread directly -lpthread is discarded and libstdc++ (added by the # toolchain later) will then use the dynamic version. For this we also need # to pass -lstdc++ explicitly before -lpthread. For pure C programs -lstdc++ # will be discarded anyway. # This adds these flags as dependencies for *all* targets. Adding this to # CMAKE_EXE_LINKER_FLAGS instead would cause them to be included before any # object files and therefore discarded. This should be set in the # INTERFACE_LINK_LIBRARIES of Threads::Threads but some third_part targets # don't depend on it. link_libraries(-Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic) elseif(CMAKE_USE_PTHREADS_INIT) # "whole-archive" is not supported on OSX. if (NOT APPLE) # Set pthreads as a whole-archive, otherwise weak symbols in the static # libraries will discard pthreads symbols leading to segmentation fault at # runtime. message(STATUS "Using -lpthread as --whole-archive") set_target_properties(Threads::Threads PROPERTIES INTERFACE_LINK_LIBRARIES "-Wl,--whole-archive;-lpthread;-Wl,--no-whole-archive") endif() endif() endif() # JPEGXL_STATIC 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") endif() if (CXX_MACRO_PREFIX_MAP) add_compile_options(-fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.) endif() 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 add_definitions(-D_CRT_SECURE_NO_WARNINGS) 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) 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() # 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() if(WIN32) # Not supported by clang-cl, but frame pointers are default on Windows else() add_compile_options(-fno-omit-frame-pointer) endif() # 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() add_compile_options( # Ignore this to allow redefining __DATE__ and others. -Wno-builtin-macro-redefined # 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) # Separately build/configure testing frameworks and other third_party libraries # to allow disabling tests in those libraries. include(third_party/testing.cmake) add_subdirectory(third_party) # Copy the JXL license file to the output build directory. configure_file("${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" ${PROJECT_BINARY_DIR}/LICENSE.jpeg-xl COPYONLY) # Enable tests regardless of where they are defined. enable_testing() include(CTest) # Specify default location of `testdata`: if(NOT DEFINED JPEGXL_TEST_DATA_PATH) set(JPEGXL_TEST_DATA_PATH "${PROJECT_SOURCE_DIR}/testdata") endif() # Libraries. 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() 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") 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 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$" 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() 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) endif () # Plugins for third-party software if (JPEGXL_ENABLE_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() libjxl-0.11.1/CODE_OF_CONDUCT.md000066400000000000000000000106111472134335300156610ustar00rootroot00000000000000# Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. This Code of Conduct also applies outside the project spaces when the Project Steward has a reasonable belief that an individual's behavior may have a negative impact on the project or its community. ## Conflict Resolution We do not believe that all conflict is bad; healthy debate and disagreement often yield positive results. However, it is never okay to be disrespectful or to engage in behavior that violates the project’s code of conduct. If you see someone violating the code of conduct, you are encouraged to address the behavior directly with those involved. Many issues can be resolved quickly and easily, and this gives people more control over the outcome of their dispute. If you are unable to resolve the matter for any reason, or if the behavior is threatening or harassing, report it. We are dedicated to providing an environment where participants feel welcome and safe. Reports should be directed to Jyrki Alakuijala , the Project Steward(s) for JPEG XL. It is the Project Steward’s duty to receive and address reported violations of the code of conduct. They will then work with a committee consisting of representatives from the Open Source Programs Office and the Google Open Source Strategy team. If for any reason you are uncomfortable reaching out to the Project Steward, please email opensource@google.com. We will investigate every complaint, but you may not receive a direct response. We will use our discretion in determining when and how to follow up on reported incidents, which may range from not taking action to permanent expulsion from the project and project-sponsored spaces. We will notify the accused of the report and provide them an opportunity to discuss it before any action is taken. The identity of the reporter will be omitted from the details of the report supplied to the accused. In potentially harmful situations, such as ongoing harassment or threats to anyone's safety, we may take action without notice. ## Attribution This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html libjxl-0.11.1/CONTRIBUTING.md000066400000000000000000000136461472134335300153260ustar00rootroot00000000000000# Contributing to libjxl ## Contributing with bug reports For security-related issues please see [SECURITY.md](SECURITY.md). We welcome suggestions, feature requests and bug reports. Before opening a new issue please take a look if there is already an existing one in the following link: * https://github.com/libjxl/libjxl/issues ## Contributing with patches and Pull Requests We'd love to accept your contributions to the JPEG XL Project. Please read through this section before sending a Pull Request. ### Contributor License Agreements Our project is open source under the terms outlined in the [LICENSE](LICENSE) and [PATENTS](PATENTS) files. Before we can accept your contributions, even for small changes, there are just a few small guidelines you need to follow: Please fill out either the individual or corporate Contributor License Agreement (CLA) with Google. JPEG XL Project is an an effort by multiple individuals and companies, including the initial contributors Cloudinary and Google, but Google is the legal entity in charge of receiving these CLA and relicensing this software: * If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA](https://code.google.com/legal/individual-cla-v1.0.html). * If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA](https://code.google.com/legal/corporate-cla-v1.0.html). Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests. ***NOTE***: Only original source code from you and other people that have signed the CLA can be accepted into the main repository. ### License Contributions are licensed under the project's [LICENSE](LICENSE). Each new file must include the following header when possible, with comment style adapted to the language as needed: ``` // 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. ``` ### Code Reviews All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. ### Contribution philosophy * Prefer small changes, even if they don't implement a complete feature. Small changes are easier to review and can be submitted faster. Think about what's the smallest unit you can send that makes sense to review and submit in isolation. For example, new modules that are not yet used by the tools but have their own unittests are ok. If you have unrelated changes that you discovered while working on something else, please send them in a different Pull Request. If your are refactoring code and changing functionality try to send the refactor first without any change in 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 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 looking at your commit trying to figure out why was the change introduced. If related to an issue in this or another repository include a link to it. * Code Style: We follow the [Google C++ Coding Style](https://google.github.io/styleguide/cppguide.html). A [clang-format](https://clang.llvm.org/docs/ClangFormat.html) configuration file is available to automatically format your code, you can invoke it with the `./ci.sh lint` helper tool. * Testing: Test your change and explain in the commit message *how* your commit was tested. For example adding unittests or in some cases just testing with the existing ones is enough. In any case, mention what testing was performed so reviewers can evaluate whether that's enough testing. In many cases, testing that the Continuous Integration workflow passes is enough. * Make one commit per Pull Request / review, unless there's a good reason not to. If you have multiple changes send multiple Pull Requests and each one can have its own review. * When addressing comments from reviewers prefer to squash or fixup your edits and force-push your commit. When merging changes into the repository we don't want to include the history of code review back and forth changes or typos. Reviewers can click on the "force-pushed" automatic comment on a Pull Request to see the changes between versions. We use "Rebase and merge" policy to keep a linear git history which is easier to reason about. * Your change must pass the build and test workflows. There's a `ci.sh` script to help building and testing these configurations. See [building and testing](doc/building_and_testing.md) for more details. ### Contributing checklist. * Sign the CLA (only needed once per user, see above). * AUTHORS: You can add your name to the [AUTHORS](AUTHORS) file. * Style guide. Check `./ci.sh lint`. * Meaningful commit description: What and *why*, links to issues, testing procedure. * Squashed multiple edits into a single commit. * Upload your changes to your fork and [create a Pull Request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request). # Community Guidelines This project follows [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/). libjxl-0.11.1/CONTRIBUTORS000066400000000000000000000012401472134335300147400ustar00rootroot00000000000000# This files lists individuals who made significant contributions to the JPEG XL # code base, such as design, adding features, performing experiments, ... # Small changes such as a small bugfix or fixing spelling errors are not # included. If you'd like to be included in this file thanks to a significant # contribution, feel free to send a pull request changing this file. Alex Deymo Alexander Rhatushnyak Evgenii Kliuchnikov Iulia-Maria Comșa Jan Wassenberg Jon Sneyers Jyrki Alakuijala Krzysztof Potempa Lode Vandevenne Luca Versari Martin Bruse Moritz Firsching Renata Khasanova Robert Obryk Sami Boukortt Sebastian Gomez-Gonzalez Thomas Fischbacher Zoltan Szabadka libjxl-0.11.1/LICENSE000066400000000000000000000027441472134335300140770ustar00rootroot00000000000000Copyright (c) the JPEG XL Project Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. libjxl-0.11.1/MODULE.bazel000066400000000000000000000010031472134335300150610ustar00rootroot00000000000000# 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") libjxl-0.11.1/MODULE.bazel.lock000066400000000000000000000303261472134335300160220ustar00rootroot00000000000000{ "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": [] } } } } libjxl-0.11.1/PATENTS000066400000000000000000000024651472134335300141330ustar00rootroot00000000000000Additional IP Rights Grant (Patents) "This implementation" means the copyrightable works distributed by Google as part of the JPEG XL project. Google hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, transfer and otherwise run, modify and propagate the contents of this implementation of JPEG XL, where such license applies only to those patent claims, both currently owned or controlled by Google and acquired in the future, licensable by Google that are necessarily infringed by this implementation of JPEG XL. This grant does not include claims that would be infringed only as a consequence of further modification of this implementation. If you or your agent or exclusive licensee institute or order or agree to the institution of patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that this implementation of JPEG XL or any code incorporated within this implementation of JPEG XL constitutes direct or contributory patent infringement, or inducement of patent infringement, then any patent rights granted to you under this License for this implementation of JPEG XL shall terminate as of the date such litigation is filed. libjxl-0.11.1/README.md000066400000000000000000000153031472134335300143440ustar00rootroot00000000000000# JPEG XL reference implementation [![Build/Test](https://github.com/libjxl/libjxl/actions/workflows/build_test.yml/badge.svg)]( https://github.com/libjxl/libjxl/actions/workflows/build_test.yml) [![Build/Test Cross](https://github.com/libjxl/libjxl/actions/workflows/build_test_cross.yml/badge.svg)]( https://github.com/libjxl/libjxl/actions/workflows/build_test_cross.yml) [![Conformance](https://github.com/libjxl/libjxl/actions/workflows/conformance.yml/badge.svg)]( https://github.com/libjxl/libjxl/actions/workflows/conformance.yml) [![CIFuzz](https://github.com/libjxl/libjxl/actions/workflows/fuzz.yml/badge.svg)]( https://github.com/libjxl/libjxl/actions/workflows/fuzz.yml) [![Releases](https://github.com/libjxl/libjxl/actions/workflows/release.yaml/badge.svg)]( 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) JXL logo 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 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 specification and can be decoded with current and future `djxl` decoders or the `libjxl` decoding library. ## Installation 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`. [![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) From the [releases page](https://github.com/libjxl/libjxl/releases/) the following can be downloaded: - Windows binaries - Debian and Ubuntu .deb packages Of course you can also [build libjxl from sources](BUILDING.md). ## Usage To encode a source image to JPEG XL with default settings: ```bash cjxl input.png output.jxl ``` 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 djxl input.jxl output.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 For speed benchmarks on single images in single or multi-threaded decoding `djxl` can print decoding speed information. See `djxl --help` for details on the decoding options and note that the output image is optional for benchmarking purposes. For more comprehensive benchmarking options, see the [benchmarking guide](doc/benchmarking.md). ### Library API 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. ## License This software is available under a 3-clause BSD license which can be found in the [LICENSE](LICENSE) file, with an "Additional IP Rights Grant" as outlined in the [PATENTS](PATENTS) file. Please note that the PATENTS file only mentions Google since Google is the legal entity receiving the Contributor License Agreements (CLA) from all contributors to the JPEG XL Project, including the initial main contributors to the JPEG XL format: Cloudinary and Google. ## Additional documentation ### Codec description * [JPEG XL Format Overview](doc/format_overview.md) * [Introductory paper](https://www.spiedigitallibrary.org/proceedings/Download?fullDOI=10.1117%2F12.2529237) (open-access) * [XL Overview](doc/xl_overview.md) - a brief introduction to the source code modules * [JPEG XL white paper](https://ds.jpeg.org/whitepapers/jpeg-xl-whitepaper.pdf) * [JPEG XL official website](https://jpeg.org/jpegxl) * [JPEG XL community website](https://jpegxl.info) ### Development process * [More information on testing/build options](doc/building_and_testing.md) * [Git guide for JPEG XL](doc/developing_in_github.md) - for developers * [Fuzzing](doc/fuzzing.md) - for developers * [Building Web Assembly artifacts](doc/building_wasm.md) * [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 If you encounter a bug or other issue with the software, please open an Issue here. There is a [subreddit about JPEG XL](https://www.reddit.com/r/jpegxl/), and informal chatting with developers and early adopters of `libjxl` can be done on the [JPEG XL Discord server](https://discord.gg/DqkQgDRTFu). libjxl-0.11.1/SECURITY.md000066400000000000000000000062401472134335300146560ustar00rootroot00000000000000# Security and Vulnerability Policy for libjxl ## TL;DR: CPE prefix: `cpe:2.3:a:libjxl_project:libjxl` To report a security issue, please email libjxl-security@google.com. Include in your email a description of the issue, the steps you took to create the issue, affected versions, and if known, mitigations for the issue. Our vulnerability management team will acknowledge receiving your email within 3 working days. This project follows a 90 day disclosure timeline. For all other bugs, where there are no security implications about disclosing the unpatched bug, open a [new issue](https://github.com/libjxl/libjxl/issues) checking first for existing similar issues. If in doubt about the security impact of a bug you discovered, email first. ## Policy overview libjxl's Security Policy is based on the [Google Open Source program guidelines](https://github.com/google/oss-vulnerability-guide) for coordinated vulnerability disclosure. Early versions of `libjxl` had a different security policy that didn't provide security and vulnerability disclosure support. Versions up to and including 0.3.7 are not covered and won't receive any security advisory. Only released versions, starting from version 0.5, are covered by this policy. Development branches, arbitrary commits from `main` branch or even releases with backported features externally patched on top are not covered. Only those versions with a release tag in `libjxl`'s repository are covered, starting from version 0.5. ## What's a "Security bug" A security bug is a bug that can potentially be exploited to let an attacker gain unauthorized access or privileges such as disclosing information or arbitrary code execution. Not all fuzzer-found bugs and not all assert() failures are considered security bugs in libjxl. For a detailed explanation and examples see our [Security Vulnerabilities Playbook](doc/vuln_playbook.md). ## What to expect To report a security issue, please email libjxl-security@google.com with all the details about the bug you encountered. * Include a description of the issue, steps to reproduce, etc. Compiler versions, flags, exact version used and even CPU are often relevant given our usage of SIMD and run-time dispatch of SIMD instructions. * A member of our security team will reply to you within 3 business days. Note that business days are different in different countries. * We will evaluate the issue and we may require more input from your side to reproduce it. * If the issue fits in the description of a security bug, we will issue a CVE, publish a fix and make a new minor or patch release with it. There is a maximum of 90 day disclosure timeline, we ask you to not publish the details before the 90 day deadline or the release date (whichever comes first). * In the case that we publish a CVE we will credit the external researcher who reported the issue. When reporting security issues please let us know if you need to include specific information while doing so, like for example a company affiliation. Our security team follows the [Security Vulnerabilities Playbook](doc/vuln_playbook.md). For more details about the process and policies please take a look at it. libjxl-0.11.1/WORKSPACE000066400000000000000000000015401472134335300143440ustar00rootroot00000000000000# 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", ) libjxl-0.11.1/bash_test.sh000077500000000000000000000216411472134335300154020ustar00rootroot00000000000000#!/bin/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. # Tests implemented in bash. These typically will run checks about the source # code rather than the compiled one. SELF=$(realpath "$0") MYDIR=$(dirname "${SELF}") set -u test_includes() { local ret=0 local f for f in $(git ls-files | grep -E '(\.cc|\.cpp|\.h)$'); do if [ ! -e "$f" ]; then continue fi # Check that the 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 # $f is not in third_party/ # Check that local files don't use the full path to third_party/ # directory since the installed versions will not have that path. # Add an exception for third_party/dirent.h. if grep -v -F 'third_party/dirent.h' "$f" | \ grep -i -H -n -E '#include\s*[<"]third_party/' >&2 && [[ $ret -eq 0 ]]; then cat >&2 <&2 ret=1 fi done return ${ret} } test_copyright() { local ret=0 local f for f in $( git ls-files | grep -E \ '(Dockerfile.*|\.c|\.cc|\.cpp|\.gni|\.h|\.java|\.sh|\.m|\.py|\.ui|\.yml)$'); do if [ ! -e "$f" ]; then continue fi if [[ "${f#third_party/}" == "$f" ]]; then # $f is not in third_party/ if ! head -n 10 "$f" | grep -F 'Copyright (c) the JPEG XL Project Authors.' >/dev/null ; then echo "$f: Missing Copyright blob near the top of the file." >&2 ret=1 fi if ! head -n 10 "$f" | grep -F 'Use of this source code is governed by a BSD-style' \ >/dev/null ; then echo "$f: Missing License blob near the top of the file." >&2 ret=1 fi fi done return ${ret} } # Check that we don't use "%zu" or "%zd" in format string for size_t. test_printf_size_t() { local ret=0 if grep -n -E '%[0-9]*z[udx]' \ $(git ls-files | grep -E '(\.c|\.cc|\.cpp|\.h)$'); then echo "Don't use '%zu' or '%zd' in a format string, instead use " \ "'%\" PRIuS \"' or '%\" PRIdS \"'." >&2 ret=1 fi 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 local f for f in $(git ls-files | grep -E "\.cc$" | xargs grep 'PRI[udx]S' | cut -f 1 -d : | uniq); do if [ ! -e "$f" ]; then continue fi if ! grep -F printf_macros.h "$f" >/dev/null; then echo "$f: Add lib/jxl/base/printf_macros.h for PRI.S, or use other " \ "types for code outside lib/jxl library." >&2 ret=1 fi done 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. # those may re-define PRIuS unconditionally causing a compile error. echo "$f: Don't use PRI.S in header files. Sorry." ret=1 done return ${ret} } # Check that "dec_" code doesn't depend on "enc_" headers. test_dec_enc_deps() { local ret=0 local f for f in $(git ls-files | grep -E '/dec_'); do if [ ! -e "$f" ]; then continue fi if [[ "${f#third_party/}" == "$f" ]]; then # $f is not in third_party/ if grep -n -H -E "#include.*/enc_" "$f" >&2; then echo "$f: Don't include \"enc_*\" files from \"dec_*\" files." >&2 ret=1 fi fi done return ${ret} } # Check for git merge conflict markers. test_merge_conflict() { local ret=0 TEXT_FILES='(\.cc|\.cpp|\.h|\.sh|\.m|\.py|\.md|\.txt|\.cmake)$' for f in $(git ls-files | grep -E "${TEXT_FILES}"); do if [ ! -e "$f" ]; then continue fi if grep -E '^<<<<<<< ' "$f"; then echo "$f: Found git merge conflict marker. Please resolve." >&2 ret=1 fi done return ${ret} } # Check that the library and the package have the same version. This prevents # accidentally having them out of sync. get_version() { local varname=$1 local line=$(grep -F "set(${varname} " lib/CMakeLists.txt | head -n 1) [[ -n "${line}" ]] line="${line#set(${varname} }" line="${line%)}" echo "${line}" } test_version() { local major=$(get_version JPEGXL_MAJOR_VERSION) local minor=$(get_version JPEGXL_MINOR_VERSION) local patch=$(get_version JPEGXL_PATCH_VERSION) # Check that the version is not empty if [[ -z "${major}${minor}${patch}" ]]; then echo "Couldn't parse version from CMakeLists.txt" >&2 return 1 fi local pkg_version=$(head -n 1 debian/changelog) # Get only the part between the first "jpeg-xl (" and the following ")". pkg_version="${pkg_version#jpeg-xl (}" pkg_version="${pkg_version%%)*}" if [[ -z "${pkg_version}" ]]; then echo "Couldn't parse version from debian package" >&2 return 1 fi local lib_version="${major}.${minor}.${patch}" lib_version="${lib_version%.0}" if [[ "${pkg_version}" != "${lib_version}"* ]]; then echo "Debian package version (${pkg_version}) doesn't match library" \ "version (${lib_version})." >&2 return 1 fi return 0 } # Check that the SHA versions in deps.sh matches the git submodules. test_deps_version() { while IFS= read -r line; do if [[ "${line:0:10}" != "[submodule" ]]; then continue fi line="${line#[submodule \"}" line="${line%\"]}" local varname=$(tr '[:lower:]' '[:upper:]' <<< "${line}") varname="${varname/\//_}" if ! grep -F "${varname}=" deps.sh >/dev/null; then # Ignoring submodule not in deps.sh continue fi local deps_sha=$(grep -F "${varname}=" deps.sh | cut -f 2 -d '"') [[ -n "${deps_sha}" ]] local git_sha=$(git ls-tree -r HEAD "${line}" | cut -f 1 | cut -f 3 -d ' ') if [[ "${deps_sha}" != "${git_sha}" ]]; then cat >&2 </dev/null; then cat >&2 <&2; then echo "Don't use \"%n\"." >&2 ret=1 fi done return ${ret} } main() { local ret=0 cd "${MYDIR}" if ! git rev-parse >/dev/null 2>/dev/null; then echo "Not a git checkout, skipping bash_test" return 0 fi IFS=$'\n' for f in $(declare -F); do local test_name=$(echo "$f" | cut -f 3 -d ' ') # Runs all the local bash functions that start with "test_". if [[ "${test_name}" == test_* ]]; then echo "Test ${test_name}: Start" if ${test_name}; then echo "Test ${test_name}: PASS" else echo "Test ${test_name}: FAIL" ret=1 fi fi done return ${ret} } main "$@" libjxl-0.11.1/ci.sh000077500000000000000000001440631472134335300140250ustar00rootroot00000000000000#!/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. # Continuous integration helper module. This module is meant to be called from # workflows during the continuous integration build, as well as from the # command line for developers. set -eu OS=`uname -s` 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}" if [[ -n "${BUILD_TARGET}" ]]; then BUILD_DIR="${BUILD_DIR:-${MYDIR}/build-${BUILD_TARGET%%-*}}" else BUILD_DIR="${BUILD_DIR:-${MYDIR}/build}" 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} export CXX=${CXX:-clang++} # Time limit for the "fuzz" command in seconds (0 means no limit). FUZZER_MAX_TIME="${FUZZER_MAX_TIME:-0}" SANITIZER="none" if [[ "${BUILD_TARGET%%-*}" == "x86_64" || "${BUILD_TARGET%%-*}" == "i686" ]]; then # Default to building all targets, even if compiler baseline is SSE4 HWY_BASELINE_TARGETS=${HWY_BASELINE_TARGETS:-HWY_EMU128} else HWY_BASELINE_TARGETS=${HWY_BASELINE_TARGETS:-} fi # 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_CROSSCOMPILING_EMULATOR=${CMAKE_CROSSCOMPILING_EMULATOR:-} CMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS:-} CMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH:-} CMAKE_MODULE_LINKER_FLAGS=${CMAKE_MODULE_LINKER_FLAGS:-} CMAKE_SHARED_LINKER_FLAGS=${CMAKE_SHARED_LINKER_FLAGS:-} CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE:-} if [[ "${ENABLE_WASM_SIMD}" -ne "0" ]]; then CMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -msimd128" CMAKE_C_FLAGS="${CMAKE_C_FLAGS} -msimd128" 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=${GITHUB_SHA:-} JPEGXL_VERSION=${JPEGXL_VERSION:-} # Benchmark parameters STORE_IMAGES=${STORE_IMAGES:-1} BENCHMARK_CORPORA="${MYDIR}/third_party/corpora" # Local flags passed to sanitizers. UBSAN_FLAGS=( -fsanitize=alignment -fsanitize=bool -fsanitize=bounds -fsanitize=builtin -fsanitize=enum -fsanitize=float-cast-overflow -fsanitize=float-divide-by-zero -fsanitize=integer-divide-by-zero -fsanitize=null -fsanitize=object-size -fsanitize=pointer-overflow -fsanitize=return -fsanitize=returns-nonnull-attribute -fsanitize=shift-base -fsanitize=shift-exponent -fsanitize=unreachable -fsanitize=vla-bound -fno-sanitize-recover=undefined # Brunsli uses unaligned accesses to uint32_t, so alignment is just a warning. -fsanitize-recover=alignment ) # -fsanitize=function doesn't work on aarch64 and arm. if [[ "${BUILD_TARGET%%-*}" != "aarch64" && "${BUILD_TARGET%%-*}" != "arm" ]]; then UBSAN_FLAGS+=( -fsanitize=function ) fi if [[ "${BUILD_TARGET%%-*}" != "arm" ]]; then UBSAN_FLAGS+=( -fsanitize=signed-integer-overflow ) fi 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 2>/dev/null | head -n 1) else COLORDIFF_BIN="cat" fi 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 2>/dev/null | head -n 1) 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) clang_version=${clang_version#"Debian "} clang_version=${clang_version#"Ubuntu "} local llvm_tag case "${clang_version}" in "clang version 6."*) CLANG_VERSION="6.0" ;; "clang version "*) # Any other clang version uses just the major version number. local suffix="${clang_version#clang version }" CLANG_VERSION="${suffix%%.*}" ;; "emcc"*) # We can't use asan or msan in the emcc case. ;; *) 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 } # Executed on exit. on_exit() { local retcode="$1" # Always cleanup the CLEANUP_FILES. cleanup } trap 'retcode=$?; { set +x; } 2>/dev/null; on_exit ${retcode}' INT TERM EXIT # These variables are populated when calling merge_request_commits(). # The current hash at the top of the current branch or merge request branch (if # running from a merge request pipeline). MR_HEAD_SHA="" # The common ancestor between the current commit and the tracked branch, such # as main. This includes a list MR_ANCESTOR_SHA="" # Populate MR_HEAD_SHA and MR_ANCESTOR_SHA. merge_request_commits() { { set +x; } 2>/dev/null # GITHUB_SHA is the current reference being build in GitHub Actions. if [[ -n "${GITHUB_SHA:-}" ]]; then # GitHub normally does a checkout of a merge commit on a shallow repository # by default. We want to get a bit more of the history to be able to diff # 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 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 MR_HEAD_SHA=$(git -C "${MYDIR}" rev-parse -q "HEAD") fi 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 pull request workflow. MR_ANCESTOR_SHA=$(git -C "${MYDIR}" rev-parse -q HEAD@{upstream} || true) fi if [[ -z "${MR_ANCESTOR_SHA}" ]]; then echo "Warning, not tracking any branch, using the last commit in HEAD.">&2 # This prints the return value with just HEAD. MR_ANCESTOR_SHA=$(git -C "${MYDIR}" rev-parse -q "${MR_HEAD_SHA}^") else # GitHub runs the pipeline on a merge commit, no need to look for the common # ancestor in that case. if [[ -z "${GITHUB_BASE_REF:-}" ]]; then MR_ANCESTOR_SHA=$(git -C "${MYDIR}" merge-base \ "${MR_ANCESTOR_SHA}" "${MR_HEAD_SHA}") fi fi set -x } # Set up and export the environment variables needed by the child processes. export_env() { if [[ "${BUILD_TARGET}" == *mingw32 ]]; then # Wine needs to know the paths to the mingw dlls. These should be # separated by ';'. WINEPATH=$("${CC:-clang}" -print-search-dirs --target="${BUILD_TARGET}" \ | grep -F 'libraries: =' | cut -f 2- -d '=' | tr ':' ';') # We also need our own libraries in the wine path. local real_build_dir=$(realpath "${BUILD_DIR}") # Some library .dll dependencies are installed in /bin: export WINEPATH="${WINEPATH};${real_build_dir};${real_build_dir}/third_party/brotli;/usr/${BUILD_TARGET}/bin" local prefix="${BUILD_DIR}/wineprefix" mkdir -p "${prefix}" export WINEPREFIX=$(realpath "${prefix}") fi # Sanitizers need these variables to print and properly format the stack # traces: LLVM_SYMBOLIZER=$("${CC:-clang}" -print-prog-name=llvm-symbolizer || true) if [[ -n "${LLVM_SYMBOLIZER}" ]]; then export ASAN_SYMBOLIZER_PATH="${LLVM_SYMBOLIZER}" export MSAN_SYMBOLIZER_PATH="${LLVM_SYMBOLIZER}" export UBSAN_SYMBOLIZER_PATH="${LLVM_SYMBOLIZER}" fi } cmake_configure() { export_env if [[ "${STACK_SIZE:-0}" == 1 ]]; then # Dump the stack size of each function in the .stack_sizes section for # analysis. CMAKE_C_FLAGS+=" -fstack-size-section" CMAKE_CXX_FLAGS+=" -fstack-size-section" fi local args=( -B"${BUILD_DIR}" -H"${MYDIR}" -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" -G Ninja -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS}" -DCMAKE_C_FLAGS="${CMAKE_C_FLAGS}" -DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" -DCMAKE_MODULE_LINKER_FLAGS="${CMAKE_MODULE_LINKER_FLAGS}" -DCMAKE_SHARED_LINKER_FLAGS="${CMAKE_SHARED_LINKER_FLAGS}" -DJPEGXL_VERSION="${JPEGXL_VERSION}" -DSANITIZER="${SANITIZER}" # These are not enabled by default in cmake. -DJPEGXL_ENABLE_VIEWERS=ON -DJPEGXL_ENABLE_PLUGINS=ON -DJPEGXL_ENABLE_DEVTOOLS=ON # We always use libfuzzer in the ci.sh wrapper. -DJPEGXL_FUZZER_LINK_FLAGS="-fsanitize=fuzzer" ) if [[ "${BUILD_TARGET}" != *mingw32 ]]; then args+=( -DJPEGXL_WARNINGS_AS_ERRORS=ON ) fi if [[ -n "${BUILD_TARGET}" ]]; then local system_name="Linux" if [[ "${BUILD_TARGET}" == *mingw32 ]]; then # When cross-compiling with mingw the target must be set to Windows and # run programs with wine. system_name="Windows" args+=( -DCMAKE_CROSSCOMPILING_EMULATOR="${WINE_BIN}" # Normally CMake automatically defines MINGW=1 when building with the # mingw compiler (x86_64-w64-mingw32-gcc) but we are normally compiling # with clang. -DMINGW=1 ) fi # EMSCRIPTEN toolchain sets the right values itself if [[ "${BUILD_TARGET}" != wasm* ]]; then # If set, BUILD_TARGET must be the target triplet such as # x86_64-unknown-linux-gnu. args+=( -DCMAKE_C_COMPILER_TARGET="${BUILD_TARGET}" -DCMAKE_CXX_COMPILER_TARGET="${BUILD_TARGET}" # Only the first element of the target triplet. -DCMAKE_SYSTEM_PROCESSOR="${BUILD_TARGET%%-*}" -DCMAKE_SYSTEM_NAME="${system_name}" -DCMAKE_TOOLCHAIN_FILE="${CMAKE_TOOLCHAIN_FILE}" ) else args+=( # sjpeg confuses WASM SIMD with SSE. -DSJPEG_ENABLE_SIMD=OFF # Building shared libs is not very useful for WASM. -DBUILD_SHARED_LIBS=OFF ) fi args+=( # These are needed to make googletest work when cross-compiling. -DCMAKE_CROSSCOMPILING=1 -DHAVE_STD_REGEX=0 -DHAVE_POSIX_REGEX=0 -DHAVE_GNU_POSIX_REGEX=0 -DHAVE_STEADY_CLOCK=0 -DHAVE_THREAD_SAFETY_ATTRIBUTES=0 ) if [[ -z "${CMAKE_FIND_ROOT_PATH}" ]]; then # find_package() will look in this prefix for libraries. CMAKE_FIND_ROOT_PATH="/usr/${BUILD_TARGET}" fi if [[ -z "${CMAKE_PREFIX_PATH}" ]]; then CMAKE_PREFIX_PATH="/usr/${BUILD_TARGET}" fi # Use pkg-config for the target. If there's no pkg-config available for the # target we can set the PKG_CONFIG_PATH to the appropriate path in most # linux distributions. local pkg_config=$(which "${BUILD_TARGET}-pkg-config" || true) if [[ -z "${pkg_config}" ]]; then pkg_config=$(which pkg-config) export PKG_CONFIG_LIBDIR="/usr/${BUILD_TARGET}/lib/pkgconfig" fi if [[ -n "${pkg_config}" ]]; then args+=(-DPKG_CONFIG_EXECUTABLE="${pkg_config}") fi fi if [[ -n "${CMAKE_CROSSCOMPILING_EMULATOR}" ]]; then args+=( -DCMAKE_CROSSCOMPILING_EMULATOR="${CMAKE_CROSSCOMPILING_EMULATOR}" ) fi if [[ -n "${CMAKE_FIND_ROOT_PATH}" ]]; then args+=( -DCMAKE_FIND_ROOT_PATH="${CMAKE_FIND_ROOT_PATH}" ) fi if [[ -n "${CMAKE_PREFIX_PATH}" ]]; then args+=( -DCMAKE_PREFIX_PATH="${CMAKE_PREFIX_PATH}" ) fi if [[ -n "${CMAKE_C_COMPILER_LAUNCHER}" ]]; then args+=( -DCMAKE_C_COMPILER_LAUNCHER="${CMAKE_C_COMPILER_LAUNCHER}" ) fi if [[ -n "${CMAKE_CXX_COMPILER_LAUNCHER}" ]]; then args+=( -DCMAKE_CXX_COMPILER_LAUNCHER="${CMAKE_CXX_COMPILER_LAUNCHER}" ) fi if [[ -n "${CMAKE_MAKE_PROGRAM}" ]]; then args+=( -DCMAKE_MAKE_PROGRAM="${CMAKE_MAKE_PROGRAM}" ) fi if [[ "${BUILD_TARGET}" == wasm* ]]; then emcmake cmake "${args[@]}" "$@" else cmake "${args[@]}" "$@" fi } 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}" -- $TARGETS # Pack test binaries if requested. if [[ "${PACK_TEST:-}" == "1" ]]; then (cd "${BUILD_DIR}" ${FIND_BIN} -name '*.cmake' -a '!' -path '*CMakeFiles*' # 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 - \ --use-compress-program="xz --threads=$(nproc --all || echo 1) -6" du -h "${BUILD_DIR}/tests.tar.xz" # Pack coverage data if also available. touch "${BUILD_DIR}/gcno.sentinel" (cd "${BUILD_DIR}"; echo gcno.sentinel; ${FIND_BIN} -name '*gcno') | \ tar -C "${BUILD_DIR}" -cvf "${BUILD_DIR}/gcno.tar.xz" -T - \ --use-compress-program="xz --threads=$(nproc --all || echo 1) -6" fi if [[ "${SKIP_TEST}" -ne "1" ]]; then (cd "${BUILD_DIR}" export UBSAN_OPTIONS=print_stacktrace=1 [[ "${TEST_STACK_LIMIT}" == "none" ]] || ulimit -s "${TEST_STACK_LIMIT}" ctest -j $(nproc --all || echo 1) ${TEST_SELECTOR} --output-on-failure) fi } # Configure the build to strip unused functions. This considerably reduces the # output size, specially for tests which only use a small part of the whole # library. strip_dead_code() { # Emscripten does tree shaking without any extra flags. if [[ "${BUILD_TARGET}" == wasm* ]]; then return 0 fi # -ffunction-sections, -fdata-sections and -Wl,--gc-sections effectively # discard all unreachable code, reducing the code size. For this to work, we # need to also pass --no-export-dynamic to prevent it from exporting all the # internal symbols (like functions) making them all reachable and thus not a # candidate for removal. CMAKE_CXX_FLAGS+=" -ffunction-sections -fdata-sections" CMAKE_C_FLAGS+=" -ffunction-sections -fdata-sections" if [[ "${OS}" == "Darwin" ]]; then CMAKE_EXE_LINKER_FLAGS+=" -dead_strip" CMAKE_SHARED_LINKER_FLAGS+=" -dead_strip" else CMAKE_EXE_LINKER_FLAGS+=" -Wl,--gc-sections -Wl,--no-export-dynamic" CMAKE_SHARED_LINKER_FLAGS+=" -Wl,--gc-sections -Wl,--no-export-dynamic" fi } ### Externally visible commands cmd_debug() { CMAKE_BUILD_TYPE="DebugOpt" cmake_configure "$@" cmake_build_and_test } cmd_release() { CMAKE_BUILD_TYPE="Release" strip_dead_code cmake_configure "$@" cmake_build_and_test } cmd_opt() { CMAKE_BUILD_TYPE="RelWithDebInfo" CMAKE_CXX_FLAGS+=" -DJXL_IS_DEBUG_BUILD" cmake_configure "$@" cmake_build_and_test } cmd_coverage() { # -O0 prohibits stack space reuse -> causes stack-overflow on dozens of tests. TEST_STACK_LIMIT="none" cmd_release -DJPEGXL_ENABLE_COVERAGE=ON "$@" if [[ "${SKIP_TEST}" -ne "1" ]]; then # If we didn't run the test we also don't print a coverage report. cmd_coverage_report fi } cmd_coverage_report() { LLVM_COV=$("${CC:-clang}" -print-prog-name=llvm-cov) local real_build_dir=$(realpath "${BUILD_DIR}") local gcovr_args=( -r "${real_build_dir}" --gcov-executable "${LLVM_COV} gcov" # 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.*' --exclude '.*test_utils..*' --object-directory "${real_build_dir}" ) ( cd "${real_build_dir}" gcovr "${gcovr_args[@]}" --html --html-details \ --output="${real_build_dir}/coverage.html" gcovr "${gcovr_args[@]}" --print-summary | tee "${real_build_dir}/coverage.txt" gcovr "${gcovr_args[@]}" --xml --output="${real_build_dir}/coverage.xml" ) } cmd_test() { export_env # Unpack tests if needed. if [[ -e "${BUILD_DIR}/tests.tar.xz" && ! -d "${BUILD_DIR}/tests" ]]; then tar -C "${BUILD_DIR}" -Jxvf "${BUILD_DIR}/tests.tar.xz" fi if [[ -e "${BUILD_DIR}/gcno.tar.xz" && ! -d "${BUILD_DIR}/gcno.sentinel" ]]; then tar -C "${BUILD_DIR}" -Jxvf "${BUILD_DIR}/gcno.tar.xz" fi (cd "${BUILD_DIR}" export UBSAN_OPTIONS=print_stacktrace=1 [[ "${TEST_STACK_LIMIT}" == "none" ]] || ulimit -s "${TEST_STACK_LIMIT}" ctest -j $(nproc --all || echo 1) ${TEST_SELECTOR} --output-on-failure "$@") } cmd_gbench() { export_env (cd "${BUILD_DIR}" export UBSAN_OPTIONS=print_stacktrace=1 lib/jxl_gbench \ --benchmark_counters_tabular=true \ --benchmark_out_format=json \ --benchmark_out=gbench.json "$@" ) } cmd_asanfuzz() { CMAKE_CXX_FLAGS+=" -fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1" CMAKE_C_FLAGS+=" -fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1" cmd_asan -DJPEGXL_ENABLE_FUZZERS=ON "$@" } 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. cmd_msan_install fi CMAKE_CXX_FLAGS+=" -fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1" CMAKE_C_FLAGS+=" -fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1" cmd_msan -DJPEGXL_ENABLE_FUZZERS=ON "$@" } cmd_asan() { SANITIZER="asan" CMAKE_C_FLAGS+=" -g -DADDRESS_SANITIZER \ -fsanitize=address ${UBSAN_FLAGS[@]}" CMAKE_CXX_FLAGS+=" -g -DADDRESS_SANITIZER \ -fsanitize=address ${UBSAN_FLAGS[@]}" strip_dead_code cmake_configure "$@" -DJPEGXL_ENABLE_TCMALLOC=OFF cmake_build_and_test } cmd_tsan() { SANITIZER="tsan" local tsan_args=( -g -DTHREAD_SANITIZER -fsanitize=thread ) CMAKE_C_FLAGS+=" ${tsan_args[@]}" CMAKE_CXX_FLAGS+=" ${tsan_args[@]}" cmake_configure "$@" -DJPEGXL_ENABLE_TCMALLOC=OFF cmake_build_and_test } cmd_msan() { SANITIZER="msan" detect_clang_version local msan_prefix="${HOME}/.msan/${CLANG_VERSION}" 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. cmd_msan_install fi local msan_c_flags=( -fsanitize=memory -fno-omit-frame-pointer -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[@]}" # Some C++ sources don't use the std at all, so the -stdlib=libc++ is unused # in those cases. Ignore the warning. -Wno-unused-command-line-argument -stdlib=libc++ # We include the libc++ from the msan directory instead, so we don't want # the std includes. -nostdinc++ -cxx-isystem"${msan_prefix}/include/c++/v1" ) local msan_linker_flags=( -L"${msan_prefix}"/lib -Wl,-rpath -Wl,"${msan_prefix}"/lib/ ) 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[@]}" \ -DJPEGXL_ENABLE_FUZZERS=OFF cmake_build_and_test } # 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}") local msan_root="${HOME}/.msan" mkdir -p "${msan_root}" # Detect the llvm to install: export CC="${CC:-clang}" export CXX="${CXX:-clang++}" detect_clang_version # Allow overriding the LLVM checkout. local llvm_root="${LLVM_ROOT:-}" if [ -z "${llvm_root}" ]; then 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="${msan_root}/${CLANG_VERSION}" rm -rf "${msan_prefix}" 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 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. _cmd_ossfuzz() { local sanitizer="$1" shift mkdir -p "${BUILD_DIR}" local real_build_dir=$(realpath "${BUILD_DIR}") # oss-fuzz defines three directories: # * /work, with the working directory to do re-builds # * /src, with the source code to build # * /out, with the output directory where to copy over the built files. # We use $BUILD_DIR as the /work and the script directory as the /src. The # /out directory is ignored as developers are used to look for the fuzzers in # $BUILD_DIR/tools/ directly. if [[ "${sanitizer}" = "memory" && ! -d "${BUILD_DIR}/msan" ]]; then sudo docker run --rm -i \ --user $(id -u):$(id -g) \ -v "${real_build_dir}":/work \ gcr.io/oss-fuzz-base/msan-libs-builder \ bash -c "cp -r /msan /work" fi # Args passed to ninja. These will be evaluated as a string separated by # spaces. local jpegxl_extra_args="$@" sudo docker run --rm -i \ -e JPEGXL_UID=$(id -u) \ -e JPEGXL_GID=$(id -g) \ -e FUZZING_ENGINE="${FUZZING_ENGINE:-libfuzzer}" \ -e SANITIZER="${sanitizer}" \ -e ARCHITECTURE=x86_64 \ -e FUZZING_LANGUAGE=c++ \ -e MSAN_LIBS_PATH="/work/msan" \ -e JPEGXL_EXTRA_ARGS="${jpegxl_extra_args}" \ -v "${MYDIR}":/src/libjxl \ -v "${MYDIR}/tools/scripts/ossfuzz-build.sh":/src/build.sh \ -v "${real_build_dir}":/work \ gcr.io/oss-fuzz/libjxl } cmd_ossfuzz_asan() { _cmd_ossfuzz address "$@" } cmd_ossfuzz_msan() { _cmd_ossfuzz memory "$@" } cmd_ossfuzz_ubsan() { _cmd_ossfuzz undefined "$@" } cmd_ossfuzz_ninja() { [[ -e "${BUILD_DIR}/build.ninja" ]] local real_build_dir=$(realpath "${BUILD_DIR}") if [[ -e "${BUILD_DIR}/msan" ]]; then echo "ossfuzz_ninja doesn't work with msan builds. Use ossfuzz_msan." >&2 exit 1 fi sudo docker run --rm -i \ --user $(id -u):$(id -g) \ -v "${MYDIR}":/src/libjxl \ -v "${real_build_dir}":/work \ gcr.io/oss-fuzz/libjxl \ ninja -C /work "$@" } 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}" 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}") tar -xf "${small_corpus_tar}" -C "${tmpdir}" run_benchmark "${tmpdir}" 1048576 } cmd_benchmark() { local nikon_corpus_tar="${BENCHMARK_CORPORA}/nikon-subset.tar" mkdir -p "${BENCHMARK_CORPORA}" curl --show-error -o "${nikon_corpus_tar}" -z "${nikon_corpus_tar}" \ "https://storage.googleapis.com/artifacts.jpegxl.appspot.com/corpora/nikon-subset.tar" local tmpdir=$(mktemp -d) CLEANUP_FILES+=("${tmpdir}") tar -xvf "${nikon_corpus_tar}" -C "${tmpdir}" local sem_id="jpegxl_benchmark-$$" local nprocs=$(nproc --all || echo 1) images=() local filename while IFS= read -r filename; do # This removes the './' filename="${filename:2}" local mode if [[ "${filename:0:4}" == "srgb" ]]; then mode="RGB_D65_SRG_Rel_SRG" elif [[ "${filename:0:5}" == "adobe" ]]; then mode="RGB_D65_Ado_Rel_Ado" else echo "Unknown image colorspace: ${filename}" >&2 exit 1 fi png_filename="${filename%.ppm}.png" png_filename=$(echo "${png_filename}" | tr '/' '_') sem --bg --id "${sem_id}" -j"${nprocs}" -- \ "${TOOLS_DIR}/decode_and_encode" \ "${tmpdir}/${filename}" "${mode}" "${tmpdir}/${png_filename}" images+=( "${png_filename}" ) done < <(cd "${tmpdir}"; ${FIND_BIN} . -name '*.ppm' -type f) sem --id "${sem_id}" --wait # We need about 10 GiB per thread on these images. run_benchmark "${tmpdir}" 10485760 } 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 } run_benchmark() { local src_img_dir="$1" local mem_per_thread="${2:-10485760}" local output_dir="${BUILD_DIR}/benchmark_results" mkdir -p "${output_dir}" 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}" --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}" "${TOOLS_DIR}/benchmark_xl" "${benchmark_args[@]}" | \ tee "${output_dir}/results.txt" # Check error code for benchmark_xl command. This will exit if not. return ${PIPESTATUS[0]} ) } # Helper function to wait for the CPU temperature to cool down on ARM. wait_for_temp() { { set +x; } 2>/dev/null local temp_limit=${1:-38000} if [[ -z "${THERMAL_FILE:-}" ]]; then echo "Must define the THERMAL_FILE with the thermal_zoneX/temp file" \ "to read the temperature from. This is normally set in the runner." >&2 exit 1 fi local org_temp=$(cat "${THERMAL_FILE}") if [[ "${org_temp}" -ge "${temp_limit}" ]]; then echo -n "Waiting for temp to get down from ${org_temp}... " fi local temp="${org_temp}" local secs=0 while [[ "${temp}" -ge "${temp_limit}" ]]; do sleep 1 temp=$(cat "${THERMAL_FILE}") echo -n "${temp} " secs=$((secs + 1)) if [[ ${secs} -ge 5 ]]; then break fi done if [[ "${org_temp}" -ge "${temp_limit}" ]]; then echo "Done, temp=${temp}" fi set -x } # Helper function to set the cpuset restriction of the current process. cmd_cpuset() { [[ "${SKIP_CPUSET:-}" != "1" ]] || return 0 local newset="$1" local mycpuset=$(cat /proc/self/cpuset) mycpuset="/dev/cpuset${mycpuset}" # Check that the directory exists: [[ -d "${mycpuset}" ]] if [[ -e "${mycpuset}/cpuset.cpus" ]]; then echo "${newset}" >"${mycpuset}/cpuset.cpus" else echo "${newset}" >"${mycpuset}/cpus" fi } # Return the encoding/decoding speed from the Stats output. _speed_from_output() { local speed="$1" local unit="${2:-MP/s}" if [[ "${speed}" == *"${unit}"* ]]; then speed="${speed%% ${unit}*}" speed="${speed##* }" echo "${speed}" fi } # Run benchmarks on ARM for the big and little CPUs. cmd_arm_benchmark() { # Flags used for cjxl encoder with .png inputs local jxl_png_benchmarks=( # Lossy options: "--epf=0 --distance=1.0 --speed=cheetah" "--epf=2 --distance=1.0 --speed=cheetah" "--epf=0 --distance=8.0 --speed=cheetah" "--epf=1 --distance=8.0 --speed=cheetah" "--epf=2 --distance=8.0 --speed=cheetah" "--epf=3 --distance=8.0 --speed=cheetah" "--modular -Q 90" "--modular -Q 50" # Lossless options: "--modular" "--modular -E 0 -I 0" "--modular -P 5" "--modular --responsive=1" # Near-lossless options: "--epf=0 --distance=0.3 --speed=fast" "--modular -Q 97" ) # Flags used for cjxl encoder with .jpg inputs. These should do lossless # JPEG recompression (of pixels or full jpeg). local jxl_jpeg_benchmarks=( "--num_reps=3" ) local images=( "testdata/jxl/flower/flower.png" ) local jpg_images=( "testdata/jxl/flower/flower.png.im_q85_420.jpg" ) if [[ "${SKIP_CPUSET:-}" == "1" ]]; then # Use a single cpu config in this case. local cpu_confs=("?") else # Otherwise the CPU config comes from the environment: local cpu_confs=( "${RUNNER_CPU_LITTLE}" "${RUNNER_CPU_BIG}" # The CPU description is something like 3-7, so these configurations only # take the first CPU of the group. "${RUNNER_CPU_LITTLE%%-*}" "${RUNNER_CPU_BIG%%-*}" ) # Check that RUNNER_CPU_ALL is defined. In the SKIP_CPUSET=1 case this will # be ignored but still evaluated when calling cmd_cpuset. [[ -n "${RUNNER_CPU_ALL}" ]] fi local jpg_dirname="third_party/corpora/jpeg" mkdir -p "${jpg_dirname}" local jpg_qualities=( 50 80 95 ) for src_img in "${images[@]}"; do for q in "${jpg_qualities[@]}"; do local jpeg_name="${jpg_dirname}/"$(basename "${src_img}" .png)"-q${q}.jpg" convert -sampling-factor 1x1 -quality "${q}" \ "${src_img}" "${jpeg_name}" jpg_images+=("${jpeg_name}") done done local output_dir="${BUILD_DIR}/benchmark_results" mkdir -p "${output_dir}" local runs_file="${output_dir}/runs.txt" if [[ ! -e "${runs_file}" ]]; then echo -e "binary\tflags\tsrc_img\tsrc size\tsrc pixels\tcpuset\tenc size (B)\tenc speed (MP/s)\tdec speed (MP/s)\tJPG dec speed (MP/s)\tJPG dec speed (MB/s)" | tee -a "${runs_file}" fi mkdir -p "${BUILD_DIR}/arm_benchmark" local flags 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=("${TOOLS_DIR}/cjxl") local src_ext="${src_img##*.}" for enc_binary in "${enc_binaries[@]}"; do local enc_binary_base=$(basename "${enc_binary}") # Select the list of flags to use for the current encoder/image pair. local img_benchmarks if [[ "${src_ext}" == "jpg" ]]; then img_benchmarks=("${jxl_jpeg_benchmarks[@]}") else img_benchmarks=("${jxl_png_benchmarks[@]}") fi for flags in "${img_benchmarks[@]}"; do # Encoding step. local enc_file_hash="${enc_binary_base} || $flags || ${src_img} || ${src_img_hash}" enc_file_hash=$(echo "${enc_file_hash}" | sha1sum | cut -f 1 -d ' ') local enc_file="${BUILD_DIR}/arm_benchmark/${enc_file_hash}.jxl" for cpu_conf in "${cpu_confs[@]}"; do cmd_cpuset "${cpu_conf}" # nproc returns the number of active CPUs, which is given by the cpuset # mask. local num_threads="$(nproc)" echo "Encoding with: ${enc_binary_base} img=${src_img} cpus=${cpu_conf} enc_flags=${flags}" local enc_output if [[ "${flags}" == *"modular"* ]]; then # We don't benchmark encoding speed in this case. if [[ ! -f "${enc_file}" ]]; then cmd_cpuset "${RUNNER_CPU_ALL:-}" "${enc_binary}" ${flags} "${src_img}" "${enc_file}.tmp" mv "${enc_file}.tmp" "${enc_file}" cmd_cpuset "${cpu_conf}" fi enc_output=" ?? MP/s" else wait_for_temp enc_output=$("${enc_binary}" ${flags} "${src_img}" "${enc_file}.tmp" \ 2>&1 | tee /dev/stderr | grep -F "MP/s [") mv "${enc_file}.tmp" "${enc_file}" fi local enc_speed=$(_speed_from_output "${enc_output}") local enc_size=$(stat -c "%s" "${enc_file}") echo "Decoding with: img=${src_img} cpus=${cpu_conf} enc_flags=${flags}" local dec_output wait_for_temp 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 ',') local img_size_x=$(echo "${img_size}" | cut -f 1 -d ' ') local img_size_y=$(echo "${img_size}" | cut -f 3 -d ' ') local img_size_px=$(( ${img_size_x} * ${img_size_y} )) local dec_speed=$(_speed_from_output "${dec_output}") # For JPEG lossless recompression modes (where the original is a JPEG) # decode to JPG as well. local jpeg_dec_mps_speed="" local jpeg_dec_mbs_speed="" if [[ "${src_ext}" == "jpg" ]]; then wait_for_temp local dec_file="${BUILD_DIR}/arm_benchmark/${enc_file_hash}.jpg" 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}") local jpeg_dec_mbs_speed=$(_speed_from_output "${dec_output}" MB/s) if ! cmp --quiet "${src_img}" "${dec_file}"; then # Add a start at the end to signal that the files are different. jpeg_dec_mbs_speed+="*" fi fi # Record entry in a tab-separated file. local src_img_base=$(basename "${src_img}") echo -e "${enc_binary_base}\t${flags}\t${src_img_base}\t${img_size}\t${img_size_px}\t${cpu_conf}\t${enc_size}\t${enc_speed}\t${dec_speed}\t${jpeg_dec_mps_speed}\t${jpeg_dec_mbs_speed}" | tee -a "${runs_file}" done done done done cmd_cpuset "${RUNNER_CPU_ALL:-}" cat "${runs_file}" } # Generate a corpus and run the fuzzer on that corpus. cmd_fuzz() { local corpus_dir=$(realpath "${BUILD_DIR}/fuzzer_corpus") local fuzzer_crash_dir=$(realpath "${BUILD_DIR}/fuzzer_crash") mkdir -p "${corpus_dir}" "${fuzzer_crash_dir}" # Generate step. "${TOOLS_DIR}/fuzzer_corpus" "${corpus_dir}" # Run step: local nprocs=$(nproc --all || echo 1) ( 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 linters (clang-format, build_cleaner, buildirier) on the pending CLs. cmd_lint() { merge_request_commits { set +x; } 2>/dev/null 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/scripts/build_cleaner.py" >"${build_patch}"; then ret=1 echo "build_cleaner.py findings:" >&2 "${COLORDIFF_BIN}" <"${build_patch}" 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=() local clang_patch local clang_format for clang_format in "${clang_format_bins[@]}"; do if ! which "${clang_format}" >/dev/null; then continue fi installed+=("${clang_format}") local tmppatch="${tmpdir}/${clang_format}.patch" # We include in this linter all the changes including the uncommitted changes # to avoid printing changes already applied. set -x # Ignoring the error that git-clang-format outputs. 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}" | grep -v 'a/third_party' >/dev/null; then if [[ -n "${LINT_OUTPUT:-}" ]]; then cp "${tmppatch}" "${LINT_OUTPUT}" fi clang_patch="${tmppatch}" else echo "clang-format check OK" >&2 return ${ret} fi done if [[ ${#installed[@]} -eq 0 ]]; then echo "You must install clang-format for \"git clang-format\"" >&2 exit 1 fi # clang-format is installed but found problems. echo "clang-format findings:" >&2 "${COLORDIFF_BIN}" < "${clang_patch}" echo "clang-format found issues in your patches from ${MR_ANCESTOR_SHA}" \ "to the current patch. Run \`./ci.sh lint | patch -p1\` from the base" \ "directory to apply them." >&2 exit 1 } # Runs clang-tidy on the pending CLs. If the "all" argument is passed it runs # clang-tidy over all the source files instead. cmd_tidy() { local what="${1:-}" if [[ -z "${CLANG_TIDY_BIN}" ]]; then echo "ERROR: You must install clang-tidy-7 or newer to use ci.sh tidy" >&2 exit 1 fi local git_args=() if [[ "${what}" == "all" ]]; then git_args=(ls-files) shift else merge_request_commits git_args=( diff-tree --no-commit-id --name-only -r "${MR_ANCESTOR_SHA}" "${MR_HEAD_SHA}" ) fi # Clang-tidy needs the compilation database generated by cmake. if [[ ! -e "${BUILD_DIR}/compile_commands.json" ]]; then # Generate the build options in debug mode, since we need the debug asserts # enabled for the clang-tidy analyzer to use them. CMAKE_BUILD_TYPE="Debug" cmake_configure # Build the autogen targets to generate the .h files from the .ui files. local autogen_targets=( $(ninja -C "${BUILD_DIR}" -t targets | grep -F _autogen: | cut -f 1 -d :) ) if [[ ${#autogen_targets[@]} != 0 ]]; then ninja -C "${BUILD_DIR}" "${autogen_targets[@]}" fi fi cd "${MYDIR}" local nprocs=$(nproc --all || echo 1) local ret=0 if ! parallel -j"${nprocs}" --keep-order -- \ "${CLANG_TIDY_BIN}" -p "${BUILD_DIR}" -format-style=file -quiet "$@" {} \ < <(git "${git_args[@]}" | grep -E '(\.cc|\.cpp)$') \ >"${BUILD_DIR}/clang-tidy.txt"; then ret=1 fi { set +x; } 2>/dev/null echo "Findings statistics:" >&2 grep -E ' \[[A-Za-z\.,\-]+\]' -o "${BUILD_DIR}/clang-tidy.txt" | sort \ | uniq -c >&2 if [[ $ret -ne 0 ]]; then cat >&2 </dev/null local debsdir="${BUILD_DIR}/debs" local f while IFS='' read -r -d '' f; do echo "=====================================================================" echo "Package $f:" dpkg --info $f dpkg --contents $f done < <(find "${BUILD_DIR}/debs" -maxdepth 1 -mindepth 1 -type f \ -name '*.deb' -print0) } build_debian_pkg() { local srcdir="$1" local srcpkg="$2" local options="${3:-}" local debsdir="${BUILD_DIR}/debs" local builddir="${debsdir}/${srcpkg}" # debuild doesn't have an easy way to build out of tree, so we make a copy # of with all symlinks on the first level. mkdir -p "${builddir}" for f in $(find "${srcdir}" -mindepth 1 -maxdepth 1 -printf '%P\n'); do if [[ ! -L "${builddir}/$f" ]]; then rm -f "${builddir}/$f" ln -s "${srcdir}/$f" "${builddir}/$f" fi done ( cd "${builddir}" debuild "${options}" -b -uc -us ) } cmd_debian_build() { local srcpkg="${1:-}" case "${srcpkg}" in jpeg-xl) build_debian_pkg "${MYDIR}" "jpeg-xl" ;; highway) build_debian_pkg "${MYDIR}/third_party/highway" "highway" "${HWY_PKG_OPTIONS}" ;; *) echo "ERROR: Must pass a valid source package name to build." >&2 ;; esac } get_version() { local varname=$1 local line=$(grep -F "set(${varname} " lib/CMakeLists.txt | head -n 1) [[ -n "${line}" ]] line="${line#set(${varname} }" line="${line%)}" echo "${line}" } cmd_bump_version() { local newver="${1:-}" if ! which dch >/dev/null; then echo "Missing dch\nTo install it run:\n sudo apt install devscripts" exit 1 fi if [[ -z "${newver}" ]]; then local major=$(get_version JPEGXL_MAJOR_VERSION) local minor=$(get_version JPEGXL_MINOR_VERSION) local patch=0 minor=$(( ${minor} + 1)) else local major="${newver%%.*}" newver="${newver#*.}" local minor="${newver%%.*}" newver="${newver#${minor}}" local patch="${newver#.}" if [[ -z "${patch}" ]]; then patch=0 fi fi newver="${major}.${minor}.${patch}" echo "Bumping version to ${newver} (${major}.${minor}.${patch})" sed -E \ -e "s/(set\\(JPEGXL_MAJOR_VERSION) [0-9]+\\)/\\1 ${major})/" \ -e "s/(set\\(JPEGXL_MINOR_VERSION) [0-9]+\\)/\\1 ${minor})/" \ -e "s/(set\\(JPEGXL_PATCH_VERSION) [0-9]+\\)/\\1 ${patch})/" \ -i lib/CMakeLists.txt sed -E \ -e "s/(LIBJXL_VERSION: )\"[0-9.]+\"/\\1\"${major}.${minor}.${patch}\"/" \ -e "s/(LIBJXL_ABI_VERSION: )\"[0-9.]+\"/\\1\"${major}.${minor}\"/" \ -i .github/workflows/conformance.yml # Update lib.gni tools/scripts/build_cleaner.py --update # Mark the previous version as "unstable". DEBCHANGE_RELEASE_HEURISTIC=log dch -M --distribution unstable --release '' DEBCHANGE_RELEASE_HEURISTIC=log dch -M \ --newversion "${newver}" \ "Bump JPEG XL version to ${newver}." } # Check that the AUTHORS file contains the email of the committer. cmd_authors() { merge_request_commits local emails local names 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/scripts/check_author.py "${emails[$i]}" "${names[$i]}" done } main() { local cmd="${1:-}" if [[ -z "${cmd}" ]]; then cat >&2 < Build the given source package. debian_stats Print stats about the built packages. oss-fuzz commands: ossfuzz_asan Build the local source inside oss-fuzz docker with asan. ossfuzz_msan Build the local source inside oss-fuzz docker with msan. ossfuzz_ubsan Build the local source inside oss-fuzz docker with ubsan. ossfuzz_ninja Run ninja on the BUILD_DIR inside the oss-fuzz docker. Extra parameters are passed to ninja, for example "djxl_fuzzer" will only build that ninja target. Use for faster build iteration after one of the ossfuzz_*san commands. You can pass some optional environment variables as well: - BUILD_DIR: The output build directory (by default "$$repo/build") - BUILD_TARGET: The target triplet used when cross-compiling. - CMAKE_FLAGS: Convenience flag to pass both CMAKE_C_FLAGS and CMAKE_CXX_FLAGS. - CMAKE_PREFIX_PATH: Installation prefixes to be searched by the find_package. - ENABLE_WASM_SIMD=1: enable experimental SIMD in WASM build (only). - 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. - TEST_SELECTOR: pass additional arguments to ctest, e.g. "-R .Resample.". - STACK_SIZE=1: Generate binaries with the .stack_sizes sections. These optional environment variables are forwarded to the cmake call as parameters: - CMAKE_BUILD_TYPE - CMAKE_C_FLAGS - CMAKE_CXX_FLAGS - CMAKE_C_COMPILER_LAUNCHER - CMAKE_CXX_COMPILER_LAUNCHER - CMAKE_CROSSCOMPILING_EMULATOR - CMAKE_FIND_ROOT_PATH - CMAKE_EXE_LINKER_FLAGS - CMAKE_MAKE_PROGRAM - CMAKE_MODULE_LINKER_FLAGS - CMAKE_SHARED_LINKER_FLAGS - CMAKE_TOOLCHAIN_FILE Example: BUILD_DIR=/tmp/build $0 opt EOF exit 1 fi cmd="cmd_${cmd}" shift set -x "${cmd}" "$@" } main "$@" libjxl-0.11.1/cmake/000077500000000000000000000000001472134335300141435ustar00rootroot00000000000000libjxl-0.11.1/cmake/FindAtomics.cmake000066400000000000000000000032431472134335300173470ustar00rootroot00000000000000# Original issue: # * https://gitlab.kitware.com/cmake/cmake/-/issues/23021#note_1098733 # # For reference: # * https://gcc.gnu.org/wiki/Atomic/GCCMM # # riscv64 specific: # * https://lists.debian.org/debian-riscv/2022/01/msg00009.html # # ATOMICS_FOUND - system has c++ atomics # ATOMICS_LIBRARIES - libraries needed to use c++ atomics include(CheckCXXSourceCompiles) # RISC-V only has 32-bit and 64-bit atomic instructions. GCC is supposed # to convert smaller atomics to those larger ones via masking and # shifting like LLVM, but it’s a known bug that it does not. This means # anything that wants to use atomics on 1-byte or 2-byte types needs # -latomic, but not 4-byte or 8-byte (though it does no harm). set(atomic_code " #include #include std::atomic n8 (0); // riscv64 std::atomic n64 (0); // armel, mipsel, powerpc int main() { ++n8; ++n64; return 0; }") check_cxx_source_compiles("${atomic_code}" ATOMICS_LOCK_FREE_INSTRUCTIONS) if(ATOMICS_LOCK_FREE_INSTRUCTIONS) set(ATOMICS_FOUND TRUE) set(ATOMICS_LIBRARIES) else() set(CMAKE_REQUIRED_LIBRARIES "-latomic") check_cxx_source_compiles("${atomic_code}" ATOMICS_IN_LIBRARY) set(CMAKE_REQUIRED_LIBRARIES) if(ATOMICS_IN_LIBRARY) set(ATOMICS_LIBRARY atomic) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Atomics DEFAULT_MSG ATOMICS_LIBRARY) set(ATOMICS_LIBRARIES ${ATOMICS_LIBRARY}) unset(ATOMICS_LIBRARY) else() if(Atomics_FIND_REQUIRED) message(FATAL_ERROR "Neither lock free instructions nor -latomic found.") endif() endif() endif() unset(atomic_code) libjxl-0.11.1/cmake/FindBrotli.cmake000066400000000000000000000043231472134335300172030ustar00rootroot00000000000000# 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(brlibs brotlicommon brotlienc brotlidec) find_package(PkgConfig QUIET) if (PkgConfig_FOUND) foreach(brlib IN ITEMS ${brlibs}) string(TOUPPER "${brlib}" BRPREFIX) pkg_check_modules("PC_${BRPREFIX}" lib${brlib}) endforeach() endif() find_path(BROTLI_INCLUDE_DIR NAMES brotli/decode.h HINTS ${PC_BROTLICOMMON_INCLUDEDIR} ${PC_BROTLICOMMON_INCLUDE_DIRS} ) foreach(brlib IN ITEMS ${brlibs}) string(TOUPPER "${brlib}" BRPREFIX) find_library(${BRPREFIX}_LIBRARY NAMES ${${BRPREFIX}_NAMES} ${brlib} HINTS ${PC_${BRPREFIX}_LIBDIR} ${PC_${BRPREFIX}_LIBRARY_DIRS} ) if (${BRPREFIX}_LIBRARY AND NOT TARGET ${brlib}) if(CMAKE_VERSION VERSION_LESS "3.13.5") add_library(${brlib} INTERFACE IMPORTED GLOBAL) 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}) else() add_library(${brlib} INTERFACE IMPORTED GLOBAL) target_include_directories(${brlib} INTERFACE ${BROTLI_INCLUDE_DIR}) target_link_libraries(${brlib} INTERFACE ${${BRPREFIX}_LIBRARY}) target_link_options(${brlib} INTERFACE ${PC_${BRPREFIX}_LDFLAGS_OTHER}) target_compile_options(${brlib} INTERFACE ${PC_${BRPREFIX}_CFLAGS_OTHER}) endif() endif() endforeach() if (BROTLICOMMON_FOUND AND BROTLIENC_FOUND AND BROTLIDEC_FOUND) set(Brotli_FOUND ON) else () set(Brotli_FOUND OFF) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Brotli FOUND_VAR Brotli_FOUND REQUIRED_VARS BROTLI_INCLUDE_DIR BROTLICOMMON_LIBRARY BROTLIENC_LIBRARY BROTLIDEC_LIBRARY VERSION_VAR Brotli_VERSION ) mark_as_advanced( BROTLI_INCLUDE_DIR BROTLICOMMON_LIBRARY BROTLIENC_LIBRARY BROTLIDEC_LIBRARY ) if (Brotli_FOUND) set(Brotli_LIBRARIES ${BROTLICOMMON_LIBRARY} ${BROTLIENC_LIBRARY} ${BROTLIDEC_LIBRARY}) set(Brotli_INCLUDE_DIRS ${BROTLI_INCLUDE_DIR}) endif() libjxl-0.11.1/cmake/FindHWY.cmake000066400000000000000000000051231472134335300164160ustar00rootroot00000000000000# 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. find_package(PkgConfig QUIET) if (PkgConfig_FOUND) pkg_check_modules(PC_HWY QUIET libhwy) set(HWY_VERSION ${PC_HWY_VERSION}) endif () find_path(HWY_INCLUDE_DIR NAMES hwy/base.h hwy/highway.h HINTS ${PC_HWY_INCLUDEDIR} ${PC_HWY_INCLUDE_DIRS} ) find_library(HWY_LIBRARY NAMES ${HWY_NAMES} hwy 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) 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]+)" _sink "${HWY_VERSION_CONTENT}") set(HWY_VERSION_MAJOR "${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]+)" _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) find_package_handle_standard_args(HWY FOUND_VAR HWY_FOUND REQUIRED_VARS HWY_LIBRARY HWY_INCLUDE_DIR VERSION_VAR HWY_VERSION ) if (HWY_LIBRARY AND NOT TARGET hwy) add_library(hwy INTERFACE IMPORTED GLOBAL) if(CMAKE_VERSION VERSION_LESS "3.13.5") set_property(TARGET hwy PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${HWY_INCLUDE_DIR}) target_link_libraries(hwy INTERFACE ${HWY_LIBRARY}) set_property(TARGET hwy PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_HWY_CFLAGS_OTHER}) else() target_include_directories(hwy INTERFACE ${HWY_INCLUDE_DIR}) target_link_libraries(hwy INTERFACE ${HWY_LIBRARY}) target_link_options(hwy INTERFACE ${PC_HWY_LDFLAGS_OTHER}) target_compile_options(hwy INTERFACE ${PC_HWY_CFLAGS_OTHER}) endif() endif() mark_as_advanced(HWY_INCLUDE_DIR HWY_LIBRARY) if (HWY_FOUND) set(HWY_LIBRARIES ${HWY_LIBRARY}) set(HWY_INCLUDE_DIRS ${HWY_INCLUDE_DIR}) endif () libjxl-0.11.1/cmake/FindLCMS2.cmake000066400000000000000000000040171472134335300165700ustar00rootroot00000000000000# 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. find_package(PkgConfig QUIET) if (PkgConfig_FOUND) pkg_check_modules(PC_LCMS2 QUIET libLCMS2) set(LCMS2_VERSION ${PC_LCMS2_VERSION}) endif () find_path(LCMS2_INCLUDE_DIR NAMES lcms2.h HINTS ${PC_LCMS2_INCLUDEDIR} ${PC_LCMS2_INCLUDE_DIRS} ) find_library(LCMS2_LIBRARY NAMES ${LCMS2_NAMES} lcms2 liblcms2 lcms-2 liblcms-2 HINTS ${PC_LCMS2_LIBDIR} ${PC_LCMS2_LIBRARY_DIRS} ) if (LCMS2_INCLUDE_DIR AND NOT LCMS_VERSION) file(READ ${LCMS2_INCLUDE_DIR}/lcms2.h LCMS2_VERSION_CONTENT) string(REGEX MATCH "#define[ \t]+LCMS_VERSION[ \t]+([0-9]+)[ \t]*\n" LCMS2_VERSION_MATCH ${LCMS2_VERSION_CONTENT}) if (LCMS2_VERSION_MATCH) string(SUBSTRING ${CMAKE_MATCH_1} 0 1 LCMS2_VERSION_MAJOR) string(SUBSTRING ${CMAKE_MATCH_1} 1 2 LCMS2_VERSION_MINOR) set(LCMS2_VERSION "${LCMS2_VERSION_MAJOR}.${LCMS2_VERSION_MINOR}") endif () endif () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(LCMS2 FOUND_VAR LCMS2_FOUND REQUIRED_VARS LCMS2_LIBRARY LCMS2_INCLUDE_DIR VERSION_VAR LCMS2_VERSION ) if (LCMS2_LIBRARY AND NOT TARGET lcms2) add_library(lcms2 INTERFACE IMPORTED GLOBAL) if(CMAKE_VERSION VERSION_LESS "3.13.5") set_property(TARGET lcms2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${LCMS2_INCLUDE_DIR}) target_link_libraries(lcms2 INTERFACE ${LCMS2_LIBRARY}) set_property(TARGET lcms2 PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_LCMS2_CFLAGS_OTHER}) else() target_include_directories(lcms2 INTERFACE ${LCMS2_INCLUDE_DIR}) target_link_libraries(lcms2 INTERFACE ${LCMS2_LIBRARY}) target_link_options(lcms2 INTERFACE ${PC_LCMS2_LDFLAGS_OTHER}) target_compile_options(lcms2 INTERFACE ${PC_LCMS2_CFLAGS_OTHER}) endif() endif() mark_as_advanced(LCMS2_INCLUDE_DIR LCMS2_LIBRARY) if (LCMS2_FOUND) set(LCMS2_LIBRARIES ${LCMS2_LIBRARY}) set(LCMS2_INCLUDE_DIRS ${LCMS2_INCLUDE_DIR}) endif () libjxl-0.11.1/debian/000077500000000000000000000000001472134335300143055ustar00rootroot00000000000000libjxl-0.11.1/debian/changelog000066400000000000000000000062111472134335300161570ustar00rootroot00000000000000jpeg-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. -- JPEG XL Maintainers Mon, 08 Aug 2022 14:43:58 +0000 jpeg-xl (0.6) unstable; urgency=medium * Bump JPEG XL version to 0.6. -- JPEG XL Maintainers Fri, 10 Sep 2021 16:08:17 +0200 jpeg-xl (0.5.0) unstable; urgency=medium * Bump JPEG XL version to 0.5.0. -- JPEG XL Maintainers Thu, 12 Aug 2021 23:49:40 +0200 jpeg-xl (0.3.7) UNRELEASED; urgency=medium * Bump JPEG XL version to 0.3.7. -- Sami Boukortt Mon, 29 Mar 2021 12:14:20 +0200 jpeg-xl (0.3.6) UNRELEASED; urgency=medium * Bump JPEG XL version to 0.3.6. -- Sami Boukortt Thu, 25 Mar 2021 17:40:58 +0100 jpeg-xl (0.3.5) UNRELEASED; urgency=medium * Bump JPEG XL version to 0.3.5. -- Sami Boukortt Tue, 23 Mar 2021 15:20:44 +0100 jpeg-xl (0.3.4) UNRELEASED; urgency=medium * Bump JPEG XL version to 0.3.4. -- Sami Boukortt Tue, 16 Mar 2021 12:13:59 +0100 jpeg-xl (0.3.3) UNRELEASED; urgency=medium * Bump JPEG XL version to 0.3.3. -- Sami Boukortt Fri, 5 Mar 2021 19:15:26 +0100 jpeg-xl (0.3.2) UNRELEASED; urgency=medium * Bump JPEG XL version to 0.3.2. -- Alex Deymo Fri, 12 Feb 2021 21:00:12 +0100 jpeg-xl (0.3.1) UNRELEASED; urgency=medium * Bump JPEG XL version to 0.3.1. -- Alex Deymo Tue, 09 Feb 2021 09:48:43 +0100 jpeg-xl (0.3) UNRELEASED; urgency=medium * Bump JPEG XL version to 0.3. -- Alex Deymo Wed, 27 Jan 2021 22:36:32 +0100 jpeg-xl (0.2) UNRELEASED; urgency=medium * Bump JPEG XL version to 0.2. -- Alex Deymo Wed, 23 Nov 2020 20:42:10 +0100 jpeg-xl (0.1) UNRELEASED; urgency=medium * JPEG XL format release candidate. -- Alex Deymo Fri, 13 Nov 2020 17:42:24 +0100 jpeg-xl (0.0.2-1) UNRELEASED; urgency=medium * Initial debian package. -- Alex Deymo Tue, 27 Oct 2020 15:27:59 +0100 libjxl-0.11.1/debian/compat000066400000000000000000000000031472134335300155040ustar00rootroot0000000000000010 libjxl-0.11.1/debian/control000066400000000000000000000053771472134335300157240ustar00rootroot00000000000000Source: jpeg-xl Maintainer: JPEG XL Maintainers Section: misc Priority: optional Standards-Version: 3.9.8 Build-Depends: asciidoc, cmake, debhelper (>= 9), libbrotli-dev, libgdk-pixbuf-2.0-dev | libgdk-pixbuf2.0-dev, libgif-dev, libgimp2.0-dev, libgoogle-perftools-dev, libgtest-dev, libhwy-dev (>= 1.0.0), libjpeg-dev, libopenexr-dev, libpng-dev, libwebp-dev, pkg-config, xdg-utils, xmlto, Homepage: https://github.com/libjxl/libjxl Rules-Requires-Root: no Package: jxl Architecture: any Section: utils Depends: ${misc:Depends}, ${shlibs:Depends} Description: JPEG XL Image Coding System - "JXL" (command line utility) The JPEG XL Image Coding System (ISO/IEC 18181) is a lossy and lossless image compression format. It has a rich feature set and is particularly optimized for responsive web environments, so that content renders well on a wide range of devices. Moreover, it includes several features that help transition from the legacy JPEG format. . This package installs the command line utilities. Package: libjxl-dev Architecture: any Section: libdevel Depends: libjxl (= ${binary:Version}), ${misc:Depends} libhwy-dev, Description: JPEG XL Image Coding System - "JXL" (development files) The JPEG XL Image Coding System (ISO/IEC 18181) is a lossy and lossless image compression format. It has a rich feature set and is particularly optimized for responsive web environments, so that content renders well on a wide range of devices. Moreover, it includes several features that help transition from the legacy JPEG format. . This package installs development files. Package: libjxl Architecture: any Multi-Arch: same Section: libs Depends: ${shlibs:Depends}, ${misc:Depends} Pre-Depends: ${misc:Pre-Depends} Description: JPEG XL Image Coding System - "JXL" (shared libraries) The JPEG XL Image Coding System (ISO/IEC 18181) is a lossy and lossless image compression format. It has a rich feature set and is particularly optimized for responsive web environments, so that content renders well on a wide range of devices. Moreover, it includes several features that help transition from the legacy JPEG format. . This package installs shared libraries. Package: libjxl-gdk-pixbuf Architecture: any Multi-Arch: same Section: libs Depends: ${shlibs:Depends}, ${misc:Depends} Pre-Depends: ${misc:Pre-Depends} Description: JPEG XL Plugin for gdk-pixbuf This package installs the required files for reading JPEG XL files in GTK applications. Package: libjxl-gimp-plugin Architecture: any Multi-Arch: same Section: graphics Depends: ${shlibs:Depends}, ${misc:Depends} Pre-Depends: ${misc:Pre-Depends} Enhances: gimp Description: JPEG XL Import and Export Plugin for GIMP This is a plugin for GIMP version 2.10.x to import and export JPEG XL images. libjxl-0.11.1/debian/copyright000066400000000000000000000242451472134335300162470ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: jpeg-xl 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 Files: third_party/skcms/* Copyright: 2018 Google Inc. License: BSD-3-clause Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: . * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. . THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Files: testdata/external/pngsuite/* Copyright: Willem van Schaik, 1996, 2011 License: PngSuite License See http://www.schaik.com/pngsuite/ for details. . Permission to use, copy, modify and distribute these images for any purpose and without fee is hereby granted. Files: testdata/external/raw.pixls/* Copyright: their respective owners listed in https://raw.pixls.us/ License: CC0-1.0 Files: testdata/external/wesaturate/* Copyright: their respective owners listed in https://www.wesaturate.com/ License: CC0-1.0 Files: testdata/external/wide-gamut-tests/ Copyright: github.com/codelogic/wide-gamut-tests authors. License: Apache-2.0 License: Apache-2.0 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at . http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. . On Debian systems, the complete text of the Apache License, Version 2 can be found in "/usr/share/common-licenses/Apache-2.0". License: CC0 Creative Commons Zero v1.0 Universal . CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. . Statement of Purpose . The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). . Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. . For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. . 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. . 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. . 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. . 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. . For more information, please see: http://creativecommons.org/publicdomain/zero/1.0/> libjxl-0.11.1/debian/jxl.install000066400000000000000000000000761472134335300164750ustar00rootroot00000000000000usr/bin/* usr/share/man/man1/cjxl.1 usr/share/man/man1/djxl.1 libjxl-0.11.1/debian/libjxl-dev.install000066400000000000000000000000741472134335300177360ustar00rootroot00000000000000usr/include/jxl/*.h usr/lib/*/*.so usr/lib/*/pkgconfig/*.pc libjxl-0.11.1/debian/libjxl-gdk-pixbuf.install000066400000000000000000000001601472134335300212140ustar00rootroot00000000000000usr/lib/*/gdk-pixbuf-*/*/loaders/* usr/share/mime/packages/image-jxl.xml usr/share/thumbnailers/jxl.thumbnailer libjxl-0.11.1/debian/libjxl-gimp-plugin.install000066400000000000000000000000151472134335300214030ustar00rootroot00000000000000usr/lib/gimp libjxl-0.11.1/debian/libjxl.install000066400000000000000000000000271472134335300171600ustar00rootroot00000000000000usr/lib/*/libjxl*.so.* libjxl-0.11.1/debian/rules000077500000000000000000000012021472134335300153600ustar00rootroot00000000000000#!/usr/bin/make -f include /usr/share/dpkg/pkg-info.mk %: dh $@ --buildsystem=cmake 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_JPEGLI=OFF \ -DJPEGXL_ENABLE_JPEGLI_LIBJPEG=OFF \ -DJPEGXL_ENABLE_PLUGINS=ON libjxl-0.11.1/debian/source/000077500000000000000000000000001472134335300156055ustar00rootroot00000000000000libjxl-0.11.1/debian/source/format000066400000000000000000000000141472134335300170130ustar00rootroot000000000000003.0 (quilt) libjxl-0.11.1/deps.sh000077500000000000000000000060651472134335300143640ustar00rootroot00000000000000#!/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. # This file downloads the dependencies needed to build JPEG XL into third_party. # These dependencies are normally pulled by git. set -eu SELF=$(realpath "$0") MYDIR=$(dirname "${SELF}") # Git revisions we use for the given submodules. Update these whenever you # update a git submodule. 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() { local path="$1" local project="$2" local varname=`echo "$path" | tr '[:lower:]' '[:upper:]'` varname="${varname//[\/-]/_}" local sha eval "sha=\${${varname}}" local down_dir="${MYDIR}/downloads" local local_fn="${down_dir}/${sha}.tar.gz" if [[ -e "${local_fn}" && -d "${MYDIR}/${path}" ]]; then echo "${path} already up to date." >&2 return 0 fi local url local strip_components=0 if [[ "${project:0:4}" == "http" ]]; then # "project" is a googlesource.com base url. url="${project}${sha}.tar.gz" else # GitHub files have a top-level directory strip_components=1 url="https://github.com/${project}/tarball/${sha}" fi echo "Downloading ${path} version ${sha}..." >&2 mkdir -p "${down_dir}" curl -L --show-error -o "${local_fn}.tmp" "${url}" mkdir -p "${MYDIR}/${path}" tar -zxf "${local_fn}.tmp" -C "${MYDIR}/${path}" \ --strip-components="${strip_components}" mv "${local_fn}.tmp" "${local_fn}" } is_git_repository() { local dir="$1" local toplevel=$(git rev-parse --show-toplevel) [[ "${dir}" == "${toplevel}" ]] } main() { if is_git_repository "${MYDIR}"; then cat >&2 <> $EMSDK/.emscripten # 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). libjxl-0.11.1/doc/color_management.md000066400000000000000000000055021472134335300174660ustar00rootroot00000000000000# Color Management [TOC] ## Why The vast majority of web images are still sRGB. However, wide-gamut material is increasingly being produced (photography, cinema, 4K). Screens covering most of the Adobe RGB gamut are readily available and some also cover most of DCI P3 (iPhone, Pixel2) or even BT.2020. Currently, after a camera records a very saturated red pixel, most raw processors would clip it to the rather small sRGB gamut before saving as JPEG. In keeping with our high-quality goal, we prevent such loss by allowing wider input color spaces. ## Which color space Even wide gamuts could be expressed relative to the sRGB primaries, but the resulting coordinates may be outside the valid 0..1 range. Surprisingly, such 'unbounded' coordinates can be passed through color transforms provided the transfer functions are expressed as parametric functions (not lookup tables). However, most image file formats (including PNG and PNM) lack min/max metadata and thus do not support unbounded coordinates. Instead, we need a larger working gamut to ensure most pixel coordinates are within bounds and thus not clipped. However, larger gamuts result in lower precision/resolution when using <= 16 bit encodings (as opposed to 32-bit float in PFM). BT.2100 or P3 DCI appear to be good compromises. ## CMS library Transforms with unbounded pixels are desirable because they reduce round-trip error in tests. This requires parametric curves, which are only supported for the common sRGB case in ICC v4 profiles. ArgyllCMS does not support v4. The other popular open-source CMS is LittleCMS. It is also used by color-managed editors (Krita/darktable), which increases the chances of interoperability. However, LCMS has race conditions and overflow issues that prevent fuzzing. We will later switch to the newer skcms. Note that this library does not intend to support multiProcessElements, so HDR transfer functions cannot be represented accurately. Thus in the long term, we will probably migrate away from ICC profiles entirely. ## Which viewer On Linux, Krita and darktable support loading our PNG output images and their ICC profile. ## How to compress/decompress ### Embedded ICC profile - Create an 8-bit or 16-bit PNG with an iCCP chunk, e.g. using darktable. - Pass it to `cjxl`, then `djxl` with no special arguments. The decoded output will have the same bit depth (can override with `--output_bit_depth`) and color space. ### Images without metadata (e.g. HDR) - Create a PGM/PPM/PFM file in a known color space. - Invoke `cjxl` with `-x color_space=RGB_D65_202_Rel_Lin` (linear 2020). For details/possible values, see color_encoding.cc `Description`. - Invoke `djxl` as above with no special arguments. libjxl-0.11.1/doc/debugging_workflows.md000066400000000000000000000046111472134335300202240ustar00rootroot00000000000000### 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 ``` libjxl-0.11.1/doc/developing_in_debian.md000066400000000000000000000032211472134335300202740ustar00rootroot00000000000000# Developing in Debian These instructions assume an up-to-date Debian/Ubuntu system. For other platforms, please instead use the following: * [Cross Compiling for Windows with Crossroad](developing_with_crossroad.md). ## Minimum build dependencies Apart from the dependencies in `third_party`, some of the tools use external 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 \ graphviz ``` Make sure your default `clang` compiler is at least version 6 by running ```bash clang --version ``` If it still shows an old version despite having, for example, `clang-7` installed, you need to update the default `clang` compiler. On Debian-based systems run: ```bash sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-7 100 sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-7 100 ``` Optionally, to compile some of the extra tool support and tests you can install the following packages: ```bash 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: ```bash sudo apt install clang-format clang-tidy curl parallel gcovr ``` ## Building The `libjxl` project uses CMake to build. We provide a script that simplifies the invocation. To build and test the project, run ```bash ./ci.sh opt ``` This writes binaries to `build/tools` and runs unit tests. More information on [build modes and testing](building_and_testing.md) is available. libjxl-0.11.1/doc/developing_in_github.md000066400000000000000000000327271472134335300203510ustar00rootroot00000000000000# Developing in GitHub This document describes the development steps related to handling the git repository. If you are new to GitHub, there's a nice [quickstart guide](https://docs.github.com/en/github/getting-started-with-github/quickstart) on GitHub explaining the basics. ## Initial setup You need to perform this set up at least once if you haven't use GitHub before. Read through the quickstart guide [Set up Git](https://docs.github.com/en/github/getting-started-with-github/set-up-git) page to get your git up and running. You will need to Fork a repository next. 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 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. Create the `~/.ssh/id_rsa_github` file executing the following. (Here and elsewhere, {{X}} are placeholders for your email/username) ```bash ssh-keygen -t rsa -b 4096 -C "{{EMAIL}}" -f ~/.ssh/id_rsa_github ``` Go to your [SSH and GPG keys](https://github.com/settings/keys) settings and paste the contents of your *public key* (the one ending in `.pub`), that would be the output of this command: ```bash cat ~/.ssh/id_rsa_github.pub ``` To use a specific key when SSHing to the github.com domain, you can add this snippet of config to your .ssh/config file executing the following. ```bash cat >> ~/.ssh/config <github.com/*{{USERNAME}}*/libjxl where {{USERNAME}} denotes your GitHub username. ### Checkout the JPEG XL code from GitHub To get the source code on your computer you need to "clone" it. There are two repositories at play here, the upstream repository (`libjxl/lbjxl`) and your fork (`{{USERNAME}}/libjxl`). You will be normally fetching new changes from the upstream repository and push changes to your fork. Getting your changes from your fork to the upstream repository is done through the Web interface, via Pull Requests. The [Fork a repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) goes in great detail, but uses the git remote names `upstream` for the shared upstream repository and `origin` for your work. This guide proposes an alternative naming scheme, used in the examples below. In this guide `origin` is the upstream shared repository and `myfork` is your 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 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 git remote -vv ``` These commands did three things: * Created the repository with `origin` as the upstream remote, * Changed the "push" URL to point to your fork, and * Create a new remote pointing to your fork. The last step is optional. Since the "fetch" URL of `origin` points to the shared repository and the "push" URL points to your fork, fetching from `origin` always gets the latest changes from the upstream repository regardless of the contents of your fork. Having a second origin called `myfork` is only useful if you need to download pending changes from your fork from a different computer. For example, if you work on multiple computers, each one with this setup, you can push to your fork from one, and then fetch from `myfork` from another computer to get those. # Life of a Pull Request The general [GitHub flow guide](https://docs.github.com/en/github/getting-started-with-github/github-flow) applies to sending Pull Requests to this project. All the commands here assume you are in a git checkout as setup here. ### Sync to the latest version ```bash git fetch origin ``` The last upstream version is now on `origin/main` and none of your local branches have been modified by this command. ### Start a new branch To start a new change you need a local branch. Each branch will represent a list of individual commits which can then be requested to be merged as a single merge request. So in general one branch is one code review, but each branch can have multiple individual commits in it. ```bash git checkout origin/main -b mybranch ``` This will create a new branch `mybranch` tracking `origin/main`. A branch can track any remove or local branch, which is used by some tools. Running `git branch -vv` will show all the branches you have have, what are they tracking and how many commits are ahead or behind. If you create a branch without tracking any other, you can add or change the tracking branch of the current branch running `git branch --set-upstream-to=...`. ### Add changes to your branch Follow any of the many online tutorials, for example [The basics](https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository) chapter from the https://git-scm.com/doc website is a good starting guide. Create, change or delete files and do a git commit with a message. The commit message is required. A commit message should follow the 50/72 rule: * First line is 50 characters or less. * Then a blank line. * Remaining text should be wrapped at 72 characters. The first line should identify your commit, since that's what most tools will show to the user. First lines like "Some fixes" are not useful. Explain what the commit contains and why. We follow the [Google C++ Coding Style](https://google.github.io/styleguide/cppguide.html). A [clang-format](https://clang.llvm.org/docs/ClangFormat.html) configuration file is available to automatically format your code, you can invoke it with the `./ci.sh lint` helper tool. Read the [CONTRIBUTING.md](../CONTRIBUTING.md) file for more information about contributing to libjxl. ### Upload your changes for review The first step is a local review of your changes to see what will you be sending for review. `gitg` is a nice Gtk UI for reviewing your local changes, or `tig` for similar ncurses console-based interface. Otherwise, from the terminal you can run: ```bash git branch -vv ``` To show the current status of your local branches. In particular, since your branch is tracking origin/main (as seen in the output) git will tell you that you are one commit ahead of the tracking branch. ``` * mybranch e74ae1a [origin/main: ahead 1] Improved decoding speed by 40% ``` It is a good idea before uploading to sync again with upstream (`git fetch origin`) and then run `git branch -vv` to check whether there are new changes upstream. If that is the case, you will see a "behind" flag in the output: ``` * mybranch e74ae1a [origin/main: ahead 1, behind 2] Improved decoding speed by 40% ``` To sync your changes on top of the latest changes in upstream you need to rebase: ```bash git rebase ``` This will by default rebase your current branch changes on top of the tracking branch. In this case, this will try to apply the current commit on top of the latest origin/main (which has 2 more commits than the ones we have in our branch) and your branch will now include that. There could be conflicts that you have to deal with. A shortcut to do both fetch and rebase is to run `git pull -r`, where the `-r` stands for "rebase" and will rebase the local commits on top of the remote ones. Before uploading a patch, make sure your patch conforms to the [contributing guidelines](../CONTRIBUTING.md) and it [builds and passes tests](building_and_testing.md). Once you are ready to send your branch for review, upload it to *your* fork: ```bash 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 pull request will be displayed. ``` Enumerating objects: 627, done. Counting objects: 100% (627/627), done. Delta compression using up to 56 threads Compressing objects: 100% (388/388), done. Writing objects: 100% (389/389), 10.71 MiB | 8.34 MiB/s, done. Total 389 (delta 236), reused 0 (delta 0) emote: remote: Create a pull request for 'mybranch' on GitHub by visiting: remote: https://github.com/{{USERNAME}}/libjxl/pull/new/mybranch remote: To github.com:{{USERNAME}}/libjxl.git * [new branch] mybranch -> mybranch ``` ### Updating submodules The repository uses submodules for external library dependencies in third_party. Each submodule points to a particular external commit of the external repository by the hash code of that external commit. Just like regular source code files, this hash code is part of the current branch and jpeg xl commit you have checked out. When changing branches or when doing `git rebase`, git will unfortunately *not* automatically set those hashes to the ones of the branch or jpeg xl commit you changed to nor set the source files of the third_party submodules to the new state. That is, even though git will have updated the jpeg xl source code files on your disk to the new ones, it will leave the submodule hashes and the files in third_party in your workspace to the ones they were before you changed branches. This will show up in a git diff because this is seen as a change compared to the branch you switched to. The git diff shows the difference in hash codes (as if you are changing to the old ones), it does not show changes in files inside the third_party directory. This mismatch can cause at least two problems: *) the jpeg xl codebase may not compile due to third_party library version mismatch if e.g. API changed or a submodule was added/removed. *) when using `commit -a` your commit, which may be a technical change unrelated to submodule changes, will unintentionally contain a change to the submodules hash code, which is undesired unless you actually want to change the version of third_party libraries. To resolve this, the submodules must be updated manually with the following command after those actions (at least when the submodules changed): ``` git submodule update --init --recursive ``` Here, the init flag ensures new modules get added when encessary and the recursive flag is required for the submodules depending on other submodules. If you checkout a different branch, you can spot that submodules changed when it shows a message similar to this: ``` M third_party/brotli M third_party/lcms ``` If you do a rebase you may end up in a harder to solve situation, where `git submodule update --init --recursive` itself fails with errors such as: ``` Unable to checkout '35ef5c554d888bef217d449346067de05e269b30' in submodule path 'third_party/brotli' ``` In that case, you can use the force flag: ``` git submodule update --init --recursive --force ``` ### 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 --amend file1 file2 file3 ...` or `git commit --amend -a` to amend all the changes from all the staged files. Once you have the new version of the "mybranch" branch to re-upload, you need to force push it to the same branch in your fork. Since you are pushing a different version of the same commit (as opposed to another commit on top of the existing ones), you need to force the operation to replace the old version. ```bash git push origin mybranch --force ``` The pull request should now be updated with the new changes. ### Merging your changes We use "rebase" as a merge policy, which means that there a no "merge" commits (commits with more than one parent) but instead only a linear history of changes. It is possible that other changes where added to the main branch since the last time you rebased your changes. These changes could create a conflict with your Pull Request, if so you need to `git fetch`, `git rebase` and push again your changes which need to go through the continuous integration workflow again to 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 pull request commit with the following command, replacing `NNNN` with the pull request number: ```bash git fetch origin refs/pull/NNNN/head git checkout FETCH_HEAD ``` The first command will add to your local git repository the remote commit for the pending pull request and store a temporary reference called `FETCH_HEAD`. The second command then checks out that reference. From this point you can review the files in your computer, create a local branch for this FETCH_HEAD or build on top of it. libjxl-0.11.1/doc/developing_in_windows_msys.md000066400000000000000000000201551472134335300216240ustar00rootroot00000000000000# Developing for Windows with MSYS2 [MSYS2](https://www.msys2.org/) ("minimal system 2") is a software distribution and a development platform based on MinGW and Cygwin. It provides a Unix-like environment to build code on Windows. These instructions were written with a 64-bit instance of Windows 10 running on a VM. They may also work on native instances of Windows and other versions of Windows. ## Build Environments MSYS2 provides multiple development [environments](https://www.msys2.org/docs/environments/). By convention, they are referred to in uppercase. They target slightly different platforms, runtime libraries, and compiler toolchains. For example, to build for 32-bit Windows, use the MINGW32 environment. For interoperability with Visual Studio projects, use the UCRT64 environment. Since all of the build environments are built on top of the MSYS environment, **all updates and package installation must be done from within the MSYS environment**. After making any package changes, `exit` all MSYS2 terminals and restart the desired build-environment. This reminder is repeated multiple times throughout this guide. * **MINGW32:** To compile for 32-bit Windows (on 64-bit Windows), use packages from the `mingw32` group. Package names are prefixed with `mingw-w64-i686`. The naming scheme may be different on the 32-bit version of MSYS2. * **MINGW64:** This is the primary environment to building for 64-bit Windows. It uses the older MSVCRT runtime, which is widely available across Windows systems. Package names are prefixed with `mingw-w64-x86_64`. * **UCRT64:** The Universal C Runtime (UCRT) is used by recent versions of Microsoft Visual Studio. It ships by default with Windows 10. For older versions of Windows, it must be provided with the application or installed by the user. Package names are prefixed with `mingw-w64-ucrt-x86_64`. * **CLANG64:** Unfortunately, the `gimp` packages are not available for the CLANG64 environment. However, `libjxl` will otherwise build in this environment if the appropriate packages are installed. Packages are prefixed with `mingw-w64-clang-x86_64`. ## Install and Upgrade MSYS2 Download MSYS2 from the homepage. Install at a location without any spaces on a drive with ample free space. After installing the packages used in this guide, MSYS2 used about 15GB of space. Toward the end of installation, select the option to run MSYS2 now. A command-line window will open. Run the following command, and answer the prompts to update the repository and close the terminal. ```bash pacman -Syu ``` Now restart the MSYS environment and run the following command to complete updates: ```bash pacman -Su ``` ## Package Management Packages are organized in groups, which share the build environment name, but in lower case. Then they have name prefixes that indicate which group they belong to. Consider this package search: `pacman -Ss cmake` ``` mingw32/mingw-w64-i686-cmake mingw64/mingw-w64-x86_64-cmake ucrt64/mingw-w64-ucrt-x86_64-cmake clang64/mingw-w64-clang-x86_64-cmake msys/cmake ``` We can see the organization `group/prefix-name`. When installing packages, the group name is optional. ```bash pacman -S mingw-w64-x86_64-cmake ``` For tools that need to be aware of the compiler to function, install the package that corresponds with the specific build-environment you plan to use. For `cmake`, install the `mingw64` version. The generic `msys/cmake` will not function correctly because it will not find the compiler. For other tools, the generic `msys` version is adequate, like `msys/git`. To remove packages, use: ```bash pacman -Rsc [package-name] ``` ## Worst-Case Scenario... If packages management is done within a build environment other than MSYS, the environment structure will be disrupted and compilation will likely fail. If this happens, it may be necessary to reinstall MSYS2. 1. Rename the `msys64` folder to `msys64.bak`. 2. Use the installer to reinstall MSYS2 to `msys64`. 3. Copy packages from `msys64.bak/var/cache/pacman/pkg/` to the new installation to save download time and bandwidth. 4. Use `pacman` from within the MSYS environment to install and update packages. 5. After successfully building a project, it is safe to delete `msys64.bak` ## The MING64 Environment Next set up the MING64 environment. The following commands should be run within the MSYS environment. `pacman -S` is used to install packages. The `--needed` argument prevents packages from being reinstalled. ```bash pacman -S --needed base-devel mingw-w64-x86_64-toolchain pacman -S git mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja \ mingw-w64-x86_64-gtest mingw-w64-x86_64-giflib \ mingw-w64-x86_64-libpng mingw-w64-x86_64-libjpeg-turbo ``` ## Build `libjxl` Download the source from the libjxl [releases](https://github.com/libjxl/libjxl/releases) page. Alternatively, you may obtain the latest development version with `git`. Run `./deps.sh` to ensure additional third-party dependencies are downloaded. Start the MINGW64 environment, create a build directory within the source directory, and configure with `cmake`. ```bash mkdir build cd build cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF \ -DJPEGXL_ENABLE_BENCHMARK=OFF -DJPEGXL_ENABLE_PLUGINS=ON \ -DJPEGXL_ENABLE_MANPAGES=OFF -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DJPEGXL_FORCE_SYSTEM_GTEST=ON .. ``` Check the output to see if any dependencies were missed and need to be installed. Adding `-G Ninja` may be helpful, but on my computer, Ninja was selected by default. Remember that package changes must be done from the MSYS environment. Then exit all MSYS2 terminals and restart the build environment. If all went well, you may now run `cmake` to build `libjxl`: ```bash cmake --build . ``` Do not be alarmed by the compiler warnings. They are a caused by differences between gcc/g++ and clang. The build should complete successfully. Then `cjxl`, `djxl`, `jxlinfo`, and others can be run from within the build environment. Moving them into the native Windows environment requires resolving `dll` issues that are beyond the scope of this document. ## The `clang` Compiler To use the `clang` compiler, install the packages that correspond with the environment you wish to use. Remember to make package changes from within the MSYS environment. ``` mingw-w64-i686-clang mingw-w64-i686-clang-tools-extra mingw-w64-i686-clang-compiler-rt mingw-w64-x86_64-clang mingw-w64-x86_64-clang-tools-extra mingw-w64-x86_64-clang-compiler-rt mingw-w64-ucrt64-x86_64-clang mingw-w64-ucrt64-x86_64-clang-tools-extra mingw-w64-ucrt64-x86_64-clang-compiler-rt ``` After the `clang` compiler is installed, 'libjxl' can be built with the `./ci.sh` script. ```bash ./ci.sh opt -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF \ -DJPEGXL_ENABLE_BENCHMARK=OFF -DJPEGXL_ENABLE_MANPAGES=OFF \ -DJPEGXL_FORCE_SYSTEM_BROTLI=ON -DJPEGXL_FORCE_SYSTEM_GTEST=ON ``` On my computer, `doxygen` packages needed to be installed to proceed with building. Use `pacman -Ss doxygen` to find the packages to install. ## The GIMP Plugin To build the GIMP plugin, install the relevant `gimp` package. This will also install dependencies. Again, perform package management tasks from only the MSYS environment. Then restart the build environment. ```bash pacman -S mingw-w64-i686-gimp pacman -S mingw-w64-x86_64-gimp pacman -S mingw-w64-ucrt-x86_64-gimp ``` If `clang` is installed, you can use the `./ci.sh` script to build. Otherwise, navigate to the build directory to reconfigure and build with `cmake`. ```bash cd build rm -r CM* cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF \ -DJPEGXL_ENABLE_BENCHMARK=OFF -DJPEGXL_ENABLE_MANPAGES=OFF \ -DJPEGXL_ENABLE_PLUGINS=ON -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DJPEGXL_FORCE_SYSTEM_GTEST=ON .. ``` The plugin is built statically, so there should be no need to install `dll` files. To try out the plugin: 1. [Download](https://www.gimp.org/downloads/) and install the stable version of GIMP (currently 2.10.24). 2. Create a new folder: `C:\Program Files\GIMP 2\lib\gimp\2.0\plug-ins\file-jxl` 3. Copy `build/plugins/gimp/file-jxl.exe` to the new folder. libjxl-0.11.1/doc/developing_in_windows_vcpkg.md000066400000000000000000000054411472134335300217440ustar00rootroot00000000000000# Developing on Windows with Visual Studio 2019 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: * [MSYS2 on Windows](developing_in_windows_msys.md) * [Crossroad on Linux](developing_with_crossroad.md) (cross compilation for Windows) ## Minimum build dependencies Apart from the dependencies in third_party, some of the tools use external dependencies that need to be installed in your system first. Please install [vcpkg](https://vcpkg.readthedocs.io/en/latest/examples/installing-and-using-packages/) (tested with version 2019.07.18), and use it to install the following libraries: ``` vcpkg install gtest:x64-windows vcpkg install giflib:x64-windows vcpkg install libjpeg-turbo:x64-windows vcpkg install libpng:x64-windows vcpkg install zlib:x64-windows ``` ## Building From Visual Studio, open the CMakeLists.txt in the JPEG XL root directory. Right-click the CMakeLists.txt entry in the Folder View of the Solution Explorer. In the context menu, select CMake Settings. Click on the green plus to add an x64-Clang configuration and the red minus to remove any non-Clang configuration (the MSVC compiler is currently not supported). Click on the blue hyperlink marked "CMakeSettings.json" and an editor will open. Insert the following text after replacing $VCPKG with the directory where you installed vcpkg above. ``` { "configurations": [ { "name": "x64-Clang-Release", "generator": "Ninja", "configurationType": "MinSizeRel", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "-DCMAKE_TOOLCHAIN_FILE=$VCPKG/scripts/buildsystems/vcpkg.cmake", "buildCommandArgs": "-v", "ctestCommandArgs": "", "inheritEnvironments": [ "clang_cl_x64" ], "variables": [ { "name": "VCPKG_TARGET_TRIPLET", "value": "x64-windows", "type": "STRING" }, { "name": "JPEGXL_ENABLE_TCMALLOC", "value": "False", "type": "BOOL" }, { "name": "gtest_force_shared_crt", "value": "True", "type": "BOOL" }, { "name": "JPEGXL_ENABLE_FUZZERS", "value": "False", "type": "BOOL" }, { "name": "JPEGXL_ENABLE_VIEWERS", "value": "False", "type": "BOOL" } ] } ] } ``` The project is now ready for use. To build, simply press F7 (or choose Build All from the Build menu). This writes binaries to `out/build/x64-Clang-Release/tools`. The main [README.md](../README.md) explains how to use the encoder/decoder and benchmark binaries. libjxl-0.11.1/doc/developing_with_crossroad.md000066400000000000000000000076141472134335300214300ustar00rootroot00000000000000# Cross Compiling for Windows with Crossroad [Crossroad](https://pypi.org/project/crossroad/) is a tool to set up cross-compilation environments on GNU/Linux distributions. These instructions assume a Debian/Ubuntu system. However, they can likely be adapted to other Linux environments. Since Ubuntu can be run on Windows through WSL, these instruction may be useful for developing directly on Windows. ## Install Crossroad Crossroad requires tools included with `python3-docutils` and `mingw-w64`. They may be installed using: ```bash sudo aptitude install python3-docutils mingw-w64 ``` The `zstandard` python package is also required, but is not available in the repositories. It may be installed using `pip`. ```bash pip3 install zstandard ``` After the dependencies are installed, crossroad itself maybe installed with `pip`. ```bash pip3 install crossroad ``` If there are errors while running crossroad, it may need to be downloaded and installed directly using `setup.py`. Instructions are on the crossroad homepage. ## Update Debian Alternatives Since `libjxl` uses C++ features that require posix threads, the symlinks used by the Debian alternative system need to be updated: ```bash sudo update-alternatives --config x86_64-w64-mingw32-g++ ``` Select the option that indicates `posix` usage. Repeat for `gcc` and `i686`: ```bash sudo update-alternatives --config x86_64-w64-mingw32-gcc sudo update-alternatives --config i686-w64-mingw32-gcc sudo update-alternatives --config i686-w64-mingw32-g++ ``` ## Create a New Crossroad Project Crossroad supports the following platforms: ``` native Native platform (x86_64 GNU/Linux) android-x86 Generic Android/Bionic on x86 android-mips64 Generic Android/Bionic on MIPS64 android-x86-64 Generic Android/Bionic on x86-64 w64 Windows 64-bit w32 Windows 32-bit android-arm64 Generic Android/Bionic on ARM64 android-mips Generic Android/Bionic on MIPS android-arm Generic Android/Bionic on ARM ``` To begin cross compiling for Windows, a new project needs to be created: ```bash crossroad w64 [project-name] ``` ## Install Dependencies Since the `gimp` development package is required to build the GIMP plugin and also includes most of the packages required by `libjxl`, install it first. ```bash crossroad install gimp ``` `gtest` and `brotli` are also required. ```bash crossroad install gtest brotli ``` If any packages are later found to be missing, you may search for them using: ```bash crossroad search [...] ``` ## Build `libjxl` Download the source from the libjxl [releases](https://github.com/libjxl/libjxl/releases) page. Alternatively, you may obtain the latest development version with `git`. Run `./deps.sh` to ensure additional third-party dependencies are downloaded. Unfortunately, the script `./ci.sh` does not work with Crossroad, so `cmake` will need to be called directly. Create a build directory within the source directory. If you haven't already, start your crossroad project and run `cmake`: ```bash mkdir build cd build crossroad w64 libjxl crossroad cmake -DCMAKE_BUILD_TYPE=Release \ -DBUILD_TESTING=OFF -DBUILD_SHARED_LIBS=OFF \ -DJPEGXL_ENABLE_BENCHMARK=OFF -DJPEGXL_ENABLE_MANPAGES=OFF \ -DJPEGXL_ENABLE_PLUGINS=ON -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DJPEGXL_FORCE_SYSTEM_GTEST=ON .. ``` Check the output to see if any dependencies were missed and need to be installed. If all went well, you may now run `cmake` to build `libjxl`: ```bash cmake --build . ``` ## Try out the GIMP Plugin The plugin is built statically, so there should be no need to install `dll` files. To try out the plugin: 1. [Download](https://www.gimp.org/downloads/) and install the stable version of GIMP (currently 2.10.24). 2. Create a new folder: `C:\Program Files\GIMP 2\lib\gimp\2.0\plug-ins\file-jxl` 3. Copy `build/plugins/gimp/file-jxl.exe` to the new folder. libjxl-0.11.1/doc/encode_effort.md000066400000000000000000000055711472134335300167640ustar00rootroot00000000000000# 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. libjxl-0.11.1/doc/format_overview.md000066400000000000000000000324711472134335300173770ustar00rootroot00000000000000# JPEG XL Format Overview This document gives an overview of the JPEG XL file format and codestream, its features, and the underlying design rationale. The aim of this document is to provide general insight into the format capabilities and design, thus helping developers better understand how to use the `libjxl` API. ## Codestream and File Format The JPEG XL format is defined in ISO/IEC 18181. This standard consists of four parts: * 18181-1: Core codestream * 18181-2: File format * 18181-3: Conformance testing * 18181-4: Reference implementation ### Core codestream The core codestream contains all the data necessary to decode and display still image or animation data. This includes basic metadata like image dimensions, the pixel data itself, colorspace information, orientation, upsampling, etc. ### File format The JPEG XL file format can take two forms: * A 'naked' codestream. In this case, only the image/animation data itself is stored, and no additional metadata can be included. Such a file starts with the bytes `0xFF0A` (the JPEG marker for "start of JPEG XL codestream"). * An ISOBMFF-based container. This is a box-based container that includes a JPEG XL codestream box (`jxlc`), and can optionally include other boxes with additional information, such as Exif metadata. In this case, the file starts with the bytes `0x0000000C 4A584C20 0D0A870A`. ### Conformance testing This part of the standard defines precision bounds and test cases for conforming decoders, to verify that they implement all coding tools correctly and accurately. ### Reference implementation The `libjxl` software is the reference implementation of JPEG XL. ## Metadata versus Image Data JPEG XL makes a clear separation between metadata and image data. Everything that is needed to correctly display an image is considered to be image data, and is part of the core codestream. This includes elements that have traditionally been considered 'metadata', such as ICC profiles and Exif orientation. The goal is to reduce the ambiguity and potential for incorrect implementations that can be caused by having a 'black box' codestream that only contains numerical pixel data, requiring applications to figure out how to correctly interpret the data (i.e. apply color transforms, upsampling, orientation, blending, cropping, etc.). By including this functionality in the codestream itself, the decoder can provide output in a normalized way (e.g. in RGBA, orientation already applied, frames blended and coalesced), simplifying things and making it less error-prone for applications. The remaining metadata, e.g. Exif or XMP, can be stored in the container format, but it does not influence image rendering. In the case of Exif orientation, this field has to be ignored by applications, since the orientation in the codestream always takes precedence (and will already have been applied transparently by the decoder). This means that stripping metadata can be done without affecting the displayed image. ## Codestream Features ### Color Management In JPEG XL, images always have a fully defined colorspace, i.e. it is always unambiguous how to interpret the pixel values. There are two options: * Pixel data is in a specified (non-XYB) colorspace, and the decoder will produce a pixel buffer in this colorspace plus an ICC profile that describes that colorspace. Mathematically lossless encoding can only use this option. * Pixel data is in the XYB colorspace, which is an absolute colorspace. In this case, the decoder can produce a pixel buffer directly in a desired display space like sRGB, Display-P3 or Rec.2100 PQ. The image header always contains a colorspace; however, its meaning depends on which of the above two options were used: * In the first case (non-XYB), the signaled colorspace defines the interpretation of the pixel data. * In the second case (XYB), the signaled colorspace is merely a _suggestion_ of a target colorspace to represent the image in, i.e. it is the colorspace the original image was in, that has a sufficiently wide gamut and a suitable transfer curve to represent the image data with high fidelity using a limited bit depth representation. Colorspaces can be signaled in two ways in JPEG XL: * CICP-style Enum values: This is a very compact representation that covers most or all of the common colorspaces. The decoder can convert XYB to any of these colorspaces without requiring an external color management library. * ICC profiles: Arbitrary ICC profiles can also be used, including CMYK ones. The ICC profile data gets compressed. In this case, external color management software (e.g. lcms2 or skcms) has to be used for color conversions. ### Frames A JPEG XL codestream contains one or more frames. In the case of animation, these frames have a duration and can be looped (infinitely or a number of times). Zero-duration frames are possible and represent different layers of the image. Frames can have a blendmode (Replace, Add, Alpha-blend, Multiply, etc.) and they can use any previous frame as a base. They can be smaller than the image canvas, in which case the pixels outside the crop are copied from the base frame. They can be positioned at an arbitrary offset from the image canvas; this offset can also be negative and frames can also be larger than the image canvas, in which case parts of the frame will be invisible and only the intersection with the image canvas will be shown. By default, the decoder will blend and coalesce frames, producing only a single output frame when there are subsequent zero-duration frames, and all output frames are of the same size (the size of the image canvas) and have either no duration (in case of a still image) or a non-zero duration (in case of animation). ### Pixel Data Every frame contains pixel data encoded in one of two modes: * VarDCT mode: In this mode, variable-sized DCT transforms are applied and the image data is encoded in the form of DCT coefficients. This mode is always lossy, but it can also be used to losslessly represent an existing (already lossy) JPEG image, in which case only the DCT8x8 is used. * Modular mode: In this mode, only integer arithmetic is used, which enables lossless compression. However, this mode can also be used for lossy compression. Multiple transformations can be used to improve compression or to obtain other desirable effects: reversible color transforms (RCTs), (delta) palette transforms, and a modified non-linear Haar transform called Squeeze, which facilitates (but does not require) lossy compression and enables progressive decoding. Internally, the VarDCT mode uses Modular sub-bitstreams to encode various auxiliary images, such as the "LF image" (a 1:8 downscaled version of the image that contains the DC coefficients of DCT8x8 and low-frequency coefficients of the larger DCT transforms), extra channels besides the three color channels (e.g. alpha), and weights for adaptive quantization. In addition, both modes can separately encode additional 'image features' that are rendered on top of the decoded image: * Patches: rectangles from a previously decoded frame (which can be a 'hidden' frame that is not displayed but only stored to be referenced later) can be blended using one of the blendmodes on top of the current frame. This allows the encoder to identify repeating patterns (such as letters of text) and encode them only once, using patches to insert the pattern in multiple spots. These patterns are encoded in a previous frame, making it possible to add Modular-encoded pixels to a VarDCT-encoded frame or vice versa. * Splines: centripetal Catmull-Rom splines can be encoded, with a color and a thickness that can vary along the arclength of the curve. Although the current encoder does not use this bitstream feature yet, we anticipate that it can be useful to complement DCT-encoded data, since thin lines are hard to represent faithfully using the DCT. * Noise: luma-modulated synthetic noise can be added to an image, e.g. to emulate photon noise, in a way that avoids poor compression due to high frequency DCT coefficients. Finally, both modes can also optionally apply two filtering methods to the decoded image, which both have the goal of reducing block artifacts and ringing: * Gabor-like transform ('Gaborish'): a small (3x3) blur that gets applied across block and group boundaries, reducing blockiness. The encoder applies the inverse sharpening transform before encoding, effectively getting the benefits of lapped transforms without the disadvantages. * Edge-preserving filter ('EPF'): similar to a bilateral filter, this smoothing filter avoids blurring edges while reducing ringing. The strength of this filter is signaled and can locally be adapted. ### Groups In both modes (Modular and VarDCT), the frame data is signaled as a sequence of groups. These groups can be decoded independently, and the frame header contains a table of contents (TOC) with bitstream offsets for the start of each group. This enables parallel decoding, and also partial decoding of a region of interest or a progressive preview. In VarDCT mode, all groups have dimensions 256x256 (or smaller at the right and bottom borders). First the LF image is encoded, also in 256x256 groups (corresponding to 2048x2048 pixels, since this data corresponds to the 1:8 image). This means there is always a basic progressive preview available in VarDCT mode. Optionally, the LF image can be encoded separately in a (hidden) LF frame, which can itself recursively be encoded in VarDCT mode and have its own LF frame. This makes it possible to represent huge images while still having an overall preview that can be efficiently decoded. Then the HF groups are encoded, corresponding to the remaining AC coefficients. The HF groups can be encoded in multiple passes for more progressive refinement steps; the coefficients of all passes are added. Unlike JPEG progressive scan scripts, JPEG XL allows signaling any amount of detail in any part of the image in any pass. In Modular mode, groups can have dimensions 128x128, 256x256, 512x512 or 1024x1024. If the Squeeze transform was used, the data will be split in three parts: the Global groups (the top of the Laplacian pyramid that fits in a single group), the LF groups (the middle part of the Laplacian pyramid that corresponds to the data needed to reconstruct the 1:8 image) and the HF groups (the base of the Laplacian pyramid), where the HF groups are again possibly encoded in multiple passes (up to three: one for the 1:4 image, one for the 1:2 image, and one for the 1:1 image). In case of a VarDCT image with extra channels (e.g. alpha), the VarDCT groups and the Modular groups are interleaved in order to allow progressive previews of all the channels. The default group order is to encode the LF and HF groups in scanline order (top to bottom, left to right), but this order can be permuted arbitrarily. This allows, for example, a center-first ordering or a saliency-based ordering, causing the bitstream to prioritize progressive refinements in a different way. ## File Format Features Besides the image data itself (stored in the `jxlc` codestream box), the optional container format allows storing additional information. ## Metadata Three types of metadata can be included in a JPEG XL container: * Exif (`Exif`) * XMP (`xml `) * JUMBF (`jumb`) This metadata can contain information about the image, such as copyright notices, GPS coordinates, camera settings, etc. If it contains rendering-impacting information (such as Exif orientation), the information in the codestream takes precedence. ## Compressed Metadata The container allows the above metadata to be stored either uncompressed (e.g. plaintext XML in the case of XMP) or by Brotli-compression. In the latter case, the box type is `brob` (Brotli-compressed Box) and the first four bytes of the box contents define the actual box type (e.g. `xml `) it represents. ## JPEG Bitstream Reconstruction Data JPEG XL can losslessly recompress existing JPEG files. The general design philosophy still applies in this case: all the image data is stored in the codestream box, including the DCT coefficients of the original JPEG image and possibly an ICC profile or Exif orientation. In order to allow bit-identical reconstruction of the original JPEG file (not just the image but the actual file), additional information is needed, since the same image data can be encoded in multiple ways as a JPEG file. The `jbrd` box (JPEG Bitstream Reconstruction Data) contains this information. Typically it is relatively small. Using the image data from the codestream, the JPEG bitstream reconstruction data, and possibly other metadata boxes that were present in the JPEG file (Exif/XMP/JUMBF), the exact original JPEG file can be reconstructed. This box is not needed to display a recompressed JPEG image; it is only needed to reconstruct the original JPEG file. ## Frame Index The container can optionally store a `jxli` box, which contains an index of offsets to keyframes of a JPEG XL animation. It is not needed to display the animation, but it does facilitate efficient seeking. ## Partial Codestream The codestream can optionally be split into multiple `jxlp` boxes; conceptually, this is equivalent to a single `jxlc` box that contains the concatenation of all partial codestream boxes. This makes it possible to create a file that starts with the data needed for a progressive preview of the image, followed by metadata, followed by the remaining image data. libjxl-0.11.1/doc/fuzzing.md000066400000000000000000000210641472134335300156510ustar00rootroot00000000000000# Fuzzing Fuzzing is a technique to find potential bugs by providing randomly generated invalid inputs. To detect potential bugs such as programming errors we use fuzzing in combination with ASan (Address Sanitizer), MSan (Memory Sanitizer), UBSan (Undefined Behavior Sanitizer) and asserts in the code. An invalid input will likely produce a decoding error (some API function returning error), which is absolutely not a problem, but what it should not do is access memory out of bounds, use uninitialized memory or hit a false assert condition. ## Automated Fuzzing with oss-fuzz libjxl fuzzing is integrated into [oss-fuzz](https://github.com/google/oss-fuzz) as the project `libjxl`. oss-fuzz regularly runs the fuzzers on the `main` branch and reports bugs into their bug tracker which remains private until the bugs are fixed in main. ## Fuzzer targets There are several fuzzer executable targets defined in the `tools/` directory to fuzz different parts of the code. The main one is `djxl_fuzzer`, which uses the public C decoder API to attempt to decode an image. The fuzzer input is not directly the .jxl file, the last few bytes of the fuzzer input are used to decide *how* will the API be used (if preview is requested, the pixel format requested, if the .jxl input data is provided altogether, etc) and the rest of the fuzzer input is provided as the .jxl file to the decoder. Some bugs might reproduce only if the .jxl input is decoded in certain way. The remaining fuzzer targets execute a specific portion the codec that might be easier to fuzz independently from the whole codec. ## Reproducing fuzzer bugs A fuzzer target, like `djxl_fuzzer` accepts as a parameter one or more files that will be used as inputs. This runs the fuzzer program in test-only mode where no new inputs are generated and only the provided files are tested. This is the easiest way to reproduce a bug found by the fuzzer using the generated test case from the bug report. oss-fuzz uses a specific compiler version and flags, and it is built using Docker. Different compiler versions will have different support for detecting certain actions as errors, so we want to reproduce the build from oss-fuzz as close as possible. To reproduce the build as generated by oss-fuzz there are a few helper commands in `ci.sh` as explained below. ### Generate the gcr.io/oss-fuzz/libjxl image First you need the ossfuzz libjxl builder image. This is the base oss-fuzz builder image with a few dependencies installed. To generate it you need to check out the oss-fuzz project and build it: ```bash git clone https://github.com/google/oss-fuzz.git ~/oss-fuzz cd ~/oss-fuzz sudo infra/helper.py build_image libjxl ``` This will create the `gcr.io/oss-fuzz/libjxl` docker image. You can check if it was created verifying that it is listed in the output of the `sudo docker image ls` command. ### Build the fuzzer targets with oss-fuzz To build the fuzzer targets from the current libjxl source checkout, use the `./ci.sh ossfuzz_msan` command for MSan, `./ci.sh ossfuzz_asan` command for ASan or `./ci.sh ossfuzz_ubsan` command for UBSan. All the `JXL_ASSERT` and `JXL_DASSERT` calls are enabled in all the three modes. These ci.sh helpers will reproduce the oss-fuzz docker call to build libjxl mounting the current source directory into the Docker container. Ideally you will run this command in a different build directory separated from your regular builds. For example, for MSan builds run: ```bash BUILD_DIR=build-fuzzmsan ./ci.sh ossfuzz_msan ``` After this, the fuzzer program will be generated in the build directory like for other build modes: `build-fuzzmsan/tools/djxl_fuzzer`. ### Iterating changes with oss-fuzz builds After modifying the source code to fix the fuzzer-found bug, or to include more debug information, you can rebuild only a specific fuzzer target to save on rebuilding time and immediately run the test case again. For example, for rebuilding and testing only `djxl_fuzzer` in MSan mode we can run: ```bash BUILD_DIR=build-fuzzmsan ./ci.sh ossfuzz_msan djxl_fuzzer && build-fuzzmsan/tools/djxl_fuzzer path/to/testcase.bin ``` When MSan and ASan fuzzers fail they will print a stack trace at the point where the error occurred, and some related information. To make these these stack traces useful we need to convert the addresses to function names and source file names and lines, which is done with the "symbolizer". For UBSan to print a stack trace we need to set the `UBSAN_OPTIONS` environment variables when running the fuzzer. Set the following environment variables when testing the fuzzer binaries. Here `clang` should match the compiler version used by the container, you can pass a different compiler version in the following example by first installing the 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) export MSAN_SYMBOLIZER_PATH="${symbolizer}" export UBSAN_SYMBOLIZER_PATH="${symbolizer}" export ASAN_SYMBOLIZER_PATH="${symbolizer}" export ASAN_OPTIONS=detect_leaks=1 export UBSAN_OPTIONS=print_stacktrace=1 ``` Note: The symbolizer binary must be a program called `llvm-symbolizer`, any other file name will fail. There are normally symlinks already installed with the right name which the `-print-prog-name` would print. ## Running the fuzzers locally Running the fuzzer targets in fuzzing mode can be achieved by running them with no parameters, or better with a parameter with the path to a *directory* containing a seed of files to use as a starting point. Note that passing a directory is considered a corpus to use for fuzzing while passing a file is considered an input to evaluate. Multi-process fuzzing is also supported. For details about all the fuzzing options run: ```bash build-fuzzmsan/tools/djxl_fuzzer -help=1 ``` ## Writing fuzzer-friendly code Fuzzing on itself can't find programming bugs unless an input makes the program 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_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 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. * For MSan builds it is also possible to specify that certain values must be initialized. This is automatic for values that are used to make decisions (like when used in an `if` statement or in the ternary operator condition) but those checks can be made explicit for image data using the `JXL_CHECK_IMAGE_INITIALIZED(image, rect)` macro. This helps document and check (only in MSan builds) that a given portion of the image is expected to be initialized, allowing to catch errors earlier in the process. ## Dealing with use-of-uninitialized memory In MSan builds it is considered an error to *use* uninitialized memory. Using the memory normally requires something like a decision / branch based on the uninitialized value, just running `memcpy()` or simple arithmetic over uninitialized memory is not a problem. Notably, computing `DemoteTo()`, `NearestInt()` or similar expressions that create a branch based on the value of the uninitialized memory will trigger an MSan error. In libjxl we often run vectorized operations over a series of values, rounding up to the next multiple of a vector size, thus operating over uninitialized values past the end of the requested region. These values are part of the image padding but are not initialized. This behavior would not create an MSan error unless the processing includes operations like `NearestInt()`. For such cases the preferred solution is to use `msan::UnpoisonMemory` over the portion of memory of the last SIMD vector before processing, and then running `msan::PoisonMemory` over the corresponding value in the output side. A note including why this is safe to do must be added, for example if the processing doesn't involve any cross-lane computation. Initializing padding memory in MSan builds is discouraged because it may hide bugs in functions that weren't supposed to read from the padding. Initializing padding memory in all builds, including Release builds, would mitigate the MSan potential security issue but it would hide the logic bug for a longer time and potentially incur in a performance hit. libjxl-0.11.1/doc/jxl.svg000066400000000000000000000023151472134335300151470ustar00rootroot00000000000000libjxl-0.11.1/doc/man/000077500000000000000000000000001472134335300144035ustar00rootroot00000000000000libjxl-0.11.1/doc/man/cjxl.txt000066400000000000000000000054731472134335300161150ustar00rootroot00000000000000cjxl(1) ======= :doctype: manpage Name ---- cjxl - compress images to JPEG XL Synopsis -------- *cjxl* ['options'...] 'input' ['output.jxl'] Description ----------- `cjxl` compresses an image or animation to the JPEG XL format. It is intended to spare users the trouble of determining a set of optimal parameters for each individual image. Instead, for a given target quality, it should provide consistent visual results across various kinds of images. The defaults have been chosen to be sensible, so that the following commands should give satisfactory results in most cases: ---- cjxl input.png output.jxl cjxl input.jpg output.jxl cjxl input.gif output.jxl ---- Options ------- -h:: --help:: Displays the options that `cjxl` supports. On its own, it will only show basic options. It can be combined with `-v` or `-v -v` to show increasingly advanced options as well. -v:: --verbose:: Increases verbosity. Can be repeated to increase it further, and also applies to `--help`. -d 'distance':: --distance='distance':: The preferred way to specify quality. It is specified in multiples of a just-noticeable difference. That is, `-d 0` is mathematically lossless, `-d 1` should be visually lossless, and higher distances yield denser and denser files with lower and lower fidelity. Lossy sources such as JPEG and GIF files are compressed losslessly by default, and in the case of JPEG files specifically, the original JPEG can then be reconstructed bit-for-bit. For lossless sources, `-d 1` is the default. -q 'quality':: --quality='quality':: Alternative way to indicate the desired quality. 100 is lossless and lower values yield smaller files. There is no lower bound to this quality parameter, but positive values should approximately match the quality setting of libjpeg. -e 'effort':: --effort='effort':: Controls the amount of effort that goes into producing an ``optimal'' file in terms of quality/size. That is to say, all other parameters being equal, a higher effort should yield a file that is at least as dense and possibly denser, and with at least as high and possibly higher quality. + Recognized effort settings, from fastest to slowest, are: + - 1 or ``lightning'' - 2 or ``thunder'' - 3 or ``falcon'' - 4 or ``cheetah'' - 5 or ``hare'' - 6 or ``wombat'' - 7 or ``squirrel'' (default) - 8 or ``kitten'' - 9 or ``tortoise'' Examples -------- ---- # Compress a PNG file to a high-quality JPEG XL version. $ cjxl input.png output.jxl # Compress it at a slightly lower quality, appropriate for web use. $ cjxl -d 2 input.png output.jxl # Compress it losslessly. These are equivalent. $ cjxl -d 0 input.png lossless.jxl $ cjxl -q 100 input.png lossless.jxl # Compress a JPEG file losslessly. $ cjxl input.jpeg lossless-jpeg.jxl ---- See also -------- *djxl*(1) libjxl-0.11.1/doc/man/djxl.txt000066400000000000000000000023741472134335300161130ustar00rootroot00000000000000djxl(1) ======= :doctype: manpage Name ---- djxl - decompress JPEG XL images Synopsis -------- *djxl* ['options'...] 'input.jxl' ['output'] Description ----------- `djxl` decompresses a JPEG XL image or animation. The output format is determined by the extension of the output file, which can be `.png`, `.jpg`, `.ppm`, `.pfm`. If the JPEG XL input file contains an animation, multiple output files will be produced, with names of the form "'output'-*framenumber*.ext". Options ------- -h:: --help:: Displays the options that `djxl` supports. -j:: --pixels_to_jpeg:: By default, if the input JPEG XL contains a recompressed JPEG file, djxl reconstructs the exact original JPEG file if the output file has the `.jpg` (or `.jpeg`) filename extension. This flag causes the decoder to instead decode the image to pixels and encode a new (lossy) JPEG in this case. -q 'quality':: --jpeg_quality='quality':: When decoding to `.jpg`, use this output quality. This option implicitly enables the --pixels_to_jpeg option. Examples -------- ---- # Decompress a JPEG XL file to PNG $ djxl input.jxl output.png # Reconstruct a losslessly-recompressed JPEG file $ djxl lossless-jpeg.jxl reconstructed.jpeg ---- See also -------- *cjxl*(1) libjxl-0.11.1/doc/release.md000066400000000000000000000370741472134335300156050ustar00rootroot00000000000000# libjxl release process This guide documents the release process for the libjxl project. libjxl follows the [semantic versioning](https://semver.org/spec/v2.0.0.html) specification for released versions. Releases are distributed as tags in the git repository with the semantic version prefixed by the letter "v". For example, release version "0.3.7" will have a git tag "v0.3.7". The public API is explicitly defined as C headers in the `lib/include` directory, normally installed in your include path. All other headers are internal API and are not covered by the versioning rules. ## Development and release workflow New code development is performed on the `main` branch of the git repository. Pre-submit checks enforce minimum build and test requirements for new patches that balance impact and test latency, but not all checks are performed before pull requests are merged. Several slower checks only run *after* the code has been merged to `main`, resulting in some errors being detected hours after the code is merged or even days after in the case of fuzzer-detected bugs. Release tags are cut from *release branches*. Each MAJOR.MINOR version has its own release branch, for example releases `0.7.0`, `0.7.1`, `0.7.2`, ... would have tags `v0.7.0`, `v0.7.1`, `v0.7.2`, ... on commits from the `v0.7.x` branch. `v0.7.x` is a branch name, not a tag name, and doesn't represent a released version since semantic versioning requires that the PATCH is a non-negative 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 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 new features. New features often come with new bugs which take time to fix, so having a release branch allows us to cherry-pick *bug fixes* from the `main` branch into the release branch without including the new *features* from `main`. For this reason it is important to make small commits in `main` and separate bug fixes from new features. After the initial minor release (`MAJOR.MINOR.PATCH`, for example `0.5.0`) the release branch is used to continue to cherry-pick fixes to be included in a patch release, for example a version `0.5.1` release. Patch fixes are only meant to fix security bugs or other critical bugs that can't wait until the next major or minor release. Release branches *may* continue to be maintained even after the next minor or major version has been released to support users that can't update to a newer minor release. In that case, the same process applies to all the maintained release branches. A release branch with specific cherry-picks from `main` means that the release code is actually a version of the code that never existed in the `main` branch, so it needs to be tested independently. Pre-submit and post-submit tests run on release branches (branches matching `v*.*.x`) but extra manual checks should be performed before a release, specially if multiple bug fixes interact with each other. Take this into account when selecting which commits to include in a release. The objective is to have a stable version that can be used without problems for months. Having the latest improvements at the time the release tag is created is a non-goal. ## Creating a release branch A new release branch is needed before creating a new major or minor release, that is, a new release where the MAJOR or MINOR numbers are increased. Patch releases, where only the PATCH number is increased, reuse the branch from the previous release of the same MAJOR and MINOR numbers. The following instructions assume that you followed the recommended [libjxl git setup](developing_in_github.md) where `origin` points to the upstream libjxl/libjxl project, otherwise use the name of your upstream remote repository instead of `origin`. The release branch is normally created from the latest work in `main` at the time the branch is created, but it is possible to create the branch from an older commit if the current `main` is particularly unstable or includes commits that were not intended to be included in the release. The following example creates the branch `v0.5.x` from the latest commit in main (`origin/main`), if a different commit is to be used then replace `origin/main` with the SHA of that commit. Change the `v0.5.x` branch name to the one you are creating. ```bash git fetch origin main git push git@github.com:libjxl/libjxl.git origin/main:refs/heads/v0.5.x ``` Here we use the SSH URL explicitly since you are pushing to the `libjxl/libjxl` project directly to a branch there. If you followed the guide `origin` will have the HTTPS URL which wouldn't normally let you push since you wouldn't be authenticated. The `v*.*.x` branches are [GitHub protected branches](https://docs.github.com/en/github/administering-a-repository/defining-the-mergeability-of-pull-requests/about-protected-branches) 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 We use GitHub labels in Pull Requests to keep track of the changes that should be merged into a given release branch. For this purpose create a new label for each new MAJOR.MINOR release branch called `merge-MAJOR.MINOR`, for example, `merge-0.5`. In the [edit labels](https://github.com/libjxl/libjxl/issues/labels) page, click on "New label" and create the label. Pick your favorite color. Labels are a GitHub-only concept and are not represented in git. You can add the label to a Pull Request even after it was merged, whenever it is decided that the Pull Request should be included in the given release branch. Adding the label doesn't automatically merge it to the release branch. ## Update the versioning number The version number (as returned by `JxlDecoderVersion`) in the source code in `main` must match the semantic versioning of a release. After the release branch is created the code in `main` will only be included in the next major or minor release. Right after a release branch update the version targeting the next release. Artifacts from `main` should include the new (unreleased) version, so it is important to update it. For example, after the `v0.5.x` branch is created from main, you should update the version on `main` to `0.6.0`. To help update it, run this helper command (in a Debian-based system): ```bash ./ci.sh bump_version 0.6.0 ``` This will update the version in the following files: * `lib/CMakeLists.txt` * `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. * `.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 After a Pull Request that should be included in a release branch has been merged to `main` it can be cherry-picked to the release branch. Before cherry-picking a change to a release branch it is important to check that it doesn't introduce more problems, in particular it should run for some time in `main` to make sure post-submit tests and the fuzzers run on it. Waiting for a day is a good idea. Most of the testing is done on the `main` branch, so be careful with what commits are cherry-picked to a branch. Refactoring code is often not a good candidate to cherry-pick. To cherry-pick a single commit to a release branch (in this example to `v0.5.x`) you can run: ```bash git fetch origin git checkout origin/v0.5.x -b merge_to_release git cherry-pick -x SHA_OF_MAIN_COMMIT # -x will annotate the cherry-pick with the original SHA_OF_MAIN_COMMIT value. # If not already mentioned in the original commit, add the original PR number to # the commit, for example add "(cherry picked from PR #NNNN)". git commit --amend ``` The `SHA_OF_MAIN_COMMIT` is the hash of the commit as it landed in main. Use `git log origin/main` to list the recent main commits and their hashes. Making sure that the commit message on the cherry-picked commit contains a reference to the original pull request (like `#NNNN`) is important. It creates an automatic comment in the original pull request notifying that it was mentioned in another commit, helping keep track of the merged pull requests. If the original commit was merged with the "Squash and merge" policy it will automatically contain the pull request number on the first line, if this is not the case you can amend the commit message of the cherry-pick to include a reference. Multiple commits can be cherry-picked and tested at once to save time. Continue running `git cherry-pick` and `git commit --amend` multiple times for all the 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: ```bash git push myfork merge_to_release ``` If you used the [guide](developing_in_github.md) `myfork` would be `origin` in that example. Click on the URL displayed, which will be something like `https://github.com/mygithubusername/libjxl/pull/new/merge_to_release` In the "Open a pull request" page, change the drop-down base branch from "base: main" (the default) to the release branch you are targeting. The pull request approval and pre-submit rules apply as with normal pull requests to the `main` branch. **Important:** When merging multiple cherry-picks use "Rebase and merge" policy, not the squash one since otherwise you would discard the individual commit message references from the git history in the release branch. ## Publishing a release Once a release tag is created it must not be modified, so you need to prepare the changes before creating the release. Make sure you checked the following: * The semantic version number in the release branch (see `lib/CMakeLists.txt`) matches the number you intend to release, all three MAJOR, MINOR and PATCH should match. Otherwise send a pull request to the release branch to update them. * The GitHub Actions checks pass on the release branch. Look for the green tick next to the last commit on the release branch. This should be visible on the branch page, for example: https://github.com/libjxl/libjxl/tree/v0.5.x * There no open fuzzer-found bugs for the release branch. The most effective way is to [run the fuzzer](fuzzing.md) on the release branch for a while. You can seed the fuzzer with corpus generated by oss-fuzz by [downloading it](https://google.github.io/oss-fuzz/advanced-topics/corpora/#downloading-the-corpus), for example `djxl_fuzzer` with libFuzzer will use: gs://libjxl-corpus.clusterfuzz-external.appspot.com/libFuzzer/libjxl_djxl_fuzzer * Manually check that images encode/decode ok. * Manually check that downstream projects compile with our code. Sometimes bugs on build scripts are only detected when other projects try to use our library. For example, test compiling [imagemagick](https://github.com/ImageMagick/ImageMagick) and Chrome. A [GitHub "release"](https://docs.github.com/en/github/administering-a-repository/releasing-projects-on-github/about-releases) consists of two different concepts: * a git "tag": this is a name (`v` plus the semantic version number) with a commit hash associated, defined in the git repository. Most external projects will use git tags or HTTP URLs to these tags to fetch the code. * a GitHub "release": this is a GitHub-only concept and is not represented in git other than by having a git tag associated with the release. A GitHub release has a given source code commit SHA associated (through the tag) but it *also* contains release notes and optional binary files attached to the release. Releases from the older GitLab repository only have a git tag in GitHub, while newer releases have both a git tag and a release entry in GitHub. To publish a release open the [New Release page](https://github.com/libjxl/libjxl/releases/new) and follow these instructions: * Set the "Tag version" as "v" plus the semantic version number. * Select the "Target" as your release branch. For example for a "v0.7.1" release tag you should use the "v0.7.x" branch. * Use the version number as the release title. * Copy-paste the relevant section of the [CHANGELOG.md](../CHANGELOG.md) to the release notes into the release notes. Add any other information pertaining 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 ``` libjxl-0.11.1/doc/software_support.md000066400000000000000000000133001472134335300175750ustar00rootroot00000000000000# JPEG XL software support This document attempts to keep track of software that is using libjxl to support JPEG XL. This list serves several purposes: - thank/acknowledge other projects for integrating jxl support - point end-users to software that can read/write jxl - keep track of the adoption status of jxl - in case of a (security) bug in libjxl, it's easier to see who might be affected and check if they are updated (in case they use static linking) Please add missing software to this list. ## Browsers - 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: 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. - 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://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 - Qt / KDE: [plugin available](https://github.com/novomesk/qt-jpegxl-image-plugin) - GDK-pixbuf: plugin available in libjxl repo - [gThumb](https://ubuntuhandbook.org/index.php/2021/04/gthumb-3-11-3-adds-jpeg-xl-support/) - [MacOS viewer/QuickLook plugin](https://github.com/yllan/JXLook) - [Windows Imaging Component](https://github.com/mirillis/jpegxl-wic) - [Windows thumbnail handler](https://github.com/saschanaz/jxl-winthumb) - [OpenMandriva Lx (since 4.3 RC)](https://www.openmandriva.org/en/news/article/openmandriva-lx-4-3-rc-available-for-testing) - [KaOS (since 2021.06)](https://news.itsfoss.com/kaos-2021-06-release/) - [EFL (since 1.27, no external plugin needed)](https://www.enlightenment.org) ## 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 - [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 ## 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 libjxl-0.11.1/doc/sphinx/000077500000000000000000000000001472134335300151415ustar00rootroot00000000000000libjxl-0.11.1/doc/sphinx/api.rst000066400000000000000000000004741472134335300164510ustar00rootroot00000000000000libjxl API reference ==================== ``libjxl`` exposes a C API for encoding and decoding JPEG XL files with some C++ header-only helpers for C++ users. .. toctree:: :caption: API REFERENCE :maxdepth: 2 api_decoder api_encoder api_common api_metadata api_color api_threads api_cpp libjxl-0.11.1/doc/sphinx/api_color.rst000066400000000000000000000001761472134335300176460ustar00rootroot00000000000000Color encoding and conversion ============================= .. doxygengroup:: libjxl_color :members: :private-members: libjxl-0.11.1/doc/sphinx/api_common.rst000066400000000000000000000001531472134335300200130ustar00rootroot00000000000000Common API concepts =================== .. doxygengroup:: libjxl_common :members: :private-members: libjxl-0.11.1/doc/sphinx/api_cpp.rst000066400000000000000000000001301472134335300173000ustar00rootroot00000000000000C++ helpers =========== .. doxygengroup:: libjxl_cpp :members: :private-members: libjxl-0.11.1/doc/sphinx/api_decoder.rst000066400000000000000000000002021472134335300201230ustar00rootroot00000000000000Decoder API - ``jxl/decode.h`` ============================== .. doxygengroup:: libjxl_decoder :members: :private-members: libjxl-0.11.1/doc/sphinx/api_encoder.rst000066400000000000000000000002021472134335300201350ustar00rootroot00000000000000Encoder API - ``jxl/encode.h`` ============================== .. doxygengroup:: libjxl_encoder :members: :private-members: libjxl-0.11.1/doc/sphinx/api_metadata.rst000066400000000000000000000001671472134335300203100ustar00rootroot00000000000000Image and frame metadata ======================== .. doxygengroup:: libjxl_metadata :members: :private-members: libjxl-0.11.1/doc/sphinx/api_threads.rst000066400000000000000000000002021472134335300201500ustar00rootroot00000000000000Multi-threaded Encoder/Decoder ============================== .. doxygengroup:: libjxl_threads :members: :private-members: libjxl-0.11.1/doc/sphinx/conf.py000066400000000000000000000062231472134335300164430ustar00rootroot00000000000000# 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. # Configuration file for the Sphinx documentation builder. # # See https://www.sphinx-doc.org/en/master/usage/configuration.html import os import re import subprocess def GetVersion(): """Function to get the version of the current code.""" with open(os.path.join( os.path.dirname(__file__), '../../lib/CMakeLists.txt'), 'r') as f: cmakevars = {} for line in f: m = re.match(r'set\(JPEGXL_([A-Z]+)_VERSION ([^\)]+)\)', line) if m: cmakevars[m.group(1)] = m.group(2) return '%s.%s.%s' % (cmakevars['MAJOR'], cmakevars['MINOR'], cmakevars['PATCH']) def ConfigProject(app, config): # Configure the doxygen xml directory as the "xml" directory next to the # sphinx output directory. Doxygen generates by default the xml files in a # "xml" sub-directory of the OUTPUT_DIRECTORY. build_dir = os.path.dirname(app.outdir) xml_dir = os.path.join(build_dir, 'xml') config.breathe_projects['libjxl'] = xml_dir # Read the docs build environment doesn't run our cmake script so instead we # need to run doxygen manually here. if os.environ.get('READTHEDOCS', None) != 'True': return root_dir = os.path.realpath(os.path.join(app.srcdir, '../../')) doxyfile = os.path.join(build_dir, 'Doxyfile-rtd.doc') with open(doxyfile, 'w') as f: f.write(f""" FILE_PATTERNS = *.c *.h GENERATE_HTML = NO GENERATE_LATEX = NO GENERATE_XML = YES INPUT = lib/include doc/api.txt OUTPUT_DIRECTORY = {build_dir} PROJECT_NAME = LIBJXL QUIET = YES RECURSIVE = YES STRIP_FROM_PATH = lib/include WARN_AS_ERROR = YES """) subprocess.check_call(['doxygen', doxyfile], cwd=root_dir) def setup(app): # Generate doxygen XML on init when running from Read the docs. app.connect("config-inited", ConfigProject) ### Project information project = 'libjxl' project_copyright = 'JPEG XL Project Authors' author = 'JPEG XL Project Authors' version = GetVersion() ### General configuration extensions = [ # For integration with doxygen documentation. 'breathe', # sphinx readthedocs theme. 'sphinx_rtd_theme', # Do we use it? 'sphinx.ext.graphviz', ] breathe_default_project = 'libjxl' breathe_projects = {} # All the API is in C, except those files that end with cxx.h. breathe_domain_by_extension = {'h': 'cpp'} breathe_domain_by_file_pattern = { '*cxx.h': 'cpp', } breathe_implementation_filename_extensions = ['.cc'] # These are defined at build time by cmake. c_id_attributes = [ 'JXL_EXPORT', 'JXL_DEPRECATED', 'JXL_THREADS_EXPORT', ] cpp_id_attributes = c_id_attributes breathe_projects_source = { 'libjxl' : ('../../', [ 'doc/api.txt', 'lib/include/jxl', ]) } # Recognized suffixes. source_suffix = ['.rst', '.md'] ### Options for HTML output # Use the readthedocs.io theme when generating the HTML output. html_theme = 'sphinx_rtd_theme' libjxl-0.11.1/doc/sphinx/index.rst000066400000000000000000000004201472134335300167760ustar00rootroot00000000000000.. libjxl sphinx documentation entrypoint JPEG XL image format reference implementation ============================================= .. toctree:: :maxdepth: 3 :caption: Contents: api Indices and tables ================== * :ref:`genindex` * :ref:`search` libjxl-0.11.1/doc/sphinx/requirements.txt000066400000000000000000000000401472134335300204170ustar00rootroot00000000000000breathe sphinx sphinx-rtd-theme libjxl-0.11.1/doc/vuln_playbook.md000066400000000000000000000257111472134335300170440ustar00rootroot00000000000000# Security Vulnerabilities Playbook ## Reporting security bugs Report security bugs by emailing libjxl-security@google.com. Don't open a GitHub issue, don't discuss it public forums like Discord and don't send a Pull Request if you think you have found a security bug. ## Overview This document outlines the guidelines followed by the project when handling security bugs, their fixes, disclosure and coordination with security researchers. For more context about this guide, read the [coordinated vulnerability disclosure guidelines](https://github.com/google/oss-vulnerability-guide/blob/main/guide.md) from Google Open Source Programs Office. The main target audience of this guide is the coordinator from the libjxl Vulnerability Management Team (VMT) handling the requests, however it is useful for other people to understand what to expect from this process. Members of the VMT monitor the reports received by email and will coordinate for these to be addressed. This doesn't mean that said member would fix the bug, but their responsibility is to make sure it is handled properly according to this guide. ## Life of security bug The Coordinator from VMT will make sure that the following steps are taken. 1. Acknowledge the bug report. Our policy mandates a maximum of **3 business days** to respond to bug reports in the given email, but you should respond as soon as possible and keep a fluid communication with the reporter, who has spent some time looking at the issue. 2. Determine if the bug is a security bug covered by our policy. Not all bugs are security bugs, and not all security bugs are covered by this vulnerability disclosure policy. See the [What's a Security bug] section below. 3. Determine the affected versions. Often new bugs on stable projects are found on new features or because of those new features, so only the most recent versions are affected. It is important to determine both what older versions are affected, so users running those older versions can patch or update the software, and also what older versions are *not* affected. It is possible that stable distributions ship older versions that didn't contain the bug and therefore don't need to patch the code. Often maintainers of package distributions need to patch older versions instead of updating due to incompatibilities with newer ones and they need to understand what's the vulnerable code. Security bugs that have already been fixed in `main` or in already released code but not disclosed as a vulnerability, for example if fixed as a result of a refactor, should be treated like any other security bug in this policy and disclosed indicating the range of older affected versions (expect for versions before 0.5, see below). In such case a new release would likely not be needed if one already exists, but stable distributions may be still using those version and need to be aware of the issue and fix. If no released version is affected by the bug, for example because it was only introduced in the `main` branch but not yet released, then no vulnerability disclosure is needed. Note: Versions before 0.5 are not covered by the security policy. Those versions have multiple security issues and should not be used anyway. 4. Communicate with the reporter Communicate the decision to the reporter. If the bug was not considered a security bug or not covered by this policy, explain why and direct the reporter to open a public [issue in GitHub](https://github.com/libjxl/libjxl/issues) or open one on their behalf. You don't need to follow the rest of the guide in this case. If the bug *is* a covered security bug then follow the rest of this guide. Ask the reporter how they want to be credited in the disclosure: name and company affiliation if any. Security researchers often value this recognition and helps them dedicate their time to finding security bugs in our project. There's no bug bounty (monetary compensation for security bugs) available for libjxl. 5. Create a Security Advisory draft in GitHub At this point it was established that the bug is a security issue that requires a vulnerability disclosure. Start by creating a Security Advisory draft in the [Security Advisories](https://github.com/libjxl/libjxl/security/advisories) page in GitHub. Add a short description of the bug explaining what's the issue and what's the impact of the issue. Being 'hard' or 'complex' to exploit is not a reason to discard the potential impact. You can update this description later, save it as a draft in GitHub. Add the reporter to the security advisory draft if they have a GitHub account, and add the project members that will be working on a fix for the bug. Establish the severity of the issue according to the impact and tag the appropriate Common Weakness Enumeration (CWE) values. This helps classify the security issues according to their nature. 6. Work on a fix in a private branch Coordinators can work on the fix themselves, use a proposed fix from the reporter if there is one, or work with other project members to create one. Work on a fix for the bug in *private*. Don't publish a Pull Request with the fix like you normally do, and don't upload the fix to your libjxl fork. If you ask another project member to work on it, explain them that they should follow this guide. 7. Request a CVE number The Common Vulnerabilities and Exposures (CVE) is the system used to disclose vulnerabilities in software. A CVE number, like CVE-2021-NNNNNN, is a unique identifier for a given vulnerability. These numbers are assigned by a CVE Numbering Authority (CNA) with scope on the given project that has the vulnerability. For libjxl, we use Google's Generic CNA. For VMT coordinators at Google, file a bug at [go/cve-request](https://goto.google.com/cve-request) to request a CVE. See go/vcp-cna for context. When requesting the CVE include: * A description of the problem (example: bug when parsing this field) * A description of the impact of the bug (example: OOB read, remote code execution, etc) * The proposed CWE id(s) determined earlier. * List of affected versions. * Reporter of the bug and their preferred name/company to include in the disclosure. * Links to the issues/fixes (if already public), these can be added later, even after the CVE is public. * The CPE prefix of the affected project (`cpe:2.3:a:libjxl_project:libjxl`) When in doubt, you can discuss these with the security team while requesting it. 8. File a Security bug in Chromium (if affected). libjxl project is in charge of updating and maintaining Chromium's libjxl integration code, this includes updating the libjxl library when needed. While the regular CVE disclosure process will eventually create a bug to update Chromium, filing one at this stage speeds up the process. [go/crbug](https://goto.google.com/crbug), select the "Security Bug" template and complete the details. This bug will be used to keep track of what versions of Chromium need backporting. The new bug in Chromium will not be public initially, but will be made public some time after the issue is fixed. 9. Test the fixes on the intended releases When disclosing a vulnerability normally two ways to fix it are offered: * A patch or set of patches that fix the issue on `main` branch, and * A new release that contains the security fix for the user to update to. New releases that fix the vulnerability should be PATCH releases, that is, a previous release (like 1.2.3) plus the patches that fix the vulnerability, becoming a new version (like 1.2.4). See the [release process](release.md) for details. At least the latest MINOR release branch should have a PATCH release with the fix, however it might make sense to also backport the fix to older minor branch releases, depending on long-term support schedule for certain releases. For example, if many users are still using a particular older version of the library and updating to a new version requires significant changes (due to a redesigned API or new unavailable dependencies) it is helpful to provide a PATCH release there too. In either case, make sure that you test the fix in all the branches that you intend to release it to. The Continuous Integration pipelines don't work on the private forks created by the Security Advisory, so manual testing of the fix is needed there before making it public. Don't upload it to your public fork for testing. 10. Coordinate a date for release of the vulnerability disclosure. Agree with the reporter and security folks from the CNA on a release date. There is a maximum of 90 day disclosure timeline from the day the bug was reported. On the disclosure date publish the fixes and tag the new PATCH release with the fix. You can prepare private drafts of the release for review beforehand to reduce the workload. Update Chromium to the new release version (if affected) and work with Chrome engineers on the required backports. ## What's a Security bug 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_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 users of the project, to let them know that there is a security issue and that they should update or patch it. Unreleased versions are not relevant in this context. A bug introduced in the `main` branch that is not yet in any release is not covered by this guide even if the bug allows a remote code execution. CVEs should have a non-empty list of affected released versions. "Developer only" code is also not covered by this policy. In particular, tools that are not installed by the build, or not installed when packaging `libjxl` are not covered. For example, a bug in `tone_map` would not affect users since is a developer-only tool. The rationale behind this is that users of the 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 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 same process that it shouldn't have access to, even a small OOB read can allow the attacker to read an address in the stack or in the heap, defeating address space randomization techniques. In combination with other bugs these can enable or simplify attacks to the process using libjxl. OOB reads don't need to require a segmentation fault to be a problem, leaking process information in decoded RGB pixels could be used as part of an exploit in some scenarios. OOB writes and remote code execution (RCE) are security bugs of at least high security impact. libjxl-0.11.1/doc/xl_overview.md000066400000000000000000000153011472134335300165230ustar00rootroot00000000000000# XL Overview ## Requirements JPEG XL was designed for two main requirements: * high quality: visually lossless at reasonable bitrates; * decoding speed: multithreaded decoding should be able to reach around 400 Megapixel/s on large images. These goals apply to various types of images, including HDR content, whose support is made possible by full-precision (float32) computations and extensive support of color spaces and transfer functions. High performance is achieved by designing the format with careful consideration of memory bandwidth usage and ease of SIMD/GPU implementation. The full requirements for JPEG XL are listed in document wg1m82079. ## General architecture The architecture follows the traditional block transform model with improvements in the individual components. For a quick overview, we sketch a "block diagram" of the lossy format decoder in the form of module names in **bold** followed by a brief description. Note that post-processing modules in [brackets] are optional - they are unnecessary or even counterproductive at very high quality settings. **Header**: decode metadata (e.g. image dimensions) from compressed fields (smaller than Exp-Golomb thanks to per-field encodings). The compression and small number of required fields enables very compact headers - much smaller than JFIF and HEVC. The container supports multiple images (e.g. animations/bursts) and passes (progressive). **Bitstream**: decode transform coefficient residuals using rANS-encoded <#bits,bits> symbols **Dequantize**: from adaptive quant map side information, plus chroma from luma **DC prediction**: expand DC residuals using adaptive (history-based) predictors **Chroma from luma**: restore predicted X from B and Y from B **IDCT:** 2x2..32x32, floating-point **[Gaborish]**: additional deblocking convolution with 3x3 kernel **[Edge preserving filter]**: nonlinear adaptive smoothing controlled by side information **[Noise injection]**: add perceptually pleasing noise according to a per-image noise model **Color space conversion**: from perceptual opsin XYB to linear RGB **[Converting to other color spaces via ICC]** The encoder is basically the reverse: **Color space conversion**: from linear RGB to perceptual opsin XYB **[Noise estimation]**: compute a noise model for the image **[Gaborish]**: sharpening to counteract the blurring on the decoder side **DCT**: transform sizes communicated via per-block side information **Chroma from luma**: find the best multipliers of Y for X and B channels of entire image **Adaptive quantization**: iterative search for quant map that yields the best perceived restoration **Quantize**: store 16-bit prediction residuals **DC prediction**: store residuals (prediction happens in quantized space) **Entropy coding**: rANS and context modeling with clustering # File Structure A codestream begins with a `FileHeader` followed by one or more "passes" (= scans: e.g. DC or AC_LF) which are then added together (summing the respective color components in Opsin space) to form the final image. There is no limit to the number of passes, so an encoder could choose to send salient parts first, followed by arbitrary decompositions of the final image (in terms of resolution, bit depth, quality or spatial location). Each pass contains groups of AC and DC data. A group is a subset of pixels that can be decoded in parallel. DC groups contain 256x256 DCs (from 2048x2048 input pixels), AC groups cover 256x256 input pixels. Each pass starts with a table of contents (sizes of each of their DC+AC groups), which enables parallel decoding and/or the decoding of a subset. However, there is no higher-level TOC of passes, as that would prevent appending additional images and could be too constraining for the encoder. ## Lossless JPEG XL supports tools for lossless coding designed by Alexander Rhatushnyak and Jon Sneyers. They are about 60-75% of size of PNG, and smaller than WebP lossless for photos. An adaptive predictor computes 4 from the NW, N, NE and W pixels and combines them with weights based on previous errors. The error value is encoded in a bucket chosen based on a heuristic max error. The result is entropy-coded using the ANS encoder. ## Current Reference Implementation ### Conventions The software is written in C++ and built using CMake 3.6 or later. Error handling is done by having functions return values of type `jxl::Status` (a thin wrapper around bool which checks that it is not ignored). A convenience macro named `JXL_RETURN_IF_ERROR` makes this more convenient by automatically forwarding errors, and another macro named `JXL_FAILURE` exits with an error message if reached, with no effect in optimized builds. 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 debug ``` In such builds, the first JXL_FAILURE will print a message identifying where the problem is and the program will exit immediately afterwards. ### Architecture Getting back to the earlier block diagram: **Header** handling is implemented in `headers.h` and `field*`. **Bitstream**: `entropy_coder.h`, `dec_ans_*`. **(De)quantize**: `quantizer.h`. **DC prediction**: `predictor.h`. **Chroma from luma**: `chroma_from_luma.h` **(I)DCT**: `dct*.h`. Instead of operating directly on blocks of memory, the functions operate on thin wrappers which can handle blocks spread across multiple image lines. **DCT size selection**: `ac_strategy.cc` **[Gaborish]**: `enc_gaborish.h`. **[Edge preserving filter]**: `epf.h` **[Noise injection]**: `noise*` (currently disabled) **Color space conversion**: `color_*`, `dec_xyb.h`. ## Decoder overview After decoding headers, the decoder begins processing frames (`dec_frame.cc`). For each pass, it will read the DC group table of contents (TOC) and start decoding, dequantizing and restoring color correlation of each DC group (covering 2048x2048 pixels in the input image) in parallel (`compressed_dc.cc`). The DC is split into parts corresponding to each AC group (with 1px of extra border); the AC group TOC is read and each AC group (256x256 pixels) is processed in parallel (`dec_group.cc`). In each AC group, the decoder reads per-block side information indicating the kind of DCT transform; this is followed by the quantization field. Then, AC coefficients are read, dequantized and have color correlation restored on a tile per tile basis for better locality. After all the groups are read, postprocessing is applied: Gaborish smoothing and edge preserving filter, to reduce blocking and other artifacts. Finally, the image is converted back from the XYB color space (`dec_xyb.cc`) and saved to the output image (`codec_*.cc`). libjxl-0.11.1/examples/000077500000000000000000000000001472134335300147015ustar00rootroot00000000000000libjxl-0.11.1/examples/CMakeLists.txt000066400000000000000000000016731472134335300174500ustar00rootroot00000000000000# 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. # Example project using libjxl. cmake_minimum_required(VERSION 3.10) project(SAMPLE_LIBJXL LANGUAGES C CXX) # Use pkg-config to find libjxl. find_package(PkgConfig) 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) add_executable(decode_progressive decode_progressive.cc) target_link_libraries(decode_progressive PkgConfig::Jxl) add_executable(encode_oneshot encode_oneshot.cc) target_link_libraries(encode_oneshot PkgConfig::Jxl) libjxl-0.11.1/examples/decode_exif_metadata.cc000066400000000000000000000122411472134335300213060ustar00rootroot00000000000000// 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 C++ example decodes a JPEG XL image in one shot (all input bytes // 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 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 | JXL_DEC_BOX_COMPLETE)) { fprintf(stderr, "JxlDecoderSubscribeEvents failed\n"); return false; } bool support_decompression = true; if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec.get(), JXL_TRUE)) { fprintf(stderr, "NOTE: decompressing brob boxes not supported with the currently " "used jxl library.\n"); support_decompression = false; } JxlDecoderSetInput(dec.get(), jxl, size); JxlDecoderCloseInput(dec.get()); const constexpr size_t kChunkSize = 65536; size_t output_pos = 0; for (;;) { JxlDecoderStatus status = JxlDecoderProcessInput(dec.get()); if (status == JXL_DEC_ERROR) { fprintf(stderr, "Decoder error\n"); return false; } else if (status == JXL_DEC_NEED_MORE_INPUT) { fprintf(stderr, "Error, already provided all input\n"); return false; } else if (status == JXL_DEC_BOX) { if (!exif->empty()) { size_t remaining = JxlDecoderReleaseBoxBuffer(dec.get()); exif->resize(exif->size() - remaining); // No need to wait for JXL_DEC_SUCCESS or decode other boxes. return true; } JxlBoxType type; 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; } if (!memcmp(type, "Exif", 4)) { exif->resize(kChunkSize); JxlDecoderSetBoxBuffer(dec.get(), exif->data(), exif->size()); } } else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) { size_t remaining = JxlDecoderReleaseBoxBuffer(dec.get()); output_pos += kChunkSize - remaining; exif->resize(exif->size() + kChunkSize); JxlDecoderSetBoxBuffer(dec.get(), exif->data() + output_pos, exif->size() - output_pos); } else if (status == JXL_DEC_BOX_COMPLETE) { if (!exif->empty()) { size_t remaining = JxlDecoderReleaseBoxBuffer(dec.get()); exif->resize(exif->size() - remaining); return true; } return true; } else { fprintf(stderr, "Unknown decoder status\n"); return false; } } } bool LoadFile(const char* filename, std::vector* out) { FILE* file = fopen(filename, "rb"); if (!file) { return false; } if (fseek(file, 0, SEEK_END) != 0) { fclose(file); return false; } long size = ftell(file); // NOLINT // Avoid invalid file or directory. if (size >= LONG_MAX || size < 0) { fclose(file); return false; } if (fseek(file, 0, SEEK_SET) != 0) { fclose(file); return false; } out->resize(size); size_t readsize = fread(out->data(), 1, size, file); if (fclose(file) != 0) { return false; } return readsize == static_cast(size); } bool WriteFile(const char* filename, const uint8_t* data, size_t size) { FILE* file = fopen(filename, "wb"); if (!file) { fprintf(stderr, "Could not open %s for writing", filename); return false; } fwrite(data, 1, size, file); if (fclose(file) != 0) { return false; } return true; } int main(int argc, char* argv[]) { if (argc != 3) { fprintf(stderr, "Usage: %s \n" "Where:\n" " jxl = input JPEG XL image filename\n" " exif = output exif filename\n" "Output files will be overwritten.\n", argv[0]); return 1; } const char* jxl_filename = argv[1]; const char* exif_filename = argv[2]; std::vector jxl; if (!LoadFile(jxl_filename, &jxl)) { fprintf(stderr, "couldn't load %s\n", jxl_filename); return 1; } std::vector exif; if (!DecodeJpegXlExif(jxl.data(), jxl.size(), &exif)) { fprintf(stderr, "Error while decoding the jxl file\n"); return 1; } if (exif.empty()) { printf("No exif data present in this image\n"); } else { // TODO(lode): the exif box data contains the 4-byte TIFF header at the // beginning, check whether this is desired to be part of the output, or // should be removed. if (!WriteFile(exif_filename, exif.data(), exif.size())) { fprintf(stderr, "Error while writing the exif file\n"); return 1; } printf("Successfully wrote %s\n", exif_filename); } return 0; } libjxl-0.11.1/examples/decode_oneshot.cc000066400000000000000000000204561472134335300202010ustar00rootroot00000000000000// 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 C++ example decodes a JPEG XL image in one shot (all input bytes // 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 #include /** 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 * 0..1 but may go beyond this range for HDR or wide gamut. The ICC profile * describes the color format of the pixel data. */ bool DecodeJpegXlOneShot(const uint8_t* jxl, size_t size, std::vector* pixels, size_t* xsize, size_t* ysize, std::vector* icc_profile) { // Multi-threaded parallel runner. auto runner = JxlResizableParallelRunnerMake(nullptr); auto dec = JxlDecoderMake(nullptr); if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE)) { fprintf(stderr, "JxlDecoderSubscribeEvents failed\n"); return false; } if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner.get())) { fprintf(stderr, "JxlDecoderSetParallelRunner failed\n"); return false; } JxlBasicInfo info; JxlPixelFormat format = {4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0}; JxlDecoderSetInput(dec.get(), jxl, size); JxlDecoderCloseInput(dec.get()); for (;;) { JxlDecoderStatus status = JxlDecoderProcessInput(dec.get()); if (status == JXL_DEC_ERROR) { fprintf(stderr, "Decoder error\n"); return false; } else if (status == JXL_DEC_NEED_MORE_INPUT) { fprintf(stderr, "Error, already provided all input\n"); return false; } else if (status == JXL_DEC_BASIC_INFO) { if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) { fprintf(stderr, "JxlDecoderGetBasicInfo failed\n"); return false; } *xsize = info.xsize; *ysize = info.ysize; JxlResizableParallelRunnerSetThreads( runner.get(), JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize)); } else if (status == JXL_DEC_COLOR_ENCODING) { // Get the ICC color profile of the pixel data size_t icc_size; if (JXL_DEC_SUCCESS != 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(), JXL_COLOR_PROFILE_TARGET_DATA, icc_profile->data(), icc_profile->size())) { fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n"); return false; } } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { size_t buffer_size; if (JXL_DEC_SUCCESS != JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) { fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n"); return false; } if (buffer_size != *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 = static_cast(pixels->data()); size_t pixels_buffer_size = pixels->size() * sizeof(float); if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format, pixels_buffer, pixels_buffer_size)) { fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n"); return false; } } else if (status == JXL_DEC_FULL_IMAGE) { // Nothing to do. Do not yet return. If the image is an animation, more // full frames may be decoded. This example only keeps the last one. } else if (status == JXL_DEC_SUCCESS) { // All decoding successfully finished. // It's not required to call JxlDecoderReleaseInput(dec.get()) here since // the decoder will be destroyed. return true; } else { fprintf(stderr, "Unknown decoder status\n"); return false; } } } /** Writes to .pfm file (Portable FloatMap). Gimp, tev viewer and ImageMagick * support viewing this format. * The input pixels are given as 32-bit floating point with 4-channel RGBA. * The alpha channel will not be written since .pfm does not support it. */ bool WritePFM(const char* filename, const float* pixels, size_t xsize, size_t ysize) { FILE* file = fopen(filename, "wb"); if (!file) { fprintf(stderr, "Could not open %s for writing", filename); return false; } uint32_t endian_test = 1; uint8_t little_endian[4]; memcpy(little_endian, &endian_test, 4); 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++) { const float* f = &pixels[(y * xsize + x) * 4 + c]; fwrite(f, 4, 1, file); } } } if (fclose(file) != 0) { return false; } return true; } bool LoadFile(const char* filename, std::vector* out) { FILE* file = fopen(filename, "rb"); if (!file) { return false; } if (fseek(file, 0, SEEK_END) != 0) { fclose(file); return false; } long size = ftell(file); // NOLINT // Avoid invalid file or directory. if (size >= LONG_MAX || size < 0) { fclose(file); return false; } if (fseek(file, 0, SEEK_SET) != 0) { fclose(file); return false; } out->resize(size); size_t readsize = fread(out->data(), 1, size, file); if (fclose(file) != 0) { return false; } return readsize == static_cast(size); } bool WriteFile(const char* filename, const uint8_t* data, size_t size) { FILE* file = fopen(filename, "wb"); if (!file) { fprintf(stderr, "Could not open %s for writing", filename); return false; } fwrite(data, 1, size, file); if (fclose(file) != 0) { return false; } return true; } int main(int argc, char* argv[]) { if (argc != 4) { fprintf(stderr, "Usage: %s \n" "Where:\n" " jxl = input JPEG XL image filename\n" " pfm = output Portable FloatMap image filename\n" " icc = output ICC color profile filename\n" "Output files will be overwritten.\n", argv[0]); return 1; } const char* jxl_filename = argv[1]; const char* pfm_filename = argv[2]; const char* icc_filename = argv[3]; std::vector jxl; if (!LoadFile(jxl_filename, &jxl)) { fprintf(stderr, "couldn't load %s\n", jxl_filename); return 1; } std::vector pixels; std::vector icc_profile; 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"); return 1; } if (!WritePFM(pfm_filename, pixels.data(), xsize, ysize)) { fprintf(stderr, "Error while writing the PFM image file\n"); return 1; } if (!WriteFile(icc_filename, icc_profile.data(), icc_profile.size())) { fprintf(stderr, "Error while writing the ICC profile file\n"); return 1; } printf("Successfully wrote %s and %s\n", pfm_filename, icc_filename); return 0; } libjxl-0.11.1/examples/decode_progressive.cc000066400000000000000000000172311472134335300210670ustar00rootroot00000000000000// 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 C++ example decodes a JPEG XL image progressively (input bytes are // passed in chunks). The example outputs the intermediate steps to PAM files. #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS #endif #include #include #include #include #include #include // PRIu64 #include #include #include #include bool WritePAM(const char* filename, const uint8_t* buffer, size_t w, size_t h) { FILE* fp = fopen(filename, "wb"); if (!fp) { fprintf(stderr, "Could not open %s for writing", filename); return false; } fprintf(fp, "P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\nTUPLTYPE " "RGB_ALPHA\nENDHDR\n", 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; } return true; } /** Decodes JPEG XL image to 8-bit integer RGBA pixels and an ICC Profile, in a * progressive way, saving the intermediate steps. */ 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; size_t ysize = 0; // Multi-threaded parallel runner. auto runner = JxlResizableParallelRunnerMake(nullptr); auto dec = JxlDecoderMake(nullptr); if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE)) { fprintf(stderr, "JxlDecoderSubscribeEvents failed\n"); return false; } if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(), JxlResizableParallelRunner, runner.get())) { fprintf(stderr, "JxlDecoderSetParallelRunner failed\n"); return false; } JxlBasicInfo info; JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; size_t seen = 0; JxlDecoderSetInput(dec.get(), jxl, chunksize); size_t remaining = chunksize; for (;;) { JxlDecoderStatus status = JxlDecoderProcessInput(dec.get()); if (status == JXL_DEC_ERROR) { fprintf(stderr, "Decoder error\n"); return false; } else if (status == JXL_DEC_NEED_MORE_INPUT || status == JXL_DEC_SUCCESS || status == JXL_DEC_FULL_IMAGE) { seen += remaining - JxlDecoderReleaseInput(dec.get()); printf("Flushing after %" PRIu64 " bytes\n", static_cast(seen)); if (status == JXL_DEC_NEED_MORE_INPUT && JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec.get())) { printf("flush error (no preview yet)\n"); } else { char fname[1024]; if (snprintf(fname, 1024, "%s-%" PRIu64 ".pam", filename, static_cast(seen)) >= 1024) { fprintf(stderr, "Filename too long\n"); return false; }; if (!WritePAM(fname, pixels.data(), xsize, ysize)) { fprintf(stderr, "Error writing progressive output\n"); } } remaining = size - seen; if (remaining > chunksize) remaining = chunksize; if (remaining == 0) { if (status == JXL_DEC_NEED_MORE_INPUT) { fprintf(stderr, "Error, already provided all input\n"); return false; } else { return true; } } JxlDecoderSetInput(dec.get(), jxl + seen, remaining); } else if (status == JXL_DEC_BASIC_INFO) { if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) { fprintf(stderr, "JxlDecoderGetBasicInfo failed\n"); return false; } xsize = info.xsize; ysize = info.ysize; JxlResizableParallelRunnerSetThreads( runner.get(), JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize)); } else if (status == JXL_DEC_COLOR_ENCODING) { // Get the ICC color profile of the pixel data size_t icc_size; if (JXL_DEC_SUCCESS != 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(), JXL_COLOR_PROFILE_TARGET_ORIGINAL, icc_profile.data(), icc_profile.size())) { fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n"); return false; } } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { size_t buffer_size; if (JXL_DEC_SUCCESS != JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) { fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n"); return false; } if (buffer_size != xsize * ysize * 4) { fprintf(stderr, "Invalid out buffer size %" PRIu64 " != %" PRIu64 "\n", static_cast(buffer_size), static_cast(xsize * ysize * 4)); return false; } pixels.resize(xsize * ysize * 4); if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format, pixels.data(), pixels.size())) { fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n"); return false; } } else { fprintf(stderr, "Unknown decoder status\n"); return false; } } } bool LoadFile(const char* filename, std::vector* out) { FILE* file = fopen(filename, "rb"); if (!file) { return false; } if (fseek(file, 0, SEEK_END) != 0) { fclose(file); return false; } long size = ftell(file); // NOLINT // Avoid invalid file or directory. if (size >= LONG_MAX || size < 0) { fclose(file); return false; } if (fseek(file, 0, SEEK_SET) != 0) { fclose(file); return false; } out->resize(size); size_t readsize = fread(out->data(), 1, size, file); if (fclose(file) != 0) { return false; } return readsize == static_cast(size); } int main(int argc, char* argv[]) { if (argc < 3) { fprintf( stderr, "Usage: %s [chunksize]\n" "Where:\n" " jxl = input JPEG XL image filename\n" " basename = prefix of output filenames\n" " chunksize = loads chunksize bytes at a time and writes\n" " intermediate results to basename-[bytes loaded].pam\n" "Output files will be overwritten.\n", argv[0]); return 1; } const char* jxl_filename = argv[1]; const char* png_filename = argv[2]; std::vector jxl; if (!LoadFile(jxl_filename, &jxl)) { fprintf(stderr, "couldn't load %s\n", jxl_filename); return 1; } size_t chunksize = jxl.size(); if (argc > 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; } chunksize = cs; } if (!DecodeJpegXlProgressive(jxl.data(), jxl.size(), png_filename, chunksize)) { fprintf(stderr, "Error while decoding the jxl file\n"); return 1; } return 0; } libjxl-0.11.1/examples/encode_oneshot.cc000066400000000000000000000205561472134335300202140ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This example 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 /** * Reads from .pfm file (Portable FloatMap) * * @param filename name of the file to read * @param pixels vector to fill with loaded pixels as 32-bit floating point with * 3-channel RGB * @param xsize set to width of loaded image * @param ysize set to height of loaded image */ bool ReadPFM(const char* filename, std::vector* pixels, uint32_t* xsize, uint32_t* ysize) { FILE* file = fopen(filename, "rb"); if (!file) { fprintf(stderr, "Could not open %s for reading.\n", filename); return false; } uint32_t endian_test = 1; 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); // NOLINT // Avoid invalid file or directory. if (size >= LONG_MAX || size < 0) { fclose(file); return false; } if (fseek(file, 0, SEEK_SET) != 0) { fclose(file); return false; } std::vector data; data.resize(size); size_t readsize = fread(data.data(), 1, size, file); if (static_cast(readsize) != size) { // NOLINT fclose(file); return false; } if (fclose(file) != 0) { return false; } std::stringstream datastream; std::string datastream_content(data.data(), data.size()); datastream.str(datastream_content); std::string pf_token; getline(datastream, pf_token, '\n'); if (pf_token != "PF") { fprintf(stderr, "%s doesn't seem to be a 3 channel Portable FloatMap file (missing " "'PF\\n' " "bytes).\n", filename); return false; } std::string xsize_token; getline(datastream, xsize_token, ' '); *xsize = std::stoi(xsize_token); std::string ysize_token; getline(datastream, ysize_token, '\n'); *ysize = std::stoi(ysize_token); std::string endianness_token; getline(datastream, endianness_token, '\n'); bool input_little_endian; if (endianness_token == "1.0") { input_little_endian = false; } else if (endianness_token == "-1.0") { input_little_endian = true; } else { fprintf(stderr, "%s doesn't seem to be a Portable FloatMap file (endianness token " "isn't '1.0' or '-1.0').\n", filename); return false; } size_t offset = pf_token.size() + 1 + xsize_token.size() + 1 + ysize_token.size() + 1 + endianness_token.size() + 1; if (data.size() != *ysize * *xsize * 3 * 4 + offset) { 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, 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 != input_little_endian) { fprintf(stderr, "%s has a different endianness than we do, conversion is not " "supported.\n", filename); return false; } pixels->resize(*ysize * *xsize * 3); for (int y = *ysize - 1; y >= 0; y--) { 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)); offset += sizeof(float); } } } return true; } /** * Compresses the provided pixels. * * @param pixels input pixels * @param xsize width of the input image * @param ysize height of the input image * @param compressed will be populated with the compressed bytes */ bool EncodeJxlOneshot(const std::vector& pixels, const uint32_t xsize, const uint32_t ysize, std::vector* compressed) { auto enc = JxlEncoderMake(/*memory_manager=*/nullptr); auto runner = JxlThreadParallelRunnerMake( /*memory_manager=*/nullptr, JxlThreadParallelRunnerDefaultNumWorkerThreads()); if (JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(enc.get(), JxlThreadParallelRunner, runner.get())) { fprintf(stderr, "JxlEncoderSetParallelRunner failed\n"); return false; } JxlPixelFormat pixel_format = {3, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0}; JxlBasicInfo basic_info; JxlEncoderInitBasicInfo(&basic_info); basic_info.xsize = xsize; basic_info.ysize = ysize; basic_info.bits_per_sample = 32; basic_info.exponent_bits_per_sample = 8; basic_info.uses_original_profile = JXL_FALSE; if (JXL_ENC_SUCCESS != JxlEncoderSetBasicInfo(enc.get(), &basic_info)) { fprintf(stderr, "JxlEncoderSetBasicInfo failed\n"); return false; } JxlColorEncoding color_encoding = {}; 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"); return false; } JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(enc.get(), nullptr); if (JXL_ENC_SUCCESS != JxlEncoderAddImageFrame(frame_settings, &pixel_format, static_cast(pixels.data()), sizeof(float) * pixels.size())) { fprintf(stderr, "JxlEncoderAddImageFrame failed\n"); return false; } JxlEncoderCloseInput(enc.get()); compressed->resize(64); uint8_t* next_out = compressed->data(); size_t avail_out = compressed->size() - (next_out - compressed->data()); JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out); if (process_result == JXL_ENC_NEED_MORE_OUTPUT) { size_t offset = next_out - compressed->data(); compressed->resize(compressed->size() * 2); next_out = compressed->data() + offset; avail_out = compressed->size() - offset; } } compressed->resize(next_out - compressed->data()); if (JXL_ENC_SUCCESS != process_result) { fprintf(stderr, "JxlEncoderProcessOutput failed\n"); return false; } return true; } /** * Writes bytes to file. */ bool WriteFile(const std::vector& bytes, const char* filename) { FILE* file = fopen(filename, "wb"); if (!file) { fprintf(stderr, "Could not open %s for writing\n", filename); return false; } 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) { fprintf(stderr, "Could not close %s\n", filename); return false; } return true; } int main(int argc, char* argv[]) { if (argc != 3) { fprintf(stderr, "Usage: %s \n" "Where:\n" " pfm = input Portable FloatMap image filename\n" " jxl = output JPEG XL image filename\n" "Output files will be overwritten.\n", argv[0]); return 1; } const char* pfm_filename = argv[1]; const char* jxl_filename = argv[2]; std::vector pixels; uint32_t xsize; uint32_t ysize; if (!ReadPFM(pfm_filename, &pixels, &xsize, &ysize)) { fprintf(stderr, "Couldn't load %s\n", pfm_filename); return 2; } std::vector compressed; if (!EncodeJxlOneshot(pixels, xsize, ysize, &compressed)) { fprintf(stderr, "Couldn't encode jxl\n"); return 3; } if (!WriteFile(compressed, jxl_filename)) { fprintf(stderr, "Couldn't write jxl file\n"); return 4; } return 0; } libjxl-0.11.1/examples/examples.cmake000066400000000000000000000013311472134335300175170ustar00rootroot00000000000000# 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. 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) target_link_libraries(decode_progressive jxl_dec jxl_threads) add_executable(encode_oneshot ${CMAKE_CURRENT_LIST_DIR}/encode_oneshot.cc) target_link_libraries(encode_oneshot jxl jxl_threads) libjxl-0.11.1/flake.lock000066400000000000000000000027311472134335300150220ustar00rootroot00000000000000{ "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 } libjxl-0.11.1/flake.nix000066400000000000000000000015111472134335300146630ustar00rootroot00000000000000{ 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++ ''; }; } ); } libjxl-0.11.1/lib/000077500000000000000000000000001472134335300136315ustar00rootroot00000000000000libjxl-0.11.1/lib/BUILD000066400000000000000000000203511472134335300144140ustar00rootroot00000000000000# 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 ] libjxl-0.11.1/lib/CMakeLists.txt000066400000000000000000000126451472134335300164010ustar00rootroot00000000000000# 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(JPEGXL_MAJOR_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}") # This is the library API/ABI compatibility version. Changing this value makes # the shared library incompatible with previous version. A program linked # against this shared library SOVERSION will not run with an older SOVERSION. # It is important to update this value when making incompatible API/ABI changes # so that programs that depend on libjxl can update their dependencies. Semantic # versioning allows 0.y.z to have incompatible changes in minor versions. set(JPEGXL_SO_MINOR_VERSION 11) if (JPEGXL_MAJOR_VERSION EQUAL 0) set(JPEGXL_LIBRARY_SOVERSION "${JPEGXL_MAJOR_VERSION}.${JPEGXL_SO_MINOR_VERSION}") else() set(JPEGXL_LIBRARY_SOVERSION "${JPEGXL_MAJOR_VERSION}") endif() # List of warning and feature flags for our library and tests. if (MSVC) set(JPEGXL_INTERNAL_FLAGS # TODO(janwas): add flags ) else () 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 ) 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 OR BUILD_TESTING) include(jxl_extras.cmake) endif() include(jxl_threads.cmake) if (JPEGXL_ENABLE_JPEGLI) include(jpegli.cmake) endif() # 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}") if(BUILD_TESTING) include(GoogleTest) endif() # Tests for the jxl library. include(jxl_tests.cmake) if(BUILD_TESTING) # Google benchmark for the jxl library include(jxl_benchmark.cmake) endif() libjxl-0.11.1/lib/extras/000077500000000000000000000000001472134335300151375ustar00rootroot00000000000000libjxl-0.11.1/lib/extras/LICENSE.apngdis000066400000000000000000000017711472134335300175760ustar00rootroot00000000000000APNG Disassembler 2.8 Deconstructs APNG files into individual frames. http://apngdis.sourceforge.net Copyright (c) 2010-2015 Max Stepin maxst at users.sourceforge.net zlib license ------------ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. libjxl-0.11.1/lib/extras/README.md000066400000000000000000000003021472134335300164110ustar00rootroot00000000000000## JPEG XL "extras" The files in this directory do not form part of the library or codec and are only used by tests or specific internal tools that have access to the internals of the library. libjxl-0.11.1/lib/extras/alpha_blend.cc000066400000000000000000000040421472134335300176770ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/extras/alpha_blend.h000066400000000000000000000007651472134335300175510ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/extras/codec.cc000066400000000000000000000066401472134335300165310ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include "lib/extras/codec.h" #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/status.h" namespace jxl { namespace { // Any valid encoding is larger (ensures codecs can read the first few bytes) constexpr size_t kMinBytes = 9; } // namespace Status SetFromBytes(const Span bytes, const extras::ColorHints& color_hints, CodecInOut* io, 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, &ppf, constraints, orig_codec)) { return ConvertPackedPixelFileToCodecInOut(ppf, pool, io); } return JXL_FAILURE("Codecs failed to decode"); } Status Encode(const extras::PackedPixelFile& ppf, const extras::Codec codec, std::vector* bytes, ThreadPool* pool) { bytes->clear(); std::unique_ptr encoder; switch (codec) { case extras::Codec::kPNG: encoder = extras::GetAPNGEncoder(); if (encoder) { break; } else { return JXL_FAILURE("JPEG XL was built without (A)PNG support"); } case extras::Codec::kJPG: encoder = extras::GetJPEGEncoder(); if (encoder) { break; } else { return JXL_FAILURE("JPEG XL was built without JPEG support"); } case extras::Codec::kPNM: if (ppf.info.alpha_bits > 0) { encoder = extras::GetPAMEncoder(); } else if (ppf.info.num_color_channels == 1) { encoder = extras::GetPGMEncoder(); } else if (ppf.info.bits_per_sample <= 16) { encoder = extras::GetPPMEncoder(); } else { encoder = extras::GetPFMEncoder(); } break; case extras::Codec::kPGX: encoder = extras::GetPGXEncoder(); break; case extras::Codec::kGIF: return JXL_FAILURE("Encoding to GIF is not implemented"); case extras::Codec::kEXR: encoder = extras::GetEXREncoder(); 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"); } if (!encoder) { return JXL_FAILURE("Invalid codec."); } extras::EncodedImage encoded_image; JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded_image, pool)); JXL_ENSURE(encoded_image.bitstreams.size() == 1); *bytes = encoded_image.bitstreams[0]; return true; } Status Encode(const extras::PackedPixelFile& ppf, const std::string& pathname, std::vector* bytes, ThreadPool* pool) { std::string extension; const extras::Codec codec = extras::CodecFromPath(pathname, nullptr, &extension); return Encode(ppf, codec, bytes, pool); } } // namespace jxl libjxl-0.11.1/lib/extras/codec.h000066400000000000000000000036251472134335300163730ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #ifndef LIB_EXTRAS_CODEC_H_ #define LIB_EXTRAS_CODEC_H_ // Facade for image encoders/decoders (PNG, PNM, ...). #include #include #include #include "lib/extras/dec/color_hints.h" #include "lib/extras/dec/decode.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #include "lib/jxl/codec_in_out.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/field_encodings.h" // MakeBit 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, constraints, orig_codec); } Status Encode(const extras::PackedPixelFile& ppf, extras::Codec codec, std::vector* bytes, ThreadPool* pool); Status Encode(const extras::PackedPixelFile& ppf, const std::string& pathname, std::vector* bytes, ThreadPool* pool = nullptr); } // namespace jxl #endif // LIB_EXTRAS_CODEC_H_ libjxl-0.11.1/lib/extras/codec_test.cc000066400000000000000000000432461472134335300175730ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "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.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/span.h" #include "lib/jxl/base/status.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/test_utils.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 { 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, const size_t bits_per_sample) { switch (codec) { case Codec::kJPG: return ".jpg"; case Codec::kPGX: return ".pgx"; case Codec::kPNG: return ".png"; case Codec::kPNM: if (bits_per_sample == 32) return ".pfm"; if (has_alpha) return ".pam"; return is_gray ? ".pgm" : ".ppm"; case Codec::kEXR: return ".exr"; default: return std::string(); } } void VerifySameImage(const PackedImage& im0, size_t bits_per_sample0, const PackedImage& im1, size_t bits_per_sample1, bool lossless = true) { ASSERT_EQ(im0.xsize, im1.xsize); ASSERT_EQ(im0.ysize, im1.ysize); ASSERT_EQ(im0.format.num_channels, im1.format.num_channels); auto get_factor = [](JxlPixelFormat f, size_t bits) -> double { return 1.0 / ((1u << std::min(test::GetPrecision(f.data_type), bits)) - 1); }; double factor0 = get_factor(im0.format, bits_per_sample0); double factor1 = get_factor(im1.format, bits_per_sample1); 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 = test::ConvertToRGBA32(pixels1, im1.xsize, im1.ysize, im1.format, factor1); double tolerance = lossless ? 0.5 * std::min(factor0, factor1) : 3.0f / 255.0f; if (bits_per_sample0 == 32 || bits_per_sample1 == 32) { tolerance = 0.5 * std::max(factor0, factor1); } for (size_t y = 0; y < im0.ysize; ++y) { for (size_t x = 0; x < im0.xsize; ++x) { for (size_t c = 0; c < im0.format.num_channels; ++c) { size_t ix = (y * im0.xsize + x) * 4 + c; double val0 = rgba0[ix]; double val1 = rgba1[ix]; ASSERT_NEAR(val1, val0, tolerance) << "y = " << y << " x = " << x << " c = " << c; } } } } JxlColorEncoding CreateTestColorEncoding(bool is_gray) { JxlColorEncoding c; c.color_space = is_gray ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB; c.white_point = JXL_WHITE_POINT_D65; c.primaries = JXL_PRIMARIES_P3; c.rendering_intent = JXL_RENDERING_INTENT_RELATIVE; c.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR; // Roundtrip through internal color encoding to fill in primaries and white // point CIE xy coordinates. ColorEncoding c_internal; EXPECT_TRUE(c_internal.FromExternal(c)); c = c_internal.ToExternal(); return c; } std::vector GenerateICC(JxlColorEncoding color_encoding) { ColorEncoding c; EXPECT_TRUE(c.FromExternal(color_encoding)); EXPECT_TRUE(!c.ICC().empty()); return c.ICC(); } void StoreRandomValue(uint8_t* out, Rng* rng, JxlPixelFormat format, size_t bits_per_sample) { uint64_t max_val = (1ull << bits_per_sample) - 1; if (format.data_type == JXL_TYPE_UINT8) { *out = rng->UniformU(0, max_val); } else if (format.data_type == JXL_TYPE_UINT16) { uint32_t val = rng->UniformU(0, max_val); if (format.endianness == JXL_BIG_ENDIAN) { StoreBE16(val, out); } else { StoreLE16(val, out); } } else { ASSERT_EQ(format.data_type, JXL_TYPE_FLOAT); float val = rng->UniformF(0.0, 1.0); uint32_t uval; memcpy(&uval, &val, 4); if (format.endianness == JXL_BIG_ENDIAN) { StoreBE32(uval, out); } else { StoreLE32(uval, out); } } } void FillPackedImage(size_t bits_per_sample, PackedImage* image) { JxlPixelFormat format = image->format; size_t bytes_per_channel = PackedImage::BitsPerChannel(format.data_type) / 8; uint8_t* out = static_cast(image->pixels()); size_t stride = image->xsize * format.num_channels * bytes_per_channel; ASSERT_EQ(image->pixels_size, image->ysize * stride); Rng rng(129); for (size_t y = 0; y < image->ysize; ++y) { for (size_t x = 0; x < image->xsize; ++x) { for (size_t c = 0; c < format.num_channels; ++c) { StoreRandomValue(out, &rng, format, bits_per_sample); out += bytes_per_channel; } } } } struct TestImageParams { Codec codec; size_t xsize; size_t ysize; size_t bits_per_sample; bool is_gray; bool add_alpha; bool big_endian; bool add_extra_channels; bool ShouldTestRoundtrip() const { if (codec == Codec::kPNG) { return bits_per_sample <= 16; } else if (codec == Codec::kPNM) { // TODO(szabadka) Make PNM encoder endianness-aware. return ((bits_per_sample <= 16 && big_endian) || (bits_per_sample == 32 && !add_alpha && !big_endian)); } else if (codec == Codec::kPGX) { return ((bits_per_sample == 8 || bits_per_sample == 16) && is_gray && !add_alpha); } else if (codec == Codec::kEXR) { #if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ defined(THREAD_SANITIZER) // OpenEXR 2.3 has a memory leak in IlmThread_2_3::ThreadPool return false; #else return bits_per_sample == 32 && !is_gray; #endif } else if (codec == Codec::kJPG) { return bits_per_sample == 8 && !add_alpha; } else { return false; } } JxlPixelFormat PixelFormat() const { JxlPixelFormat format; format.num_channels = (is_gray ? 1 : 3) + (add_alpha ? 1 : 0); format.data_type = (bits_per_sample == 32 ? JXL_TYPE_FLOAT : bits_per_sample > 8 ? JXL_TYPE_UINT16 : JXL_TYPE_UINT8); format.endianness = big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN; format.align = 0; return format; } std::string DebugString() const { std::ostringstream os; os << "bps:" << bits_per_sample << " gr:" << is_gray << " al:" << add_alpha << " be: " << big_endian << " ec: " << add_extra_channels; return os.str(); } }; void CreateTestImage(const TestImageParams& params, PackedPixelFile* ppf) { ppf->info.xsize = params.xsize; ppf->info.ysize = params.ysize; ppf->info.bits_per_sample = params.bits_per_sample; 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 = TO_JXL_BOOL(params.codec == Codec::kEXR); JxlColorEncoding color_encoding = CreateTestColorEncoding(params.is_gray); ppf->icc = GenerateICC(color_encoding); ppf->color_encoding = color_encoding; 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)); } // Ensures reading a newly written file leads to the same image pixels. void TestRoundTrip(const TestImageParams& params, ThreadPool* pool) { if (!params.ShouldTestRoundtrip()) return; std::string extension = ExtensionFromCodec( params.codec, params.is_gray, params.add_alpha, params.bits_per_sample); printf("Codec %s %s\n", extension.c_str(), params.DebugString().c_str()); PackedPixelFile ppf_in; CreateTestImage(params, &ppf_in); EncodedImage encoded; auto encoder = Encoder::FromExtension(extension); 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; 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); 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) { ThreadPoolForTests pool(12); TestImageParams params; params.xsize = 7; params.ysize = 4; 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}) { for (bool big_endian : {false, true}) { params.codec = codec; params.bits_per_sample = static_cast(bits_per_sample); params.is_gray = is_gray; params.add_alpha = add_alpha; params.big_endian = big_endian; params.add_extra_channels = false; TestRoundTrip(params, pool.get()); if (codec == Codec::kPNM && add_alpha) { params.add_extra_channels = true; TestRoundTrip(params, pool.get()); } } } } } } } TEST(CodecTest, LosslessPNMRoundtrip) { ThreadPoolForTests pool(12); 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); 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)); 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, 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)); 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); ASSERT_TRUE(PnmParseUnsigned(MakeSpan("32"), &u)); ASSERT_TRUE(u == 32); 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, FormatNegotiation) { const std::vector accepted_formats = { {/*num_channels=*/4, /*data_type=*/JXL_TYPE_UINT16, /*endianness=*/JXL_NATIVE_ENDIAN, /*align=*/0}, {/*num_channels=*/3, /*data_type=*/JXL_TYPE_UINT8, /*endianness=*/JXL_NATIVE_ENDIAN, /*align=*/0}, {/*num_channels=*/3, /*data_type=*/JXL_TYPE_UINT16, /*endianness=*/JXL_NATIVE_ENDIAN, /*align=*/0}, {/*num_channels=*/1, /*data_type=*/JXL_TYPE_UINT8, /*endianness=*/JXL_NATIVE_ENDIAN, /*align=*/0}, }; JxlBasicInfo info; JxlEncoderInitBasicInfo(&info); info.bits_per_sample = 12; info.num_color_channels = 2; JxlPixelFormat format; EXPECT_FALSE(SelectFormat(accepted_formats, info, &format)); info.num_color_channels = 3; ASSERT_TRUE(SelectFormat(accepted_formats, info, &format)); EXPECT_EQ(format.num_channels, info.num_color_channels); // 16 is the smallest accepted format that can accommodate the 12-bit data. EXPECT_EQ(format.data_type, JXL_TYPE_UINT16); } TEST(CodecTest, EncodeToPNG) { ThreadPool* const pool = nullptr; std::unique_ptr png_encoder = Encoder::FromExtension(".png"); if (!png_encoder) { fprintf(stderr, "Skipping test because of missing codec support.\n"); return; } const std::vector original_png = jxl::test::ReadTestData( "external/wesaturate/500px/tmshre_riaphotographs_srgb8.png"); PackedPixelFile ppf; ASSERT_TRUE(extras::DecodeBytes(Bytes(original_png), ColorHints(), &ppf)); const JxlPixelFormat& format = ppf.frames.front().color.format; 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_TRUE(encoded_png.icc.empty()); ASSERT_EQ(encoded_png.bitstreams.size(), 1); PackedPixelFile decoded_ppf; ASSERT_TRUE(extras::DecodeBytes(Bytes(encoded_png.bitstreams.front()), ColorHints(), &decoded_ppf)); ASSERT_EQ(decoded_ppf.info.bits_per_sample, ppf.info.bits_per_sample); ASSERT_EQ(decoded_ppf.frames.size(), 1); VerifySameImage(ppf.frames[0].color, ppf.info.bits_per_sample, decoded_ppf.frames[0].color, decoded_ppf.info.bits_per_sample); } } // namespace } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/common.cc000066400000000000000000000041211472134335300167340ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/extras/common.h000066400000000000000000000012421472134335300165770ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/extras/compressed_icc.cc000066400000000000000000000042271472134335300204350ustar00rootroot00000000000000// 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; } libjxl-0.11.1/lib/extras/compressed_icc_test.cc000066400000000000000000000026451472134335300214760ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/extras/dec/000077500000000000000000000000001472134335300156725ustar00rootroot00000000000000libjxl-0.11.1/lib/extras/dec/apng.cc000066400000000000000000001343171472134335300171370ustar00rootroot00000000000000// 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/apng.h" // Parts of this code are taken from apngdis, which has the following license: /* APNG Disassembler 2.8 * * Deconstructs APNG files into individual frames. * * http://apngdis.sourceforge.net * * Copyright (c) 2010-2015 Max Stepin * maxst at users.sourceforge.net * * zlib license * ------------ * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. * */ #include #include #include #include #include #include #include #include #include #include #include #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/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 { constexpr std::array kPngSignature = {137, 'P', 'N', 'G', '\r', '\n', 26, '\n'}; // Returns floating-point value from the PNG encoding (times 10^5). double F64FromU32(const uint32_t x) { return static_cast(x) * 1E-5; } /** 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 (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; } /** * 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.data())); return true; } /** 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(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(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; } /** 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; } /** 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; } /** 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; } } else { // Do not consume terminator (non-decimal digit). break; } (*pos)++; } if (len == 0 || len > 8) { return JXL_FAILURE("Failed to parse decimal"); } return true; } /** * Parses a PNG text chunk with key of the form "Raw profile type ####", with * #### a type. * * Returns whether it could successfully parse the content. * We trust key and encoded are null-terminated because they come from * libpng. */ 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; } 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()); } 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") { // 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'); } /** 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); } /** 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; std::atomic has_error{false}; 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; } }; /** * Helper that chunks in-memory input. */ struct Reader { explicit Reader(Span data) : data_(data) {} 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); (void)png_set_interlace_handling(png_ptr); png_read_update_info(png_ptr, info_ptr); } 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); } // Holds intermediate state during parsing APNG file. struct Context { ~Context() { // Make sure png memory is released in any case. ResetPngDecoder(); } 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); } /** * Initialize PNG decoder. * * TODO(eustas): add details */ bool InitPngDecoder(const std::vector& chunksInfo, const RectT& viewport) { ResetPngDecoder(); 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; } 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; 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; } // 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 }; } // namespace bool CanDecodeAPNG() { return true; } /** * 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; Reader input(bytes); // 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 { JXL_RETURN_IF_ERROR(finalize_frame()); if (!ctx.InitPngDecoder(passthrough_chunks, next_frame.viewport)) { return JXL_FAILURE("Failed to initialize PNG decoder"); } } 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 = { /*num_channels=*/num_channels, /*data_type=*/ppf->info.bits_per_sample > 8 ? JXL_TYPE_UINT16 : JXL_TYPE_UINT8, /*endianness=*/JXL_BIG_ENDIAN, /*align=*/0, }; bytes_per_pixel = num_channels * (format.data_type == JXL_TYPE_UINT16 ? 2 : 1); // 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 (!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; } } 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; for (size_t i = 0; i < frames.size(); i++) { 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 (fc.dispose_op == DisposeOp::NONE) { has_nontrivial_background = true; } bool should_blend = fc.blend_op == BlendOp::OVER; bool use_for_next_frame = has_nontrivial_background && fc.dispose_op != DisposeOp::PREVIOUS; size_t x0 = vp.x0(); size_t y0 = vp.y0(); if (previous_frame_should_be_cleared) { 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.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.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. 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 < 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(pixels.pixels()) + pixels.stride * y, xsize * bytes_per_pixel); } x0 = px0; y0 = py0; xsize = pxs; ysize = pys; should_blend = false; ppf->frames.emplace_back(std::move(new_data)); } else { // If all else fails, insert a 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 = pxs; pframe.frame_info.layer_info.ysize = pys; pframe.frame_info.duration = 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 = 1; pframe.frame_info.layer_info.save_as_reference = 1; ppf->frames.emplace_back(std::move(frame.pixels)); } } else { ppf->frames.emplace_back(std::move(frame.pixels)); } auto& pframe = ppf->frames.back(); pframe.frame_info.layer_info.crop_x0 = x0; pframe.frame_info.layer_info.crop_y0 = y0; pframe.frame_info.layer_info.xsize = xsize; pframe.frame_info.layer_info.ysize = ysize; pframe.frame_info.duration = 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 = 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 && (fc.dispose_op == DisposeOp::BACKGROUND); } if (ppf->frames.empty()) return JXL_FAILURE("No frames decoded"); ppf->frames.back().frame_info.is_last = JXL_TRUE; return true; } #endif // JPEGXL_ENABLE_APNG } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/dec/apng.h000066400000000000000000000014721472134335300167740ustar00rootroot00000000000000// 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_APNG_H_ #define LIB_EXTRAS_DEC_APNG_H_ // Decodes APNG images in memory. #include #include "lib/extras/dec/color_hints.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { struct SizeConstraints; namespace extras { bool CanDecodeAPNG(); // Decodes `bytes` into `ppf`. Status DecodeImageAPNG(Span bytes, const ColorHints& color_hints, PackedPixelFile* ppf, const SizeConstraints* constraints = nullptr); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_DEC_APNG_H_ libjxl-0.11.1/lib/extras/dec/color_description.cc000066400000000000000000000173771472134335300217410ustar00rootroot00000000000000// 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/color_description.h" #include #include #include "lib/jxl/base/common.h" namespace jxl { namespace { template struct EnumName { const char* name; T value; }; 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}}); 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}}); constexpr auto kJxlPrimariesNames = to_array>({{"SRG", JXL_PRIMARIES_SRGB}, {"Cst", JXL_PRIMARIES_CUSTOM}, {"202", JXL_PRIMARIES_2100}, {"DCI", JXL_PRIMARIES_P3}}); 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}}); 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 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; } } return false; } class Tokenizer { public: Tokenizer(const std::string* input, char separator) : input_(input), separator_(separator) {} Status Next(std::string* next) { const size_t end = input_->find(separator_, start_); if (end == std::string::npos) { *next = input_->substr(start_); // rest of string } else { *next = input_->substr(start_, end - start_); } if (next->empty()) return JXL_FAILURE("Missing token"); start_ = end + 1; return true; } private: const std::string* const input_; // not owned const char separator_; size_t start_ = 0; // of next token }; Status ParseDouble(const std::string& num, double* d) { char* end; errno = 0; *d = strtod(num.c_str(), &end); if (*d == 0.0 && end == num.c_str()) { return JXL_FAILURE("Invalid double: %s", num.c_str()); } if (std::isnan(*d)) { return JXL_FAILURE("Invalid double: %s", num.c_str()); } if (errno == ERANGE) { return JXL_FAILURE("Double out of range: %s", num.c_str()); } return true; } Status ParseDouble(Tokenizer* tokenizer, double* d) { std::string num; JXL_RETURN_IF_ERROR(tokenizer->Next(&num)); return ParseDouble(num, d); } Status ParseColorSpace(Tokenizer* tokenizer, JxlColorEncoding* c) { std::string str; JXL_RETURN_IF_ERROR(tokenizer->Next(&str)); JxlColorSpace cs; if (ParseEnum(str, kJxlColorSpaceNames, &cs)) { c->color_space = cs; return true; } return JXL_FAILURE("Unknown ColorSpace %s", str.c_str()); } Status ParseWhitePoint(Tokenizer* tokenizer, JxlColorEncoding* c) { if (c->color_space == JXL_COLOR_SPACE_XYB) { // Implicit white point. c->white_point = JXL_WHITE_POINT_D65; return true; } std::string str; JXL_RETURN_IF_ERROR(tokenizer->Next(&str)); if (ParseEnum(str, kJxlWhitePointNames, &c->white_point)) return true; Tokenizer xy_tokenizer(&str, ';'); c->white_point = JXL_WHITE_POINT_CUSTOM; JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->white_point_xy + 0)); JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->white_point_xy + 1)); return true; } Status ParsePrimaries(Tokenizer* tokenizer, JxlColorEncoding* c) { if (c->color_space == JXL_COLOR_SPACE_GRAY || c->color_space == JXL_COLOR_SPACE_XYB) { // No primaries case. return true; } std::string str; JXL_RETURN_IF_ERROR(tokenizer->Next(&str)); if (ParseEnum(str, kJxlPrimariesNames, &c->primaries)) return true; Tokenizer xy_tokenizer(&str, ';'); JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_red_xy + 0)); JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_red_xy + 1)); JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_green_xy + 0)); JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_green_xy + 1)); JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_blue_xy + 0)); JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_blue_xy + 1)); c->primaries = JXL_PRIMARIES_CUSTOM; return true; } Status ParseRenderingIntent(Tokenizer* tokenizer, JxlColorEncoding* c) { std::string str; JXL_RETURN_IF_ERROR(tokenizer->Next(&str)); if (ParseEnum(str, kJxlRenderingIntentNames, &c->rendering_intent)) return true; return JXL_FAILURE("Invalid RenderingIntent %s\n", str.c_str()); } Status ParseTransferFunction(Tokenizer* tokenizer, JxlColorEncoding* c) { if (c->color_space == JXL_COLOR_SPACE_XYB) { // Implicit TF. c->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA; c->gamma = 1 / 3.; return true; } std::string str; JXL_RETURN_IF_ERROR(tokenizer->Next(&str)); if (ParseEnum(str, kJxlTransferFunctionNames, &c->transfer_function)) { return true; } if (str[0] == 'g') { JXL_RETURN_IF_ERROR(ParseDouble(str.substr(1), &c->gamma)); c->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA; return true; } return JXL_FAILURE("Invalid gamma %s", str.c_str()); } } // namespace Status ParseDescription(const std::string& description, JxlColorEncoding* c) { *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; } } // namespace jxl libjxl-0.11.1/lib/extras/dec/color_description.h000066400000000000000000000011511472134335300215620ustar00rootroot00000000000000// 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_COLOR_DESCRIPTION_H_ #define LIB_EXTRAS_COLOR_DESCRIPTION_H_ #include #include #include "lib/jxl/base/status.h" namespace jxl { // Parse the color description into a JxlColorEncoding "RGB_D65_SRG_Rel_Lin". Status ParseDescription(const std::string& description, JxlColorEncoding* JXL_RESTRICT c); } // namespace jxl #endif // LIB_EXTRAS_COLOR_DESCRIPTION_H_ libjxl-0.11.1/lib/extras/dec/color_description_test.cc000066400000000000000000000023011472134335300227550ustar00rootroot00000000000000// 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/color_description.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/test_utils.h" #include "lib/jxl/testing.h" namespace jxl { // Verify ParseDescription(Description) yields the same ColorEncoding TEST(ColorDescriptionTest, RoundTripAll) { for (const auto& cdesc : test::AllEncodings()) { const ColorEncoding c_original = test::ColorEncodingFromDescriptor(cdesc); const std::string description = Description(c_original); printf("%s\n", description.c_str()); JxlColorEncoding c_external = {}; EXPECT_TRUE(ParseDescription(description, &c_external)); ColorEncoding 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; } } TEST(ColorDescriptionTest, NanGamma) { const std::string description = "Gra_2_Per_gnan"; JxlColorEncoding c; EXPECT_FALSE(ParseDescription(description, &c)); } } // namespace jxl libjxl-0.11.1/lib/extras/dec/color_hints.cc000066400000000000000000000056051472134335300205320ustar00rootroot00000000000000// 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/color_hints.h" #include #include #include "lib/extras/dec/color_description.h" #include "lib/jxl/base/status.h" namespace jxl { namespace extras { Status ApplyColorHints(const ColorHints& color_hints, const bool color_already_set, const bool is_gray, PackedPixelFile* ppf) { bool got_color_space = color_already_set; JXL_RETURN_IF_ERROR(color_hints.Foreach( [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)) { return JXL_FAILURE("Failed to apply color_space"); } ppf->color_encoding = c_original_external; if (is_gray != (ppf->color_encoding.color_space == JXL_COLOR_SPACE_GRAY)) { return JXL_FAILURE("mismatch between file and color_space hint"); } got_color_space = true; } 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()); } return true; })); if (!got_color_space) { ppf->color_encoding.color_space = is_gray ? JXL_COLOR_SPACE_GRAY : 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; } return true; } } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/dec/color_hints.h000066400000000000000000000044461472134335300203760ustar00rootroot00000000000000// 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_COLOR_HINTS_H_ #define LIB_EXTRAS_COLOR_HINTS_H_ // Not all the formats implemented in the extras lib support bundling color // 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 #include #include #include "lib/extras/packed_image.h" #include "lib/jxl/base/status.h" namespace jxl { namespace extras { class ColorHints { public: // key=color_space, value=Description(c/pp): specify the ColorEncoding of // the pixels for decoding. Otherwise, if the codec did not obtain an ICC // profile from the image, assume sRGB. // // Strings are taken from the command line, so avoid spaces for convenience. void Add(const std::string& key, const std::string& value) { kv_.emplace_back(key, value); } // Calls `func(key, value)` for each key/value in the order they were added, // returning false immediately if `func` returns false. template Status Foreach(const Func& func) const { for (const KeyValue& kv : kv_) { Status ok = func(kv.key, kv.value); if (!ok) { return JXL_FAILURE("ColorHints::Foreach returned false"); } } return true; } private: // Splitting into key/value avoids parsing in each codec. struct KeyValue { KeyValue(std::string key, std::string value) : key(std::move(key)), value(std::move(value)) {} std::string key; std::string value; }; std::vector kv_; }; // Apply the color hints to the decoded image in PackedPixelFile if any. // color_already_set tells whether the color encoding was already set, in which // case the hints are ignored if any hint is passed. Status ApplyColorHints(const ColorHints& color_hints, bool color_already_set, bool is_gray, PackedPixelFile* ppf); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_COLOR_HINTS_H_ libjxl-0.11.1/lib/extras/dec/decode.cc000066400000000000000000000106201472134335300174230ustar00rootroot00000000000000// 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/decode.h" #include #include "lib/extras/dec/apng.h" #include "lib/extras/dec/exr.h" #include "lib/extras/dec/gif.h" #include "lib/extras/dec/jpg.h" #include "lib/extras/dec/jxl.h" #include "lib/extras/dec/pgx.h" #include "lib/extras/dec/pnm.h" namespace jxl { namespace extras { namespace { // Any valid encoding is larger (ensures codecs can read the first few bytes) constexpr size_t kMinBytes = 9; 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); } // Extension not found return ""; } } // namespace 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 (ext == ".jpg") return Codec::kJPG; if (ext == ".jpeg") return Codec::kJPG; 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 (ext == ".gif") return Codec::kGIF; 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, 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 = JXL_TRUE; ppf->info.orientation = JXL_ORIENT_IDENTITY; 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; return true; } } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/dec/decode.h000066400000000000000000000026701472134335300172730ustar00rootroot00000000000000// 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_DECODE_H_ #define LIB_EXTRAS_DEC_DECODE_H_ // Facade for image decoders (PNG, PNM, ...). #include #include #include #include "lib/extras/dec/color_hints.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { struct SizeConstraints; namespace extras { // Codecs supported by DecodeBytes. enum class Codec : uint32_t { kUnknown, // for CodecFromPath kPNG, kPNM, kPGX, kJPG, kGIF, kEXR, kJXL }; 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 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, extras::PackedPixelFile* ppf, const SizeConstraints* constraints = nullptr, Codec* orig_codec = nullptr); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_DEC_DECODE_H_ libjxl-0.11.1/lib/extras/dec/exr.cc000066400000000000000000000175471472134335300170150ustar00rootroot00000000000000// 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/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 #include #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; // OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using // uint64_t as recommended causes build failures with previous OpenEXR versions // on macOS, where the definition for OpenEXR::Int64 was actually not equivalent // to uint64_t. This alternative should work in all cases. using ExrInt64 = decltype(std::declval().tellg()); constexpr int kExrBitsPerSample = 16; constexpr int kExrAlphaBits = 16; class InMemoryIStream : public OpenEXR::IStream { public: // The data pointed to by `bytes` must outlive the InMemoryIStream. explicit InMemoryIStream(const Span bytes) : IStream(/*fileName=*/""), bytes_(bytes) {} bool isMemoryMapped() const override { return true; } char* readMemoryMapped(const int n) override { 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; return result; } bool read(char c[], const int n) override { std::copy_n(readMemoryMapped(n), n, c); return pos_ < bytes_.size(); } ExrInt64 tellg() override { return pos_; } void seekg(const ExrInt64 pos) override { if (pos >= bytes_.size()) { JXL_EXR_THROW_LENGTH_ERROR(); } pos_ = pos; } private: const Span bytes_; size_t pos_ = 0; }; } // namespace bool CanDecodeEXR() { return true; } Status DecodeImageEXR(Span bytes, const ColorHints& color_hints, PackedPixelFile* ppf, const SizeConstraints* constraints) { InMemoryIStream is(bytes); #ifdef __EXCEPTIONS std::unique_ptr input_ptr; try { input_ptr = jxl::make_unique(is); } catch (...) { // silently return false if it is not an EXR file return false; } OpenEXR::RgbaInputFile& input = *input_ptr; #else OpenEXR::RgbaInputFile input(is); #endif if ((input.channels() & OpenEXR::RgbaChannels::WRITE_RGB) != OpenEXR::RgbaChannels::WRITE_RGB) { return JXL_FAILURE("only RGB OpenEXR files are supported"); } const bool has_alpha = (input.channels() & OpenEXR::RgbaChannels::WRITE_A) == OpenEXR::RgbaChannels::WRITE_A; const float intensity_target = OpenEXR::hasWhiteLuminance(input.header()) ? OpenEXR::whiteLuminance(input.header()) : 0; auto image_size = input.displayWindow().size(); // Size is computed as max - min, but both bounds are inclusive. ++image_size.x; ++image_size.y; ppf->info.xsize = image_size.x; ppf->info.ysize = image_size.y; ppf->info.num_color_channels = 3; const JxlDataType data_type = kExrBitsPerSample == 16 ? JXL_TYPE_FLOAT16 : JXL_TYPE_FLOAT; const JxlPixelFormat format{ /*num_channels=*/3u + (has_alpha ? 1u : 0u), /*data_type=*/data_type, /*endianness=*/JXL_NATIVE_ENDIAN, /*align=*/0, }; ppf->frames.clear(); // Allocates the frame buffer. { 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; // Number of rows to read at a time. // https://www.openexr.com/documentation/ReadingAndWritingImageFiles.pdf // recommends reading the whole file at once. const int y_chunk_size = input.displayWindow().size().y + 1; std::vector input_rows(row_size * y_chunk_size); for (int start_y = std::max(input.dataWindow().min.y, input.displayWindow().min.y); start_y <= std::min(input.dataWindow().max.y, input.displayWindow().max.y); start_y += y_chunk_size) { // Inclusive. const int end_y = std::min( start_y + y_chunk_size - 1, std::min(input.dataWindow().max.y, input.displayWindow().max.y)); input.setFrameBuffer( input_rows.data() - input.dataWindow().min.x - start_y * row_size, /*xStride=*/1, /*yStride=*/row_size); input.readPixels(start_y, end_y); for (int exr_y = start_y; exr_y <= end_y; ++exr_y) { const int image_y = exr_y - input.displayWindow().min.y; const OpenEXR::Rgba* const JXL_RESTRICT input_row = &input_rows[(exr_y - start_y) * row_size]; uint8_t* row = static_cast(frame.color.pixels()) + frame.color.stride * image_y; const uint32_t pixel_size = (3 + (has_alpha ? 1 : 0)) * kExrBitsPerSample / 8; for (int exr_x = std::max(input.dataWindow().min.x, input.displayWindow().min.x); exr_x <= std::min(input.dataWindow().max.x, input.displayWindow().max.x); ++exr_x) { const int image_x = exr_x - input.displayWindow().min.x; // TODO(eustas): UB: OpenEXR::Rgba is not TriviallyCopyable memcpy(row + image_x * pixel_size, input_row + (exr_x - input.dataWindow().min.x), pixel_size); } } } ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR; ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB; ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB; ppf->color_encoding.white_point = JXL_WHITE_POINT_D65; if (OpenEXR::hasChromaticities(input.header())) { ppf->color_encoding.primaries = JXL_PRIMARIES_CUSTOM; ppf->color_encoding.white_point = JXL_WHITE_POINT_CUSTOM; const auto& chromaticities = OpenEXR::chromaticities(input.header()); ppf->color_encoding.primaries_red_xy[0] = chromaticities.red.x; ppf->color_encoding.primaries_red_xy[1] = chromaticities.red.y; ppf->color_encoding.primaries_green_xy[0] = chromaticities.green.x; ppf->color_encoding.primaries_green_xy[1] = chromaticities.green.y; ppf->color_encoding.primaries_blue_xy[0] = chromaticities.blue.x; ppf->color_encoding.primaries_blue_xy[1] = chromaticities.blue.y; ppf->color_encoding.white_point_xy[0] = chromaticities.white.x; ppf->color_encoding.white_point_xy[1] = chromaticities.white.y; } // EXR uses binary16 or binary32 floating point format. ppf->info.bits_per_sample = kExrBitsPerSample; ppf->info.exponent_bits_per_sample = kExrBitsPerSample == 16 ? 5 : 8; if (has_alpha) { ppf->info.alpha_bits = kExrAlphaBits; ppf->info.alpha_exponent_bits = ppf->info.exponent_bits_per_sample; ppf->info.alpha_premultiplied = JXL_TRUE; } ppf->info.intensity_target = intensity_target; return true; } } // namespace extras } // namespace jxl #endif // JPEGXL_ENABLE_EXR libjxl-0.11.1/lib/extras/dec/exr.h000066400000000000000000000015671472134335300166520ustar00rootroot00000000000000// 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_EXR_H_ #define LIB_EXTRAS_DEC_EXR_H_ // 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/span.h" #include "lib/jxl/base/status.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, PackedPixelFile* ppf, const SizeConstraints* constraints = nullptr); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_DEC_EXR_H_ libjxl-0.11.1/lib/extras/dec/gif.cc000066400000000000000000000363741472134335300167630ustar00rootroot00000000000000// 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/gif.h" #include "lib/jxl/base/status.h" #if JPEGXL_ENABLE_GIF #include #endif #include #include #include #include #include #include "lib/extras/size_constraints.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/rect.h" #include "lib/jxl/base/sanitizers.h" namespace jxl { namespace extras { #if JPEGXL_ENABLE_GIF namespace { struct ReadState { Span bytes; }; struct DGifCloser { void operator()(GifFileType* const ptr) const { DGifCloseFile(ptr, nullptr); } }; using GifUniquePtr = std::unique_ptr; struct PackedRgba { uint8_t r, g, b, a; }; struct PackedRgb { uint8_t r, g, b; }; 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, }; 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 } Status DecodeImageGIF(Span bytes, const ColorHints& color_hints, 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, int n) { ReadState* const state = reinterpret_cast(gif->UserData); // giflib API requires the input size `n` to be signed int. if (static_cast(n) > state->bytes.size()) { n = state->bytes.size(); } memcpy(bytes, state->bytes.data(), n); if (!state->bytes.remove_prefix(n)) return 0; return n; }; GifUniquePtr gif(DGifOpen(&state, ReadFromSpan, &error)); if (gif == nullptr) { if (error == D_GIF_ERR_NOT_GIF_FILE) { // Not an error. return false; } else { return JXL_FAILURE("Failed to read GIF: %s", GifErrorString(error)); } } error = DGifSlurp(gif.get()); if (error != GIF_OK) { return JXL_FAILURE("Failed to read GIF: %s", GifErrorString(gif->Error)); } msan::UnpoisonMemory(gif.get(), sizeof(*gif)); if (gif->SColorMap) { msan::UnpoisonMemory(gif->SColorMap, sizeof(*gif->SColorMap)); msan::UnpoisonMemory( gif->SColorMap->Colors, sizeof(*gif->SColorMap->Colors) * gif->SColorMap->ColorCount); } msan::UnpoisonMemory(gif->SavedImages, sizeof(*gif->SavedImages) * gif->ImageCount); JXL_RETURN_IF_ERROR( 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)); 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 (constraints && (total_pixel_count > constraints->dec_max_pixels)) { return JXL_FAILURE("Image too big"); } } if (!gif->SColorMap) { for (int i = 0; i < gif->ImageCount; ++i) { if (!gif->SavedImages[i].ImageDesc.ColorMap) { return JXL_FAILURE("Missing GIF color map"); } } } if (gif->ImageCount > 1) { 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; } ppf->frames.clear(); ppf->frames.reserve(gif->ImageCount); ppf->info.xsize = gif->SWidth; ppf->info.ysize = gif->SHeight; ppf->info.bits_per_sample = 8; ppf->info.exponent_bits_per_sample = 0; // alpha_bits is later set to 8 if we find a frame with transparent pixels. ppf->info.alpha_bits = 0; ppf->info.alpha_exponent_bits = 0; JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, /*color_already_set=*/false, /*is_gray=*/false, ppf)); ppf->info.num_color_channels = 3; // Pixel format for the 'canvas' onto which we paint // the (potentially individually cropped) GIF frames // of an animation. const JxlPixelFormat canvas_format{ /*num_channels=*/4u, /*data_type=*/JXL_TYPE_UINT8, /*endianness=*/JXL_NATIVE_ENDIAN, /*align=*/0, }; // Pixel format for the JXL PackedFrame that goes into the // PackedPixelFile. Here, we use 3 color channels, and provide // the alpha channel as an extra_channel wherever it is used. const JxlPixelFormat packed_frame_format{ /*num_channels=*/3u, /*data_type=*/JXL_TYPE_UINT8, /*endianness=*/JXL_NATIVE_ENDIAN, /*align=*/0, }; GifColorType background_color; if (gif->SColorMap == nullptr || gif->SBackGroundColor >= gif->SColorMap->ColorCount) { background_color = {0, 0, 0}; } else { background_color = gif->SColorMap->Colors[gif->SBackGroundColor]; } const PackedRgba background_rgba{background_color.Red, background_color.Green, background_color.Blue, 0}; 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}; Rect previous_rect_if_restore_to_background; bool replace = true; bool last_base_was_none = true; for (int i = 0; i < gif->ImageCount; ++i) { const SavedImage& image = gif->SavedImages[i]; msan::UnpoisonMemory(image.RasterBits, sizeof(*image.RasterBits) * image.ImageDesc.Width * image.ImageDesc.Height); const Rect image_rect(image.ImageDesc.Left, image.ImageDesc.Top, image.ImageDesc.Width, image.ImageDesc.Height); Rect total_rect; if (previous_rect_if_restore_to_background.xsize() != 0 || previous_rect_if_restore_to_background.ysize() != 0) { const size_t xbegin = std::min( image_rect.x0(), previous_rect_if_restore_to_background.x0()); const size_t ybegin = std::min( image_rect.y0(), previous_rect_if_restore_to_background.y0()); const size_t xend = std::max(image_rect.x0() + image_rect.xsize(), previous_rect_if_restore_to_background.x0() + previous_rect_if_restore_to_background.xsize()); const size_t yend = std::max(image_rect.y0() + image_rect.ysize(), previous_rect_if_restore_to_background.y0() + previous_rect_if_restore_to_background.ysize()); total_rect = Rect(xbegin, ybegin, xend - xbegin, yend - ybegin); previous_rect_if_restore_to_background = Rect(); replace = true; } else { total_rect = image_rect; replace = false; } if (!image_rect.IsInside(canvas_rect)) { return JXL_FAILURE("GIF frame extends outside of the canvas"); } // Allocates the frame buffer. { 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) -> 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 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_ENSURE(color_map); msan::UnpoisonMemory(color_map, sizeof(*color_map)); msan::UnpoisonMemory(color_map->Colors, sizeof(*color_map->Colors) * color_map->ColorCount); GraphicsControlBlock gcb; DGifSavedExtensionToGCB(gif.get(), i, &gcb); msan::UnpoisonMemory(&gcb, sizeof(gcb)); bool is_full_size = total_rect.x0() == 0 && total_rect.y0() == 0 && total_rect.xsize() == canvas.color.xsize && total_rect.ysize() == canvas.color.ysize; if (ppf->info.have_animation) { frame->frame_info.duration = gcb.DelayTime; frame->frame_info.layer_info.have_crop = static_cast(!is_full_size); frame->frame_info.layer_info.crop_x0 = total_rect.x0(); frame->frame_info.layer_info.crop_y0 = total_rect.y0(); frame->frame_info.layer_info.xsize = frame->color.xsize; frame->frame_info.layer_info.ysize = frame->color.ysize; if (last_base_was_none) { replace = true; } frame->frame_info.layer_info.blend_info.blendmode = replace ? JXL_BLEND_REPLACE : JXL_BLEND_BLEND; // We always only reference at most the last frame frame->frame_info.layer_info.blend_info.source = last_base_was_none ? 0u : 1u; frame->frame_info.layer_info.blend_info.clamp = 1; frame->frame_info.layer_info.blend_info.alpha = 0; // TODO(veluca): this could in principle be implemented. if (last_base_was_none && (total_rect.x0() != 0 || total_rect.y0() != 0 || total_rect.xsize() != canvas.color.xsize || total_rect.ysize() != canvas.color.ysize || !replace)) { return JXL_FAILURE( "GIF with dispose-to-0 is not supported for non-full or " "blended frames"); } switch (gcb.DisposalMode) { case DISPOSE_DO_NOT: case DISPOSE_BACKGROUND: frame->frame_info.layer_info.save_as_reference = 1u; last_base_was_none = false; break; case DISPOSE_PREVIOUS: frame->frame_info.layer_info.save_as_reference = 0u; break; default: frame->frame_info.layer_info.save_as_reference = 0u; last_base_was_none = true; } } // Update the canvas by creating a copy first. 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) { // Assumes format.align == 0. row points to the beginning of the y row in // the image_rect. PackedRgba* row = static_cast(new_canvas_image.pixels()) + (y + image_rect.y0()) * new_canvas_image.xsize + image_rect.x0(); for (size_t x = 0; x < image_rect.xsize(); ++x, ++byte_index) { const GifByteType byte = image.RasterBits[byte_index]; if (byte >= color_map->ColorCount) { return JXL_FAILURE("GIF color is out of bounds"); } if (byte == gcb.TransparentColor) continue; GifColorType color = color_map->Colors[byte]; row[x].r = color.Red; row[x].g = color.Green; row[x].b = color.Blue; row[x].a = 255; } } const PackedImage& sub_frame_image = frame->color; if (replace) { // Copy from the new canvas image to the subframe for (size_t y = 0; y < total_rect.ysize(); ++y) { const PackedRgba* row_in = static_cast(new_canvas_image.pixels()) + (y + total_rect.y0()) * new_canvas_image.xsize + total_rect.x0(); PackedRgb* row_out = static_cast(sub_frame_image.pixels()) + y * sub_frame_image.xsize; for (size_t x = 0; x < sub_frame_image.xsize; ++x) { row_out[x].r = row_in[x].r; row_out[x].g = row_in[x].g; row_out[x].b = row_in[x].b; JXL_RETURN_IF_ERROR(set_pixel_alpha(x, y, row_in[x].a)); } } } else { for (size_t y = 0, byte_index = 0; y < image_rect.ysize(); ++y) { // Assumes format.align == 0 PackedRgb* row = static_cast(sub_frame_image.pixels()) + y * sub_frame_image.xsize; for (size_t x = 0; x < image_rect.xsize(); ++x, ++byte_index) { const GifByteType byte = image.RasterBits[byte_index]; if (byte > color_map->ColorCount) { return JXL_FAILURE("GIF color is out of bounds"); } if (byte == gcb.TransparentColor) { row[x].r = 0; row[x].g = 0; row[x].b = 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; JXL_RETURN_IF_ERROR(set_pixel_alpha(x, y, 255)); } } } if (!frame->extra_channels.empty()) { ppf->info.alpha_bits = 8; } switch (gcb.DisposalMode) { case DISPOSE_DO_NOT: canvas.color = std::move(new_canvas_image); break; case DISPOSE_BACKGROUND: std::fill_n(static_cast(canvas.color.pixels()), canvas.color.xsize * canvas.color.ysize, background_rgba); previous_rect_if_restore_to_background = image_rect; break; case DISPOSE_PREVIOUS: break; case DISPOSAL_UNSPECIFIED: default: std::fill_n(static_cast(canvas.color.pixels()), canvas.color.xsize * canvas.color.ysize, background_rgba); } } // Finally, if any frame has an alpha-channel, every frame will need // to have an alpha-channel. bool seen_alpha = false; for (const PackedFrame& frame : ppf->frames) { if (!frame.extra_channels.empty()) { seen_alpha = true; break; } } if (seen_alpha) { for (PackedFrame& frame : ppf->frames) { JXL_RETURN_IF_ERROR(ensure_have_alpha(&frame)); } } return true; #else return false; #endif } } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/dec/gif.h000066400000000000000000000015641472134335300166160ustar00rootroot00000000000000// 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_GIF_H_ #define LIB_EXTRAS_DEC_GIF_H_ // Decodes GIF 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/span.h" #include "lib/jxl/base/status.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, PackedPixelFile* ppf, const SizeConstraints* constraints = nullptr); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_DEC_GIF_H_ libjxl-0.11.1/lib/extras/dec/jpegli.cc000066400000000000000000000222401472134335300174530ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/extras/dec/jpegli.h000066400000000000000000000021131472134335300173120ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/extras/dec/jpg.cc000066400000000000000000000300061472134335300167600ustar00rootroot00000000000000// 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/jpg.h" #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" namespace jxl { namespace extras { #if JPEGXL_ENABLE_JPEG namespace { constexpr unsigned char kICCSignature[12] = { 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00}; constexpr int kICCMarker = JPEG_APP0 + 2; constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; constexpr int kExifMarker = JPEG_APP0 + 1; inline bool IsJPG(const Span bytes) { if (bytes.size() < 2) return false; if (bytes[0] != 0xFF || bytes[1] != 0xD8) return false; return true; } bool MarkerIsICC(const jpeg_saved_marker_ptr marker) { return marker->marker == kICCMarker && marker->data_length >= sizeof kICCSignature + 2 && std::equal(std::begin(kICCSignature), std::end(kICCSignature), marker->data); } 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) { constexpr size_t kICCSignatureSize = sizeof kICCSignature; // ICC signature + uint8_t index + uint8_t max_index. constexpr size_t kICCHeadSize = kICCSignatureSize + 2; // Markers are 1-indexed, and we keep them that way in this vector to get a // convenient 0 at the front for when we compute the offsets later. std::vector marker_lengths; int num_markers = 0; int seen_markers_count = 0; bool has_num_markers = false; 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 (!MarkerIsICC(marker)) continue; const int current_marker = marker->data[kICCSignatureSize]; if (current_marker == 0) { return JXL_FAILURE("inconsistent JPEG ICC marker numbering"); } const int current_num_markers = marker->data[kICCSignatureSize + 1]; if (current_marker > current_num_markers) { return JXL_FAILURE("inconsistent JPEG ICC marker numbering"); } if (has_num_markers) { if (current_num_markers != num_markers) { return JXL_FAILURE("inconsistent numbers of JPEG ICC markers"); } } else { num_markers = current_num_markers; has_num_markers = true; marker_lengths.resize(num_markers + 1); } size_t marker_length = marker->data_length - kICCHeadSize; if (marker_length == 0) { // NB: if we allow empty chunks, then the next check is incorrect. return JXL_FAILURE("Empty ICC chunk"); } if (marker_lengths[current_marker] != 0) { return JXL_FAILURE("duplicate JPEG ICC marker number"); } marker_lengths[current_marker] = marker_length; seen_markers_count++; } if (marker_lengths.empty()) { // Not an error. return false; } if (seen_markers_count != num_markers) { JXL_ENSURE(has_num_markers); return JXL_FAILURE("Incomplete set of ICC chunks"); } std::vector offsets = std::move(marker_lengths); std::partial_sum(offsets.begin(), offsets.end(), offsets.begin()); icc->resize(offsets.back()); for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr; marker = marker->next) { if (!MarkerIsICC(marker)) continue; const uint8_t* first = marker->data + kICCHeadSize; uint8_t current_marker = marker->data[kICCSignatureSize]; size_t offset = offsets[current_marker - 1]; size_t marker_length = offsets[current_marker] - offset; std::copy_n(first, marker_length, icc->data() + offset); } return true; } 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; } } void MyErrorExit(j_common_ptr cinfo) { jmp_buf* env = static_cast(cinfo->client_data); (*cinfo->err->output_message)(cinfo); jpeg_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 #endif bool CanDecodeJPG() { #if JPEGXL_ENABLE_JPEG return true; #else return false; #endif } Status DecodeImageJPG(const Span bytes, 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; // 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; const auto try_catch_block = [&]() -> bool { jpeg_decompress_struct cinfo = {}; // 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 = jpeg_std_error(&jerr); jerr.error_exit = &MyErrorExit; jerr.output_message = &MyOutputMessage; if (setjmp(env)) { return false; } cinfo.client_data = static_cast(&env); jpeg_create_decompress(&cinfo); jpeg_mem_src(&cinfo, reinterpret_cast(bytes.data()), bytes.size()); jpeg_save_markers(&cinfo, kICCMarker, 0xFFFF); jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF); const auto failure = [&cinfo](const char* str) -> Status { jpeg_abort_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); return JXL_FAILURE("%s", str); }; int read_header_result = jpeg_read_header(&cinfo, TRUE); // TODO(eustas): what about JPEG_HEADER_TABLES_ONLY? if (read_header_result == JPEG_SUSPENDED) { return failure("truncated JPEG input"); } if (!VerifyDimensions(constraints, cinfo.image_width, cinfo.image_height)) { return failure("image too big"); } // 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) { return failure("unsupported number of components in JPEG"); } 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 // `jpeg_start_decompress`. ppf->color_encoding.color_space = (nbcomp == 1) ? JXL_COLOR_SPACE_GRAY : 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_PERCEPTUAL; } ReadExif(&cinfo, &ppf->metadata.exif); if (!ApplyColorHints(color_hints, /*color_already_set=*/true, /*is_gray=*/false, ppf)) { return failure("ApplyColorHints failed"); } ppf->info.xsize = cinfo.image_width; ppf->info.ysize = cinfo.image_height; // Original data is uint, so exponent_bits_per_sample = 0. ppf->info.bits_per_sample = BITS_IN_JSAMPLE; static_assert(BITS_IN_JSAMPLE == 8 || BITS_IN_JSAMPLE == 16); ppf->info.exponent_bits_per_sample = 0; ppf->info.uses_original_profile = JXL_TRUE; // No alpha in JPG ppf->info.alpha_bits = 0; ppf->info.alpha_exponent_bits = 0; 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_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, /*endianness=*/JXL_NATIVE_ENDIAN, /*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 (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()) + frame.color.stride * y)}; 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); jpeg_destroy_decompress(&cinfo); return true; }; return try_catch_block(); #else return false; #endif } } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/dec/jpg.h000066400000000000000000000023531472134335300166260ustar00rootroot00000000000000// 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_JPG_H_ #define LIB_EXTRAS_DEC_JPG_H_ // Decodes JPG pixels and metadata in memory. #include #include "lib/extras/codec.h" #include "lib/extras/dec/color_hints.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.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, PackedPixelFile* ppf, const SizeConstraints* constraints = nullptr, const JPGDecompressParams* dparams = nullptr); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_DEC_JPG_H_ libjxl-0.11.1/lib/extras/dec/jxl.cc000066400000000000000000000560601472134335300170050ustar00rootroot00000000000000// 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/jxl.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/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 { #define QUIT(M) \ fprintf(stderr, "%s\n", M); \ return false; 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; 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); next_out_ = box_data_->data() + total_size_; avail_out_ = box_data_->size() - total_size_; if (JXL_DEC_SUCCESS != JxlDecoderSetBoxBuffer(dec_, next_out_, avail_out_)) { fprintf(stderr, "JxlDecoderSetBoxBuffer failed\n"); return false; } return true; } void FinalizeOutput() { if (box_data_ == nullptr) return; Flush(); box_data_->resize(total_size_); Reset(); } private: JxlDecoder* dec_; std::vector* box_data_; uint8_t* next_out_; size_t avail_out_; size_t total_size_; void Reset() { box_data_ = nullptr; next_out_ = nullptr; avail_out_ = 0; total_size_ = 0; } void Flush() { if (box_data_ == nullptr) return; size_t remaining = JxlDecoderReleaseBoxBuffer(dec_); size_t bytes_written = avail_out_ - remaining; next_out_ += bytes_written; avail_out_ -= bytes_written; total_size_ += bytes_written; } }; 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) { 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(); if (dparams.runner_opaque != nullptr && JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec, dparams.runner, dparams.runner_opaque)) { fprintf(stderr, "JxlEncoderSetParallelRunner failed\n"); return false; } JxlPixelFormat format = {}; // Initialize to calm down clang-tidy. std::vector accepted_formats = dparams.accepted_formats; JxlColorEncoding color_encoding; size_t num_color_channels = 0; if (!dparams.color_space.empty()) { if (!jxl::ParseDescription(dparams.color_space, &color_encoding)) { fprintf(stderr, "Failed to parse color space %s.\n", dparams.color_space.c_str()); return false; } num_color_channels = color_encoding.color_space == JXL_COLOR_SPACE_GRAY ? 1 : 3; } bool can_reconstruct_jpeg = false; std::vector jpeg_data_chunk; if (jpeg_bytes != nullptr) { // 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); } int events = (JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE); bool max_passes_defined = (dparams.max_passes < std::numeric_limits::max()); if (max_passes_defined || dparams.max_downsampling > 1) { events |= JXL_DEC_FRAME_PROGRESSION; if (max_passes_defined) { JxlDecoderSetProgressiveDetail(dec, JxlProgressiveDetail::kPasses); } else { JxlDecoderSetProgressiveDetail(dec, JxlProgressiveDetail::kLastPasses); } } if (jpeg_bytes != nullptr) { events |= JXL_DEC_JPEG_RECONSTRUCTION; } 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, TO_JXL_BOOL(dparams.render_spotcolors))) { fprintf(stderr, "JxlDecoderSetRenderSpotColors failed\n"); return false; } 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, TO_JXL_BOOL(dparams.unpremultiply_alpha))) { fprintf(stderr, "JxlDecoderSetUnpremultiplyAlpha failed\n"); return false; } if (dparams.display_nits > 0 && JXL_DEC_SUCCESS != JxlDecoderSetDesiredIntensityTarget(dec, dparams.display_nits)) { fprintf(stderr, "Decoder failed to set desired intensity target\n"); return false; } if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec, JXL_TRUE)) { fprintf(stderr, "JxlDecoderSetDecompressBoxes failed\n"); return false; } } if (JXL_DEC_SUCCESS != JxlDecoderSetInput(dec, bytes, bytes_size)) { fprintf(stderr, "Decoder failed to set input\n"); return false; } uint32_t progression_index = 0; bool codestream_done = jpeg_bytes == nullptr && accepted_formats.empty(); BoxProcessor boxes(dec); for (;;) { JxlDecoderStatus status = JxlDecoderProcessInput(dec); if (status == JXL_DEC_ERROR) { fprintf(stderr, "Failed to decode image\n"); return false; } else if (status == JXL_DEC_NEED_MORE_INPUT) { if (codestream_done) { break; } if (dparams.allow_partial_input) { if (JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec)) { fprintf(stderr, "Input file is truncated and there is no preview " "available yet.\n"); return false; } break; } size_t released_size = JxlDecoderReleaseInput(dec); fprintf(stderr, "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(); JxlBoxType box_type; if (JXL_DEC_SUCCESS != JxlDecoderGetBoxType(dec, box_type, JXL_TRUE)) { fprintf(stderr, "JxlDecoderGetBoxType failed\n"); return false; } std::vector* box_data = nullptr; if (memcmp(box_type, "Exif", 4) == 0) { box_data = &ppf->metadata.exif; } else if (memcmp(box_type, "iptc", 4) == 0) { 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) { if (!boxes.InitializeOutput(box_data)) { return false; } } } else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) { if (!boxes.AddMoreOutput()) { return false; } } else if (status == JXL_DEC_JPEG_RECONSTRUCTION) { can_reconstruct_jpeg = true; // Decoding to JPEG. if (JXL_DEC_SUCCESS != JxlDecoderSetJPEGBuffer(dec, jpeg_data_chunk.data(), jpeg_data_chunk.size())) { fprintf(stderr, "Decoder failed to set JPEG Buffer\n"); 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); jpeg_bytes->insert(jpeg_bytes->end(), jpeg_data_chunk.data(), jpeg_data_chunk.data() + used_jpeg_output); if (used_jpeg_output == 0) { // Chunk is too small. jpeg_data_chunk.resize(jpeg_data_chunk.size() * 2); } if (JXL_DEC_SUCCESS != JxlDecoderSetJPEGBuffer(dec, jpeg_data_chunk.data(), jpeg_data_chunk.size())) { fprintf(stderr, "Decoder failed to set JPEG Buffer\n"); return false; } } else if (status == JXL_DEC_BASIC_INFO) { if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec, &ppf->info)) { 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"); return false; } bool have_alpha = (format.num_channels == 2 || format.num_channels == 4); 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 = JXL_FALSE; } } bool alpha_found = false; for (uint32_t i = 0; i < ppf->info.num_extra_channels; ++i) { JxlExtraChannelInfo eci; if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec, i, &eci)) { fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n"); return false; } if (eci.type == JXL_CHANNEL_ALPHA && have_alpha && !alpha_found) { // Skip the first alpha channels because it is already present in the // interleaved image. alpha_found = true; continue; } std::string name(eci.name_length + 1, 0); if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelName( dec, i, const_cast(name.data()), name.size())) { fprintf(stderr, "JxlDecoderGetExtraChannelName failed\n"); return false; } name.resize(eci.name_length); ppf->extra_channels_info.push_back({eci, i, name}); } } else if (status == JXL_DEC_COLOR_ENCODING) { if (!dparams.color_space.empty()) { if (ppf->info.uses_original_profile) { fprintf(stderr, "Warning: --color_space ignored because the image is " "not XYB encoded.\n"); } else { JxlDecoderSetCms(dec, *JxlGetDefaultCms()); if (JXL_DEC_SUCCESS != JxlDecoderSetPreferredColorProfile(dec, &color_encoding)) { fprintf(stderr, "Failed to set color space.\n"); return false; } } } size_t icc_size = 0; JxlColorProfileTarget target = JXL_COLOR_PROFILE_TARGET_DATA; if (JXL_DEC_SUCCESS != 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, target, ppf->icc.data(), icc_size)) { fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n"); return false; } } if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsEncodedProfile( 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, target, &icc_size)) { fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n"); } if (icc_size != 0) { ppf->orig_icc.resize(icc_size); if (JXL_DEC_SUCCESS != 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) { 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, const_cast(frame.name.data()), frame.name.size())) { fprintf(stderr, "JxlDecoderGetFrameName failed\n"); return false; } frame.name.resize(frame.frame_info.name_length); ppf->frames.emplace_back(std::move(frame)); progression_index = 0; } else if (status == JXL_DEC_FRAME_PROGRESSION) { size_t downsampling = JxlDecoderGetIntendedDownsamplingRatio(dec); if ((max_passes_defined && progression_index >= dparams.max_passes) || (!max_passes_defined && downsampling <= dparams.max_downsampling)) { if (JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec)) { fprintf(stderr, "JxlDecoderFlushImage failed\n"); return false; } if (ppf->frames.back().frame_info.is_last) { break; } if (JXL_DEC_SUCCESS != JxlDecoderSkipCurrentFrame(dec)) { fprintf(stderr, "JxlDecoderSkipCurrentFrame failed\n"); return false; } } ++progression_index; } else if (status == JXL_DEC_NEED_PREVIEW_OUT_BUFFER) { size_t buffer_size; if (JXL_DEC_SUCCESS != JxlDecoderPreviewOutBufferSize(dec, &format, &buffer_size)) { fprintf(stderr, "JxlDecoderPreviewOutBufferSize failed\n"); return false; } 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); return false; } if (JXL_DEC_SUCCESS != JxlDecoderSetPreviewOutBuffer( dec, &format, ppf->preview_frame->color.pixels(), buffer_size)) { fprintf(stderr, "JxlDecoderSetPreviewOutBuffer failed\n"); return false; } } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { if (jpeg_bytes != nullptr) { break; } size_t buffer_size; if (JXL_DEC_SUCCESS != JxlDecoderImageOutBufferSize(dec, &format, &buffer_size)) { fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n"); return false; } jxl::extras::PackedFrame& frame = ppf->frames.back(); if (buffer_size != frame.color.pixels_size) { fprintf(stderr, "Invalid out buffer size %" PRIuS " %" PRIuS "\n", buffer_size, frame.color.pixels_size); return false; } if (dparams.use_image_callback) { auto callback = [](void* opaque, size_t x, size_t y, size_t num_pixels, const void* pixels) { auto* ppf = reinterpret_cast(opaque); jxl::extras::PackedImage& color = ppf->frames.back().color; uint8_t* pixels_buffer = reinterpret_cast(color.pixels()); size_t sample_size = color.pixel_stride(); memcpy(pixels_buffer + (color.stride * y + sample_size * x), pixels, num_pixels * sample_size); }; if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutCallback(dec, &format, callback, ppf)) { fprintf(stderr, "JxlDecoderSetImageOutCallback failed\n"); return false; } } else { if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec, &format, frame.color.pixels(), buffer_size)) { fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n"); 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 (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( dec, &ec_format, &buffer_size, eci.index)) { fprintf(stderr, "JxlDecoderExtraChannelBufferSize failed\n"); return false; } if (buffer_size != ec.pixels_size) { fprintf(stderr, "Invalid extra channel buffer size" " %" PRIuS " %" PRIuS "\n", buffer_size, ec.pixels_size); return false; } if (JXL_DEC_SUCCESS != JxlDecoderSetExtraChannelBuffer(dec, &ec_format, ec.pixels(), buffer_size, eci.index)) { 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. break; } else if (status == JXL_DEC_PREVIEW_IMAGE) { // Nothing to do. } else if (status == JXL_DEC_FULL_IMAGE) { if (jpeg_bytes != nullptr || ppf->frames.back().frame_info.is_last) { codestream_done = true; } } else { fprintf(stderr, "Error: unexpected status: %d\n", static_cast(status)); return false; } } 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 = jpeg_data_chunk.size() - JxlDecoderReleaseJPEGBuffer(dec); jpeg_bytes->insert(jpeg_bytes->end(), jpeg_data_chunk.data(), jpeg_data_chunk.data() + used_jpeg_output); } if (decoded_bytes) { *decoded_bytes = bytes_size - JxlDecoderReleaseInput(dec); } return true; } } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/dec/jxl.h000066400000000000000000000045201472134335300166410ustar00rootroot00000000000000// 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_JXL_H_ #define LIB_EXTRAS_DEC_JXL_H_ // Decodes JPEG XL images in memory. #include #include #include #include #include #include #include #include #include "lib/extras/packed_image.h" namespace jxl { namespace extras { struct JXLDecompressParams { // If empty, little endian float formats will be accepted. std::vector accepted_formats; // Requested output color space description. std::string color_space; // If set, performs tone mapping to this intensity target luminance. float display_nits = 0.0; // Whether spot colors are rendered on the image. bool render_spotcolors = true; // Whether to keep or undo the orientation given in the header. bool keep_orientation = false; // If runner_opaque is set, the decoder uses this parallel runner. 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; // How many passes to decode at most. By default, decode everything. uint32_t max_passes = std::numeric_limits::max(); // Alternatively, one can specify the maximum tolerable downscaling factor // with respect to the full size of the image. By default, nothing less than // the full size is requested. size_t max_downsampling = 1; // Whether to use the image callback or the image buffer to get the output. 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, const JXLDecompressParams& dparams, size_t* decoded_bytes, PackedPixelFile* ppf, std::vector* jpeg_bytes = nullptr); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_DEC_JXL_H_ libjxl-0.11.1/lib/extras/dec/pgx.cc000066400000000000000000000144461472134335300170100ustar00rootroot00000000000000// 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/pgx.h" #include #include "lib/extras/size_constraints.h" #include "lib/jxl/base/bits.h" #include "lib/jxl/base/compiler_specific.h" namespace jxl { namespace extras { namespace { struct HeaderPGX { // NOTE: PGX is always grayscale size_t xsize; size_t ysize; size_t bits_per_sample; bool big_endian; bool is_signed; }; class Parser { public: explicit Parser(const Span bytes) : pos_(bytes.data()), end_(pos_ + bytes.size()) {} // Sets "pos" to the first non-header byte/pixel on success. Status ParseHeader(HeaderPGX* header, const uint8_t** pos) { // codec.cc ensures we have at least two bytes => no range check here. if (pos_[0] != 'P' || pos_[1] != 'G') return false; pos_ += 2; return ParseHeaderPGX(header, pos); } // Exposed for testing Status ParseUnsigned(size_t* number) { if (pos_ == end_) return JXL_FAILURE("PGX: reached end before number"); if (!IsDigit(*pos_)) return JXL_FAILURE("PGX: expected unsigned number"); *number = 0; while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') { *number *= 10; *number += *pos_ - '0'; ++pos_; } return true; } private: static bool IsDigit(const uint8_t c) { return '0' <= c && c <= '9'; } 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 == ' '; } Status SkipSpace() { if (pos_ == end_) return JXL_FAILURE("PGX: reached end before space"); const uint8_t c = *pos_; if (c != ' ') return JXL_FAILURE("PGX: expected space"); ++pos_; return true; } Status SkipLineBreak() { if (pos_ == end_) return JXL_FAILURE("PGX: reached end before line break"); // Line break can be either "\n" (0a) or "\r\n" (0d 0a). if (*pos_ == '\n') { pos_++; return true; } else if (*pos_ == '\r' && pos_ + 1 != end_ && *(pos_ + 1) == '\n') { pos_ += 2; return true; } return JXL_FAILURE("PGX: expected line break"); } Status SkipSingleWhitespace() { if (pos_ == end_) return JXL_FAILURE("PGX: reached end before whitespace"); if (!IsWhitespace(*pos_)) return JXL_FAILURE("PGX: expected whitespace"); ++pos_; return true; } Status ParseHeaderPGX(HeaderPGX* header, const uint8_t** pos) { JXL_RETURN_IF_ERROR(SkipSpace()); if (pos_ + 2 > end_) return JXL_FAILURE("PGX: header too small"); if (*pos_ == 'M' && *(pos_ + 1) == 'L') { header->big_endian = true; } else if (*pos_ == 'L' && *(pos_ + 1) == 'M') { header->big_endian = false; } else { return JXL_FAILURE("PGX: invalid endianness"); } pos_ += 2; JXL_RETURN_IF_ERROR(SkipSpace()); if (pos_ == end_) return JXL_FAILURE("PGX: header too small"); if (*pos_ == '+') { header->is_signed = false; } else if (*pos_ == '-') { header->is_signed = true; } else { return JXL_FAILURE("PGX: invalid signedness"); } pos_++; // Skip optional space if (pos_ < end_ && *pos_ == ' ') pos_++; JXL_RETURN_IF_ERROR(ParseUnsigned(&header->bits_per_sample)); JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); JXL_RETURN_IF_ERROR(ParseUnsigned(&header->xsize)); JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize)); // 0xa, or 0xd 0xa. JXL_RETURN_IF_ERROR(SkipLineBreak()); // TODO(jon): could do up to 24-bit by converting the values to // JXL_TYPE_FLOAT. if (header->bits_per_sample > 16) { return JXL_FAILURE("PGX: >16 bits not yet supported"); } // TODO(lode): support signed integers. This may require changing the way // external_image works. if (header->is_signed) { return JXL_FAILURE("PGX: signed not yet supported"); } size_t numpixels = header->xsize * header->ysize; size_t bytes_per_pixel = header->bits_per_sample <= 8 ? 1 : 2; if (pos_ + numpixels * bytes_per_pixel > end_) { return JXL_FAILURE("PGX: data too small"); } *pos = pos_; return true; } const uint8_t* pos_; const uint8_t* const end_; }; } // namespace Status DecodeImagePGX(const Span bytes, const ColorHints& color_hints, PackedPixelFile* ppf, const SizeConstraints* constraints) { Parser parser(bytes); HeaderPGX header = {}; const uint8_t* pos = nullptr; if (!parser.ParseHeader(&header, &pos)) return false; JXL_RETURN_IF_ERROR( 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"); } JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, /*color_already_set=*/false, /*is_gray=*/true, ppf)); ppf->info.xsize = header.xsize; ppf->info.ysize = header.ysize; // 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 = JXL_TRUE; // No alpha in PGX ppf->info.alpha_bits = 0; ppf->info.alpha_exponent_bits = 0; ppf->info.num_color_channels = 1; // Always grayscale ppf->info.orientation = JXL_ORIENT_IDENTITY; JxlDataType data_type; if (header.bits_per_sample > 8) { data_type = JXL_TYPE_UINT16; } else { data_type = JXL_TYPE_UINT8; } const JxlPixelFormat format{ /*num_channels=*/1, /*data_type=*/data_type, /*endianness=*/header.big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN, /*align=*/0, }; ppf->frames.clear(); // Allocates the frame buffer. { 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) { return JXL_FAILURE("PGX file too small"); } memcpy(frame.color.pixels(), pos, frame.color.pixels_size); return true; } } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/dec/pgx.h000066400000000000000000000015311472134335300166410ustar00rootroot00000000000000// 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_PGX_H_ #define LIB_EXTRAS_DEC_PGX_H_ // Decodes PGX pixels 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/span.h" #include "lib/jxl/base/status.h" namespace jxl { struct SizeConstraints; namespace extras { // Decodes `bytes` into `ppf`. Status DecodeImagePGX(Span bytes, const ColorHints& color_hints, PackedPixelFile* ppf, const SizeConstraints* constraints = nullptr); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_DEC_PGX_H_ libjxl-0.11.1/lib/extras/dec/pgx_test.cc000066400000000000000000000055111472134335300200400ustar00rootroot00000000000000// 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/pgx.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 Bytes(reinterpret_cast(str), strlen(str)); } TEST(CodecPGXTest, Test8bits) { std::string pgx = "PG ML + 8 2 3\npixels"; PackedPixelFile ppf; ThreadPool* pool = nullptr; 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()); EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample); EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample); EXPECT_TRUE(io.metadata.m.color_encoding.IsGray()); EXPECT_EQ(2u, io.xsize()); EXPECT_EQ(3u, io.ysize()); float eps = 1e-5; EXPECT_NEAR('p', io.Main().color()->Plane(0).Row(0)[0], eps); EXPECT_NEAR('i', io.Main().color()->Plane(0).Row(0)[1], eps); EXPECT_NEAR('x', io.Main().color()->Plane(0).Row(1)[0], eps); EXPECT_NEAR('e', io.Main().color()->Plane(0).Row(1)[1], eps); EXPECT_NEAR('l', io.Main().color()->Plane(0).Row(2)[0], eps); EXPECT_NEAR('s', io.Main().color()->Plane(0).Row(2)[1], eps); } TEST(CodecPGXTest, Test16bits) { std::string pgx = "PG ML + 16 2 3\np_i_x_e_l_s_"; PackedPixelFile ppf; ThreadPool* pool = nullptr; 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()); EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample); EXPECT_EQ(16u, io.metadata.m.bit_depth.bits_per_sample); EXPECT_TRUE(io.metadata.m.color_encoding.IsGray()); EXPECT_EQ(2u, io.xsize()); EXPECT_EQ(3u, io.ysize()); // Comparing ~16-bit numbers in floats, only ~7 bits left. float eps = 1e-3; const auto& plane = io.Main().color()->Plane(0); EXPECT_NEAR(256.0f * 'p' + '_', plane.Row(0)[0] * 257, eps); EXPECT_NEAR(256.0f * 'i' + '_', plane.Row(0)[1] * 257, eps); EXPECT_NEAR(256.0f * 'x' + '_', plane.Row(1)[0] * 257, eps); EXPECT_NEAR(256.0f * 'e' + '_', plane.Row(1)[1] * 257, eps); EXPECT_NEAR(256.0f * 'l' + '_', plane.Row(2)[0] * 257, eps); EXPECT_NEAR(256.0f * 's' + '_', plane.Row(2)[1] * 257, eps); } } // namespace } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/dec/pnm.cc000066400000000000000000000451151472134335300170010ustar00rootroot00000000000000// 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/pnm.h" #include #include #include #include #include #include #include "lib/extras/size_constraints.h" #include "lib/jxl/base/bits.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 { class Parser { public: explicit Parser(const Span bytes) : pos_(bytes.data()), end_(pos_ + bytes.size()) {} // Sets "pos" to the first non-header byte/pixel on success. Status ParseHeader(HeaderPNM* header, const uint8_t** pos) { // codec.cc ensures we have at least two bytes => no range check here. if (pos_[0] != 'P') return false; const uint8_t type = pos_[1]; pos_ += 2; switch (type) { case '4': return JXL_FAILURE("pbm not supported"); case '5': header->is_gray = true; return ParseHeaderPNM(header, pos); case '6': header->is_gray = false; return ParseHeaderPNM(header, pos); case '7': return ParseHeaderPAM(header, pos); case 'F': header->is_gray = false; return ParseHeaderPFM(header, pos); case 'f': header->is_gray = true; return ParseHeaderPFM(header, pos); default: return false; } } // Exposed for testing Status ParseUnsigned(size_t* number) { if (pos_ == end_) return JXL_FAILURE("PNM: reached end before number"); if (!IsDigit(*pos_)) return JXL_FAILURE("PNM: expected unsigned number"); *number = 0; while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') { *number *= 10; *number += *pos_ - '0'; ++pos_; } return true; } Status ParseSigned(double* number) { if (pos_ == end_) return JXL_FAILURE("PNM: reached end before signed"); if (*pos_ != '-' && *pos_ != '+' && !IsDigit(*pos_)) { return JXL_FAILURE("PNM: expected signed number"); } // Skip sign const bool is_neg = *pos_ == '-'; if (is_neg || *pos_ == '+') { ++pos_; if (pos_ == end_) return JXL_FAILURE("PNM: reached end before digits"); } // Leading digits *number = 0.0; while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') { *number *= 10; *number += *pos_ - '0'; ++pos_; } // Decimal places? if (pos_ < end_ && *pos_ == '.') { ++pos_; double place = 0.1; while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') { *number += (*pos_ - '0') * place; place *= 0.1; ++pos_; } } if (is_neg) *number = -*number; return true; } private: static bool IsDigit(const uint8_t c) { return '0' <= c && c <= '9'; } 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 == ' '; } Status SkipBlank() { if (pos_ == end_) return JXL_FAILURE("PNM: reached end before blank"); const uint8_t c = *pos_; if (c != ' ' && c != '\n') return JXL_FAILURE("PNM: expected blank"); ++pos_; return true; } Status SkipSingleWhitespace() { if (pos_ == end_) return JXL_FAILURE("PNM: reached end before whitespace"); if (!IsWhitespace(*pos_)) return JXL_FAILURE("PNM: expected whitespace"); ++pos_; return true; } Status SkipWhitespace() { if (pos_ == end_) return JXL_FAILURE("PNM: reached end before whitespace"); if (!IsWhitespace(*pos_) && *pos_ != '#') { return JXL_FAILURE("PNM: expected whitespace/comment"); } while (pos_ < end_ && IsWhitespace(*pos_)) { ++pos_; } // Comment(s) while (pos_ != end_ && *pos_ == '#') { while (pos_ != end_ && !IsLineBreak(*pos_)) { ++pos_; } // Newline(s) while (pos_ != end_ && IsLineBreak(*pos_)) pos_++; } while (pos_ < end_ && IsWhitespace(*pos_)) { ++pos_; } return true; } Status MatchString(const char* keyword, bool skipws = true) { const uint8_t* ppos = pos_; const uint8_t* kw = reinterpret_cast(keyword); while (*kw) { if (ppos >= end_) return JXL_FAILURE("PAM: unexpected end of input"); if (*kw != *ppos) return false; ppos++; kw++; } pos_ = ppos; if (skipws) { JXL_RETURN_IF_ERROR(SkipWhitespace()); } else { JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); } return true; } Status ParseHeaderPAM(HeaderPNM* header, const uint8_t** pos) { size_t depth = 3; size_t max_val = 255; JXL_RETURN_IF_ERROR(SkipWhitespace()); while (!MatchString("ENDHDR", /*skipws=*/false)) { 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; } else if (MatchString("RGB")) { } else if (MatchString("GRAYSCALE_ALPHA")) { header->has_alpha = true; header->is_gray = true; } else if (MatchString("GRAYSCALE")) { header->is_gray = true; } else if (MatchString("BLACKANDWHITE_ALPHA")) { header->has_alpha = true; header->is_gray = true; max_val = 1; } else if (MatchString("BLACKANDWHITE")) { header->is_gray = true; max_val = 1; } else 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"); } } else { constexpr size_t kMaxHeaderLength = 20; char unknown_header[kMaxHeaderLength + 1]; size_t len = std::min(kMaxHeaderLength, end_ - pos_); strncpy(unknown_header, reinterpret_cast(pos_), len); unknown_header[len] = 0; return JXL_FAILURE("PAM: unknown header keyword: %s", unknown_header); } } size_t num_channels = header->is_gray ? 1 : 3; if (header->has_alpha) num_channels++; 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: header->bits_per_sample = FloorLog2Nonzero(max_val) + 1; if ((1u << header->bits_per_sample) - 1 != max_val) return JXL_FAILURE("PNM: unsupported MaxVal (expected 2^n - 1)"); // PAM does not pack bits as in PBM. header->floating_point = false; header->big_endian = true; *pos = pos_; return true; } Status ParseHeaderPNM(HeaderPNM* header, const uint8_t** pos) { JXL_RETURN_IF_ERROR(SkipWhitespace()); JXL_RETURN_IF_ERROR(ParseUnsigned(&header->xsize)); JXL_RETURN_IF_ERROR(SkipWhitespace()); JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize)); JXL_RETURN_IF_ERROR(SkipWhitespace()); size_t max_val; JXL_RETURN_IF_ERROR(ParseUnsigned(&max_val)); if (max_val == 0 || max_val >= 65536) { return JXL_FAILURE("PNM: bad MaxVal"); } header->bits_per_sample = FloorLog2Nonzero(max_val) + 1; if ((1u << header->bits_per_sample) - 1 != max_val) return JXL_FAILURE("PNM: unsupported MaxVal (expected 2^n - 1)"); header->floating_point = false; header->big_endian = true; JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); *pos = pos_; return true; } Status ParseHeaderPFM(HeaderPNM* header, const uint8_t** pos) { JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); JXL_RETURN_IF_ERROR(ParseUnsigned(&header->xsize)); JXL_RETURN_IF_ERROR(SkipBlank()); JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize)); JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); // The scale has no meaning as multiplier, only its sign is used to // indicate endianness. All software expects nominal range 0..1. double scale; JXL_RETURN_IF_ERROR(ParseSigned(&scale)); if (scale == 0.0) { return JXL_FAILURE("PFM: bad scale factor value."); } else if (std::abs(scale) != 1.0) { JXL_WARNING("PFM: Discarding non-unit scale factor"); } header->big_endian = scale > 0.0; header->bits_per_sample = 32; header->floating_point = true; JXL_RETURN_IF_ERROR(SkipSingleWhitespace()); *pos = pos_; return true; } const uint8_t* pos_; const uint8_t* const end_; }; } // 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, 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)); if (header.bits_per_sample == 0 || header.bits_per_sample > 32) { return JXL_FAILURE("PNM: bits_per_sample invalid"); } // 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; if (header.floating_point) { ppf->info.bits_per_sample = 32; ppf->info.exponent_bits_per_sample = 8; } else { ppf->info.bits_per_sample = header.bits_per_sample; ppf->info.exponent_bits_per_sample = 0; } ppf->info.orientation = JXL_ORIENT_IDENTITY; // No alpha in PNM and PFM ppf->info.alpha_bits = (header.has_alpha ? ppf->info.bits_per_sample : 0); ppf->info.alpha_exponent_bits = 0; ppf->info.num_color_channels = (header.is_gray ? 1 : 3); 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) { // There's no float16 pnm version. data_type = JXL_TYPE_FLOAT; } else { if (header.bits_per_sample > 8) { data_type = JXL_TYPE_UINT16; } else { data_type = JXL_TYPE_UINT8; } } const JxlPixelFormat format{ /*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(); { 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"); } uint8_t* out = reinterpret_cast(frame->color.pixels()); 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; } // Exposed for testing. Status PnmParseSigned(Bytes str, double* v) { return Parser(str).ParseSigned(v); } Status PnmParseUnsigned(Bytes str, size_t* v) { return Parser(str).ParseUnsigned(v); } } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/dec/pnm.h000066400000000000000000000031731472134335300166410ustar00rootroot00000000000000// 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_PNM_H_ #define LIB_EXTRAS_DEC_PNM_H_ // Decodes PBM/PGM/PPM/PFM pixels in memory. #include #include // TODO(janwas): workaround for incorrect Win64 codegen (cause unknown) #include #include "lib/extras/dec/color_hints.h" #include "lib/extras/mmap.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.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, PackedPixelFile* ppf, const SizeConstraints* constraints = nullptr); 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 #endif // LIB_EXTRAS_DEC_PNM_H_ libjxl-0.11.1/lib/extras/enc/000077500000000000000000000000001472134335300157045ustar00rootroot00000000000000libjxl-0.11.1/lib/extras/enc/apng.cc000066400000000000000000000447641472134335300171570ustar00rootroot00000000000000// 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/apng.h" // Parts of this code are taken from apngdis, which has the following license: /* APNG Disassembler 2.8 * * Deconstructs APNG files into individual frames. * * http://apngdis.sourceforge.net * * Copyright (c) 2010-2015 Max Stepin * maxst at users.sourceforge.net * * zlib license * ------------ * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. * */ #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, 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); 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, bool encode_extra_channels = false, size_t extra_channel_index = 0) const; }; 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); } // Stores XMP and EXIF/IPTC into key/value strings for PNG class BlobsWriterPNG { public: static Status Encode(const PackedMetadata& blobs, std::vector* strings) { if (!blobs.exif.empty()) { // PNG viewers typically ignore Exif orientation but not all of them do // (and e.g. cjxl doesn't), so we overwrite the Exif orientation to the // identity to avoid repeated orientation. std::vector exif = blobs.exif; ResetExifOrientation(exif); // 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) { 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, const std::vector& bytes, std::vector* strings) { // Encoding: base16 with newline after 72 chars. const size_t base16_size = 2 * bytes.size() + DivCeil(bytes.size(), static_cast(36)) + 1; std::string base16; base16.reserve(base16_size); for (size_t i = 0; i < bytes.size(); ++i) { if (i % 36 == 0) base16.push_back('\n'); base16.push_back(EncodeNibble(bytes[i] >> 4)); base16.push_back(EncodeNibble(bytes[i] & 0x0F)); } base16.push_back('\n'); JXL_ENSURE(base16.length() == base16_size); char key[30]; snprintf(key, sizeof(key), "Raw profile type %s", type.c_str()); char header[30]; snprintf(header, sizeof(header), "\n%s\n%8" PRIuS, type.c_str(), bytes.size()); strings->emplace_back(key); strings->push_back(std::string(header) + base16); return true; } }; void MaybeAddCICP(const JxlColorEncoding& c_enc, png_structp png_ptr, png_infop info_ptr) { png_byte cicp_data[4] = {}; png_unknown_chunk cicp_chunk; if (c_enc.color_space != JXL_COLOR_SPACE_RGB) { return; } if (c_enc.primaries == JXL_PRIMARIES_P3) { if (c_enc.white_point == JXL_WHITE_POINT_D65) { cicp_data[0] = 12; } else if (c_enc.white_point == JXL_WHITE_POINT_DCI) { cicp_data[0] = 11; } else { return; } } else if (c_enc.primaries != JXL_PRIMARIES_CUSTOM && c_enc.white_point == JXL_WHITE_POINT_D65) { cicp_data[0] = static_cast(c_enc.primaries); } else { return; } if (c_enc.transfer_function == JXL_TRANSFER_FUNCTION_UNKNOWN || c_enc.transfer_function == JXL_TRANSFER_FUNCTION_GAMMA) { return; } cicp_data[1] = static_cast(c_enc.transfer_function); cicp_data[2] = 0; cicp_data[3] = 1; cicp_chunk.data = cicp_data; cicp_chunk.size = sizeof(cicp_data); cicp_chunk.location = PNG_HAVE_IHDR; memcpy(cicp_chunk.name, "cICP", 5); 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, 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); if (!ppf.info.have_animation && ppf.frames.size() != 1) { return JXL_FAILURE("Invalid number of frames"); } size_t count = 0; size_t anim_chunks = 0; for (const auto& frame : ppf.frames) { const PackedImage& color = encode_extra_channels ? frame.extra_channels[extra_channel_index] : 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; size_t out_stride = xsize * num_channels * out_bytes_per_sample; size_t out_size = ysize * out_stride; std::vector out(out_size); if (format.data_type == JXL_TYPE_UINT8) { 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(std::lroundf(in[i] * mul)); } } else { memcpy(out.data(), in, out_size); } } else if (format.data_type == JXL_TYPE_UINT16) { 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(std::lroundf(val * mul)), p_out); } } else { memcpy(out.data(), in, out_size); } } else if (format.data_type == JXL_TYPE_FLOAT) { 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 += 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); } } png_structp png_ptr; png_infop info_ptr; 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, nullptr); png_set_flush(png_ptr, 0); int width = xsize; int height = ysize; png_byte color_type = (is_gray ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_RGB); if (has_alpha) color_type |= PNG_COLOR_MASK_ALPHA; png_byte bit_depth = out_bytes_per_sample * 8; 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 && !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) { png_text text; text.key = const_cast(textstrings[kk].c_str()); text.text = const_cast(textstrings[kk + 1].c_str()); text.compression = PNG_TEXT_COMPRESSION_zTXt; png_set_text(png_ptr, info_ptr, &text, 1); } png_write_info(png_ptr, info_ptr); } else { // fake writing a header, otherwise libpng gets confused size_t pos = bytes->size(); png_write_info(png_ptr, info_ptr); bytes->resize(pos); } if (ppf.info.have_animation) { if (count == 0) { png_byte adata[8]; png_save_uint_32(adata, ppf.frames.size()); png_save_uint_32(adata + 4, ppf.info.animation.num_loops); png_byte actl[5] = "acTL"; png_write_chunk(png_ptr, actl, adata, 8); } png_byte fdata[26]; // TODO(jon): also make this work for the non-coalesced case png_save_uint_32(fdata, anim_chunks++); png_save_uint_32(fdata + 4, width); png_save_uint_32(fdata + 8, height); png_save_uint_32(fdata + 12, 0); png_save_uint_32(fdata + 16, 0); png_save_uint_16(fdata + 20, frame.frame_info.duration * ppf.info.animation.tps_denominator); png_save_uint_16(fdata + 22, ppf.info.animation.tps_numerator); fdata[24] = 1; fdata[25] = 0; png_byte fctl[5] = "fcTL"; png_write_chunk(png_ptr, fctl, fdata, 26); } std::vector rows(height); for (int y = 0; y < height; ++y) { rows[y] = out.data() + y * out_stride; } png_write_flush(png_ptr); const size_t pos = bytes->size(); png_write_image(png_ptr, rows.data()); png_write_flush(png_ptr); if (count > 0) { std::vector fdata(4); png_save_uint_32(fdata.data(), anim_chunks++); size_t p = pos; while (p + 8 < bytes->size()) { size_t len = png_get_uint_32(bytes->data() + p); JXL_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; } bytes->resize(pos); png_byte fdat[5] = "fdAT"; png_write_chunk(png_ptr, fdat, fdata.data(), fdata.size()); } count++; if (count == ppf.frames.size() || !ppf.info.have_animation) { png_write_end(png_ptr, nullptr); } png_destroy_write_struct(&png_ptr, &info_ptr); } return true; } } // namespace #endif std::unique_ptr GetAPNGEncoder() { #if JPEGXL_ENABLE_APNG return jxl::make_unique(); #else return nullptr; #endif } } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/enc/apng.h000066400000000000000000000007461472134335300170110ustar00rootroot00000000000000// 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_APNG_H_ #define LIB_EXTRAS_ENC_APNG_H_ // Encodes APNG images in memory. #include #include "lib/extras/enc/encode.h" namespace jxl { namespace extras { std::unique_ptr GetAPNGEncoder(); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_ENC_APNG_H_ libjxl-0.11.1/lib/extras/enc/encode.cc000066400000000000000000000126011472134335300174500ustar00rootroot00000000000000// 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/encode.h" #include #include "lib/extras/enc/apng.h" #include "lib/extras/enc/exr.h" #include "lib/extras/enc/jpg.h" #include "lib/extras/enc/npy.h" #include "lib/extras/enc/pgx.h" #include "lib/extras/enc/pnm.h" namespace jxl { namespace extras { Status Encoder::VerifyBasicInfo(const JxlBasicInfo& info) { if (info.xsize == 0 || info.ysize == 0) { return JXL_FAILURE("Empty image"); } if (info.num_color_channels != 1 && info.num_color_channels != 3) { return JXL_FAILURE("Invalid number of color channels"); } if (info.alpha_bits > 0 && info.alpha_bits != info.bits_per_sample) { return JXL_FAILURE("Alpha bit depth does not match image bit depth"); } if (info.orientation != JXL_ORIENT_IDENTITY) { return JXL_FAILURE("Orientation must be identity"); } return true; } 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."); } if (image.stride != image.xsize * image.pixel_stride()) { return JXL_FAILURE("Invalid image stride."); } if (image.pixels_size != image.ysize * image.stride) { return JXL_FAILURE("Invalid image size."); } size_t info_num_channels = (info.num_color_channels + (info.alpha_bits > 0 ? 1 : 0)); if (image.xsize != info.xsize || image.ysize != info.ysize || image.format.num_channels != info_num_channels) { return JXL_FAILURE("Frame size does not match image size"); } return true; } 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 (extension == ".png" || extension == ".apng") return GetAPNGEncoder(); if (extension == ".jpg") return GetJPEGEncoder(); if (extension == ".jpeg") return GetJPEGEncoder(); 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 (extension == ".exr") return GetEXREncoder(); 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 libjxl-0.11.1/lib/extras/enc/encode.h000066400000000000000000000052561472134335300173220ustar00rootroot00000000000000// 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_ENCODE_H_ #define LIB_EXTRAS_ENC_ENCODE_H_ // Facade for image encoders. #include #include #include #include #include #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 EncodedImage { // One (if the format supports animations or the image has only one frame) or // more 1quential bitstreams. std::vector> bitstreams; // For each extra channel one or more sequential bitstreams. std::vector>> extra_channel_bitstreams; std::vector preview_bitstream; // If the format does not support embedding color profiles into the bitstreams // above, it will be present here, to be written as a separate file. If it // does support them, this field will be empty. std::vector icc; // Additional output for conformance testing, only filled in by NumPyEncoder. std::vector metadata; }; class Encoder { public: static std::unique_ptr FromExtension(std::string extension); 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) 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 VerifyFormat(const JxlPixelFormat& format) const; Status VerifyPackedImage(const PackedImage& image, const JxlBasicInfo& info) const; private: std::unordered_map options_; }; std::string ListOfEncodeCodecs(); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_ENC_ENCODE_H_ libjxl-0.11.1/lib/extras/enc/exr.cc000066400000000000000000000153761472134335300170250ustar00rootroot00000000000000// 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/exr.h" #if JPEGXL_ENABLE_EXR #include #include #include #include #endif #include #include #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; 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 // on macOS, where the definition for OpenEXR::Int64 was actually not equivalent // to uint64_t. This alternative should work in all cases. using ExrInt64 = decltype(std::declval().tellg()); class InMemoryOStream : public OpenEXR::OStream { public: // `bytes` must outlive the InMemoryOStream. explicit InMemoryOStream(std::vector* const bytes) : OStream(/*fileName=*/""), bytes_(*bytes) {} void write(const char c[], const int n) override { if (bytes_.size() < pos_ + n) { bytes_.resize(pos_ + n); } std::copy_n(c, n, bytes_.begin() + pos_); pos_ += n; } ExrInt64 tellp() override { return pos_; } void seekp(const ExrInt64 pos) override { if (bytes_.size() + 1 < pos) { bytes_.resize(pos - 1); } pos_ = pos; } private: std::vector& bytes_; size_t pos_ = 0; }; // Loads a Big-Endian float float LoadBEFloat(const uint8_t* p) { uint32_t u = LoadBE32(p); float result; memcpy(&result, &u, 4); return result; } // Loads a Little-Endian float float LoadLEFloat(const uint8_t* p) { uint32_t u = LoadLE32(p); float result; memcpy(&result, &u, 4); return result; } Status EncodeImageEXR(const PackedImage& image, const JxlBasicInfo& info, const JxlColorEncoding& c_enc, ThreadPool* pool, std::vector* bytes) { OpenEXR::setGlobalThreadCount(0); 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 = FROM_JXL_BOOL(info.alpha_premultiplied); if (info.num_color_channels != 3 || c_enc.color_space != JXL_COLOR_SPACE_RGB || c_enc.transfer_function != JXL_TRANSFER_FUNCTION_LINEAR) { return JXL_FAILURE("Unsupported color encoding for OpenEXR output."); } const size_t num_channels = 3 + (has_alpha ? 1 : 0); const JxlPixelFormat format = image.format; if (format.data_type != JXL_TYPE_FLOAT) { return JXL_FAILURE("Unsupported pixel format for OpenEXR output"); } const uint8_t* in = reinterpret_cast(image.pixels()); size_t in_stride = num_channels * 4 * xsize; OpenEXR::Header header(xsize, ysize); OpenEXR::Chromaticities chromaticities; chromaticities.red = Imath::V2f(c_enc.primaries_red_xy[0], c_enc.primaries_red_xy[1]); chromaticities.green = Imath::V2f(c_enc.primaries_green_xy[0], c_enc.primaries_green_xy[1]); chromaticities.blue = Imath::V2f(c_enc.primaries_blue_xy[0], c_enc.primaries_blue_xy[1]); chromaticities.white = Imath::V2f(c_enc.white_point_xy[0], c_enc.white_point_xy[1]); OpenEXR::addChromaticities(header, chromaticities); OpenEXR::addWhiteLuminance(header, info.intensity_target); auto loadFloat = format.endianness == JXL_BIG_ENDIAN ? LoadBEFloat : LoadLEFloat; auto loadAlpha = has_alpha ? loadFloat : [](const uint8_t* p) -> float { return 1.0f; }; // Ensure that the destructor of RgbaOutputFile has run before we look at the // size of `bytes`. { InMemoryOStream os(bytes); OpenEXR::RgbaOutputFile output( os, header, has_alpha ? OpenEXR::WRITE_RGBA : OpenEXR::WRITE_RGB); // How many rows to write at once. Again, the OpenEXR documentation // recommends writing the whole image in one call. const int y_chunk_size = ysize; std::vector output_rows(xsize * y_chunk_size); for (size_t start_y = 0; start_y < ysize; start_y += y_chunk_size) { // Inclusive. const size_t end_y = std::min(start_y + y_chunk_size - 1, ysize - 1); output.setFrameBuffer(output_rows.data() - start_y * xsize, /*xStride=*/1, /*yStride=*/xsize); for (size_t y = start_y; y <= end_y; ++y) { const uint8_t* in_row = &in[(y - start_y) * in_stride]; OpenEXR::Rgba* const JXL_RESTRICT row_data = &output_rows[(y - start_y) * xsize]; for (size_t x = 0; x < xsize; ++x) { const uint8_t* in_pixel = &in_row[4 * num_channels * x]; float r = loadFloat(&in_pixel[0]); float g = loadFloat(&in_pixel[4]); float b = loadFloat(&in_pixel[8]); const float alpha = loadAlpha(&in_pixel[12]); if (!alpha_is_premultiplied) { r *= alpha; g *= alpha; b *= alpha; } row_data[x] = OpenEXR::Rgba(r, g, b, alpha); } } output.writePixels(/*numScanLines=*/end_y - start_y + 1); } } return true; } 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}) { 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}); } } } return formats; } Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, ThreadPool* pool) const override { JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); encoded_image->icc.clear(); 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(EncodeImageEXR(frame.color, ppf.info, ppf.color_encoding, pool, &encoded_image->bitstreams.back())); } return true; } }; } // namespace #endif std::unique_ptr GetEXREncoder() { #if JPEGXL_ENABLE_EXR return jxl::make_unique(); #else return nullptr; #endif } } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/enc/exr.h000066400000000000000000000007451472134335300166610ustar00rootroot00000000000000// 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_EXR_H_ #define LIB_EXTRAS_ENC_EXR_H_ // Encodes OpenEXR images in memory. #include #include "lib/extras/enc/encode.h" namespace jxl { namespace extras { std::unique_ptr GetEXREncoder(); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_ENC_EXR_H_ libjxl-0.11.1/lib/extras/enc/jpegli.cc000066400000000000000000000502411472134335300174670ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/extras/enc/jpegli.h000066400000000000000000000031501472134335300173260ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/extras/enc/jpg.cc000066400000000000000000000534441472134335300170050ustar00rootroot00000000000000// 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/jpg.h" #if JPEGXL_ENABLE_JPEG #include "lib/jxl/base/include_jpeglib.h" // NOLINT #endif #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" #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] = { 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00}; constexpr int kICCMarker = JPEG_APP0 + 2; constexpr size_t kMaxBytesInMarker = 65533; constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; constexpr int kExifMarker = JPEG_APP0 + 1; enum class JpegEncoder { kLibJpeg, kSJpeg, }; // 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, const std::vector& icc) { constexpr size_t kMaxIccBytesInMarker = kMaxBytesInMarker - sizeof kICCSignature - 2; const int num_markers = static_cast(DivCeil(icc.size(), kMaxIccBytesInMarker)); size_t begin = 0; for (int current_marker = 0; current_marker < num_markers; ++current_marker) { const size_t length = std::min(kMaxIccBytesInMarker, icc.size() - begin); jpeg_write_m_header( cinfo, kICCMarker, static_cast(length + sizeof kICCSignature + 2)); for (const unsigned char c : kICCSignature) { jpeg_write_m_byte(cinfo, c); } jpeg_write_m_byte(cinfo, current_marker + 1); jpeg_write_m_byte(cinfo, num_markers); for (size_t i = 0; i < length; ++i) { jpeg_write_m_byte(cinfo, icc[begin]); ++begin; } } } void WriteExif(jpeg_compress_struct* const cinfo, const std::vector& exif) { jpeg_write_m_header( cinfo, kExifMarker, static_cast(exif.size() + sizeof kExifSignature)); for (const unsigned char c : kExifSignature) { jpeg_write_m_byte(cinfo, c); } for (uint8_t c : exif) { jpeg_write_m_byte(cinfo, c); } } Status SetChromaSubsampling(const std::string& subsampling, jpeg_compress_struct* const cinfo) { const std::pair, std::array>> options[] = {{"444", {{{1, 1, 1}}, {{1, 1, 1}}}}, {"420", {{{2, 1, 1}}, {{2, 1, 1}}}}, {"422", {{{2, 1, 1}}, {{1, 1, 1}}}}, {"440", {{{1, 1, 1}}, {{2, 1, 1}}}}}; for (const auto& option : options) { if (subsampling == option.first) { for (size_t i = 0; i < 3; i++) { cinfo->comp_info[i].h_samp_factor = option.second.first[i]; cinfo->comp_info[i].v_samp_factor = option.second.second[i]; } return true; } } 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, 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 = {}; jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); unsigned char* buffer = nullptr; #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 = static_cast(params.optimize_coding); if (cinfo.input_components == 3) { JXL_RETURN_IF_ERROR( SetChromaSubsampling(params.chroma_subsampling, &cinfo)); } 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); } if (!exif.empty()) { ResetExifOrientation(exif); WriteExif(&cinfo, exif); } if (cinfo.input_components > 3 || cinfo.input_components < 0) return JXL_FAILURE("invalid numbers of components"); 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); bytes->resize(size); // Compressed image data is initialized by libjpeg, which we are not // instrumenting with msan. msan::UnpoisonMemory(buffer, size); std::copy_n(buffer, size, bytes->data()); std::free(buffer); 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, const JpegParams& params, std::vector* bytes) { #if !JPEGXL_ENABLE_SJPEG return JXL_FAILURE("JPEG XL was built without sjpeg support"); #else 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()); } if (!exif.empty()) { ResetExifOrientation(exif); param.exif.assign(exif.begin(), exif.end()); } if (params.chroma_subsampling == "444") { param.yuv_mode = SJPEG_YUV_444; } 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; JXL_RETURN_IF_ERROR( sjpeg::Encode(pixels, image.xsize, image.ysize, stride, param, &output)); bytes->assign( reinterpret_cast(output.data()), reinterpret_cast(output.data() + output.size())); return true; #endif } Status EncodeImageJPG(const PackedImage& image, const JxlBasicInfo& info, const std::vector& icc, std::vector exif, JpegEncoder encoder, 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), params, bytes)); break; case JpegEncoder::kSJpeg: 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"); } return true; } class JPEGEncoder : public Encoder { std::vector AcceptedFormats() const override { std::vector formats; 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) const override { JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); JpegEncoder jpeg_encoder = JpegEncoder::kLibJpeg; 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); } } 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, 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 } // namespace jxl libjxl-0.11.1/lib/extras/enc/jpg.h000066400000000000000000000007571472134335300166460ustar00rootroot00000000000000// 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_JPG_H_ #define LIB_EXTRAS_ENC_JPG_H_ // Encodes JPG pixels and metadata in memory. #include #include "lib/extras/enc/encode.h" namespace jxl { namespace extras { std::unique_ptr GetJPEGEncoder(); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_ENC_JPG_H_ libjxl-0.11.1/lib/extras/enc/jxl.cc000066400000000000000000000357231472134335300170220ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/extras/enc/jxl.h000066400000000000000000000057131472134335300166600ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/extras/enc/npy.cc000066400000000000000000000226511472134335300170270ustar00rootroot00000000000000// 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/npy.h" #include #include #include #include #include #include "lib/extras/packed_image.h" #include "lib/jxl/base/common.h" namespace jxl { namespace extras { namespace { // JSON value writing class JSONField { public: virtual ~JSONField() = default; virtual void Write(std::ostream& o, uint32_t indent) const = 0; protected: JSONField() = default; }; class JSONValue : public JSONField { public: template explicit JSONValue(const T& value) : value_(std::to_string(value)) {} explicit JSONValue(const std::string& value) : value_("\"" + value + "\"") {} explicit JSONValue(bool value) : value_(value ? "true" : "false") {} void Write(std::ostream& o, uint32_t indent) const override { o << value_; } private: std::string value_; }; class JSONDict : public JSONField { public: JSONDict() = default; template T* AddEmpty(const std::string& key) { static_assert(std::is_convertible::value, "T must be a JSONField"); T* ret = new T(); 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) { 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 { std::string indent_str(indent, ' '); o << "{"; bool is_first = true; for (const auto& key_value : values_) { if (!is_first) { o << ","; } is_first = false; o << "\n" << indent_str << " \"" << key_value.first << "\": "; key_value.second->Write(o, indent + 2); } if (!values_.empty()) { o << "\n" << indent_str; } o << "}"; } private: // Dictionary with order. std::vector>> values_; }; class JSONArray : public JSONField { public: JSONArray() = default; template T* AddEmpty() { static_assert(std::is_convertible::value, "T must be a JSONField"); T* ret = new T(); values_.emplace_back(ret); return ret; } template void Add(const T& value) { values_.emplace_back(new JSONValue(value)); } void Write(std::ostream& o, uint32_t indent) const override { std::string indent_str(indent, ' '); o << "["; bool is_first = true; for (const auto& value : values_) { if (!is_first) { o << ","; } is_first = false; o << "\n" << indent_str << " "; value->Write(o, indent + 2); } if (!values_.empty()) { o << "\n" << indent_str; } o << "]"; } private: std::vector> values_; }; void GenerateMetadata(const PackedPixelFile& ppf, std::vector* out) { JSONDict meta; // Same order as in 18181-3 CD. // Frames. auto* meta_frames = meta.AddEmpty("frames"); for (size_t i = 0; i < ppf.frames.size(); i++) { auto* frame_i = meta_frames->AddEmpty(); if (ppf.info.have_animation) { frame_i->Add("duration", JSONValue(ppf.frames[i].frame_info.duration * 1.0f * ppf.info.animation.tps_denominator / ppf.info.animation.tps_numerator)); } frame_i->Add("name", JSONValue(ppf.frames[i].name)); if (ppf.info.animation.have_timecodes) { frame_i->Add("timecode", JSONValue(ppf.frames[i].frame_info.timecode)); } } #define METADATA(FIELD) meta.Add(#FIELD, ppf.info.FIELD) METADATA(intensity_target); METADATA(min_nits); METADATA(relative_to_max_display); METADATA(linear_below); if (ppf.info.have_preview) { meta.AddEmpty("preview"); // TODO(veluca): can we have duration/name/timecode here? } { 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 (const auto& eci : ppf.extra_channels_info) { switch (eci.ec_info.type) { case JXL_CHANNEL_ALPHA: { ectype->Add(std::string("Alpha")); break; } case JXL_CHANNEL_DEPTH: { ectype->Add(std::string("Depth")); break; } case JXL_CHANNEL_SPOT_COLOR: { ectype->Add(std::string("SpotColor")); break; } case JXL_CHANNEL_SELECTION_MASK: { ectype->Add(std::string("SelectionMask")); break; } case JXL_CHANNEL_BLACK: { ectype->Add(std::string("Black")); break; } case JXL_CHANNEL_CFA: { ectype->Add(std::string("CFA")); break; } case JXL_CHANNEL_THERMAL: { ectype->Add(std::string("Thermal")); break; } default: { ectype->Add(std::string("UNKNOWN")); break; } } bps->Add(eci.ec_info.bits_per_sample); ebps->Add(eci.ec_info.exponent_bits_per_sample); } } std::ostringstream os; meta.Write(os, 0); out->resize(os.str().size()); memcpy(out->data(), os.str().data(), os.str().size()); } void Append(std::vector* out, const void* data, size_t size) { size_t pos = out->size(); out->resize(pos + size); memcpy(out->data() + pos, data, size); } void WriteNPYHeader(size_t xsize, size_t ysize, uint32_t num_channels, size_t num_frames, std::vector* out) { const uint8_t header[] = "\x93NUMPY\x01\x00"; Append(out, header, 8); std::stringstream ss; ss << "{'descr': '(ss.str().size() % 256), static_cast(ss.str().size() / 256)}; Append(out, header_len, 2); Append(out, ss.str().data(), ss.str().size()); } bool WriteFrameToNPYArray(size_t xsize, size_t ysize, const PackedFrame& frame, std::vector* out) { const auto& color = frame.color; if (color.xsize != xsize || color.ysize != ysize) { return false; } for (const auto& ec : frame.extra_channels) { if (ec.xsize != xsize || ec.ysize != ysize) { return false; } } // interleave the samples from color and extra channels for (size_t y = 0; y < ysize; ++y) { for (size_t x = 0; x < xsize; ++x) { { size_t sample_size = color.pixel_stride(); size_t offset = y * color.stride + x * sample_size; uint8_t* pixels = reinterpret_cast(color.pixels()); 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_ENSURE(offset + sample_size <= ec.pixels_size); Append(out, pixels + offset, sample_size); } } } return true; } // Writes a PackedPixelFile as a numpy 4D ndarray in binary format. bool WriteNPYArray(const PackedPixelFile& ppf, std::vector* out) { size_t xsize = ppf.info.xsize; size_t ysize = ppf.info.ysize; WriteNPYHeader(xsize, ysize, ppf.info.num_color_channels + ppf.extra_channels_info.size(), ppf.frames.size(), out); for (const auto& frame : ppf.frames) { if (!WriteFrameToNPYArray(xsize, ysize, frame, out)) { return false; } } return true; } class NumPyEncoder : public Encoder { public: Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, ThreadPool* pool) const override { JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); GenerateMetadata(ppf, &encoded_image->metadata); encoded_image->bitstreams.emplace_back(); if (!WriteNPYArray(ppf, &encoded_image->bitstreams.back())) { return false; } if (ppf.preview_frame) { size_t xsize = ppf.info.preview.xsize; size_t ysize = ppf.info.preview.ysize; WriteNPYHeader(xsize, ysize, ppf.info.num_color_channels, 1, &encoded_image->preview_bitstream); if (!WriteFrameToNPYArray(xsize, ysize, *ppf.preview_frame, &encoded_image->preview_bitstream)) { return false; } } return true; } std::vector AcceptedFormats() const override { std::vector formats; for (const uint32_t num_channels : {1, 3}) { formats.push_back(JxlPixelFormat{num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0}); } return formats; } }; } // namespace std::unique_ptr GetNumPyEncoder() { return jxl::make_unique(); } } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/enc/npy.h000066400000000000000000000010021472134335300166540ustar00rootroot00000000000000// 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_NPY_H_ #define LIB_EXTRAS_ENC_NPY_H_ // Encodes pixels to numpy array, used for conformance testing. #include #include "lib/extras/enc/encode.h" namespace jxl { namespace extras { std::unique_ptr GetNumPyEncoder(); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_ENC_NPY_H_ libjxl-0.11.1/lib/extras/enc/pgx.cc000066400000000000000000000104731472134335300170160ustar00rootroot00000000000000// 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/pgx.h" #include #include #include "lib/extras/packed_image.h" #include "lib/jxl/base/byte_order.h" namespace jxl { namespace extras { namespace { constexpr size_t kMaxHeaderSize = 200; Status EncodeHeader(const JxlBasicInfo& info, char* header, int* chars_written) { if (info.alpha_bits > 0) { return JXL_FAILURE("PGX: can't store alpha"); } if (info.num_color_channels != 1) { return JXL_FAILURE("PGX: must be grayscale"); } // TODO(lode): verify other bit depths: for other bit depths such as 1 or 4 // bits, have a test case to verify it works correctly. For bits > 16, we may // need to change the way external_image works. if (info.bits_per_sample != 8 && info.bits_per_sample != 16) { return JXL_FAILURE("PGX: bits other than 8 or 16 not yet supported"); } // Use ML (Big Endian), LM may not be well supported by all decoders. *chars_written = snprintf(header, kMaxHeaderSize, "PG ML + %u %u %u\n", info.bits_per_sample, info.xsize, info.ysize); JXL_RETURN_IF_ERROR(static_cast(*chars_written) < kMaxHeaderSize); return true; } Status EncodeImagePGX(const PackedFrame& frame, const JxlBasicInfo& info, std::vector* bytes) { char header[kMaxHeaderSize]; int header_size = 0; JXL_RETURN_IF_ERROR(EncodeHeader(info, header, &header_size)); 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; if (info.bits_per_sample != data_bits_per_sample) { return JXL_FAILURE("Bit depth does not match pixel data type"); } std::vector pixels(num_samples * bytes_per_sample); if (format.data_type == JXL_TYPE_UINT8) { 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; uint8_t* p_out = pixels.data(); for (size_t i = 0; i < num_samples; ++i, p_in += 2, p_out += 2) { StoreBE16(LoadLE16(p_in), p_out); } } else { memcpy(pixels.data(), in, num_samples * bytes_per_sample); } } else { return JXL_FAILURE("Unsupported pixel data type"); } bytes->resize(static_cast(header_size) + pixels.size()); memcpy(bytes->data(), header, static_cast(header_size)); memcpy(bytes->data() + header_size, pixels.data(), pixels.size()); return true; } class PGXEncoder : public Encoder { public: std::vector AcceptedFormats() const override { std::vector formats; 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=*/1, /*data_type=*/data_type, /*endianness=*/endianness, /*align=*/0}); } } return formats; } Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, ThreadPool* pool) const override { JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); encoded_image->icc.assign(ppf.icc.begin(), ppf.icc.end()); 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( EncodeImagePGX(frame, ppf.info, &encoded_image->bitstreams.back())); } return true; } }; } // namespace std::unique_ptr GetPGXEncoder() { return jxl::make_unique(); } } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/enc/pgx.h000066400000000000000000000007671472134335300166650ustar00rootroot00000000000000// 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_PGX_H_ #define LIB_EXTRAS_ENC_PGX_H_ // Encodes PGX pixels in memory. #include #include #include "lib/extras/enc/encode.h" namespace jxl { namespace extras { std::unique_ptr GetPGXEncoder(); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_ENC_PGX_H_ libjxl-0.11.1/lib/extras/enc/pnm.cc000066400000000000000000000302031472134335300170030ustar00rootroot00000000000000// 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/pnm.h" #include #include #include #include #include #include #include "lib/extras/packed_image.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" namespace jxl { namespace extras { namespace { constexpr size_t kMaxHeaderSize = 2000; class BasePNMEncoder : public Encoder { public: Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image, 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()) { JXL_WARNING("PNM encoder ignoring metadata - use a different codec"); } encoded_image->icc = ppf.icc; 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( 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; encoded_image->extra_channel_bitstreams.emplace_back(); auto& ec_bitstreams = encoded_image->extra_channel_bitstreams.back(); for (const auto& frame : ppf.frames) { ec_bitstreams.emplace_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 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}) { 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 true; } Status EncodeExtraChannel(const PackedImage& image, size_t bits_per_sample, std::vector* bytes) const override { return true; } 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"); } } }; } // 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(); } std::unique_ptr GetPGMEncoder() { return jxl::make_unique(); } std::unique_ptr GetPAMEncoder() { return jxl::make_unique(); } } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/enc/pnm.h000066400000000000000000000013761472134335300166560ustar00rootroot00000000000000// 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_PNM_H_ #define LIB_EXTRAS_ENC_PNM_H_ // Encodes/decodes PBM/PGM/PPM/PFM pixels in memory. // TODO(janwas): workaround for incorrect Win64 codegen (cause unknown) #include #include #include "lib/extras/enc/encode.h" namespace jxl { namespace extras { std::unique_ptr GetPAMEncoder(); std::unique_ptr GetPGMEncoder(); std::unique_ptr GetPNMEncoder(); std::unique_ptr GetPPMEncoder(); std::unique_ptr GetPFMEncoder(); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_ENC_PNM_H_ libjxl-0.11.1/lib/extras/exif.cc000066400000000000000000000026731472134335300164110ustar00rootroot00000000000000// 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/exif.h" #include "lib/jxl/base/byte_order.h" namespace jxl { constexpr uint16_t kExifOrientationTag = 274; void ResetExifOrientation(std::vector& exif) { if (exif.size() < 12) return; // not enough bytes for a valid exif blob bool bigendian; uint8_t* t = exif.data(); if (LoadLE32(t) == 0x2A004D4D) { bigendian = true; } else if (LoadLE32(t) == 0x002A4949) { bigendian = false; } else { return; // not a valid tiff header } t += 4; 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)); t += 2; while (nb_tags > 0) { if (t + 12 >= exif.data() + exif.size()) return; uint16_t tag = (bigendian ? LoadBE16(t) : LoadLE16(t)); t += 2; if (tag == kExifOrientationTag) { uint16_t type = (bigendian ? LoadBE16(t) : LoadLE16(t)); t += 2; uint32_t count = (bigendian ? LoadBE32(t) : LoadLE32(t)); t += 4; if (type == 3 && count == 1) { if (bigendian) { StoreBE16(1, t); } else { StoreLE16(1, t); } } return; } else { t += 10; nb_tags--; } } } } // namespace jxl libjxl-0.11.1/lib/extras/exif.h000066400000000000000000000007261472134335300162500ustar00rootroot00000000000000// 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_EXIF_H_ #define LIB_EXTRAS_EXIF_H_ #include #include namespace jxl { // Sets the Exif orientation to the identity, to avoid repeated orientation void ResetExifOrientation(std::vector& exif); } // namespace jxl #endif // LIB_EXTRAS_EXIF_H_ libjxl-0.11.1/lib/extras/gain_map.cc000066400000000000000000000176641472134335300172370ustar00rootroot00000000000000// 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; } libjxl-0.11.1/lib/extras/gain_map_test.cc000066400000000000000000000145031472134335300202630ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/extras/hlg.cc000066400000000000000000000037061472134335300162260ustar00rootroot00000000000000// 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/hlg.h" #include #include namespace jxl { float GetHlgGamma(const float peak_luminance, const float surround_luminance) { return 1.2f * std::pow(1.111f, std::log2(peak_luminance / 1000.f)) * std::pow(0.98f, std::log2(surround_luminance / 5.f)); } Status HlgOOTF(ImageBundle* ib, const float gamma, ThreadPool* pool) { ColorEncoding linear_rec2020; linear_rec2020.SetColorSpace(ColorSpace::kRGB); 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, *JxlGetDefaultCms(), pool)); 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; } Status HlgInverseOOTF(ImageBundle* ib, const float gamma, ThreadPool* pool) { return HlgOOTF(ib, 1.f / gamma, pool); } } // namespace jxl libjxl-0.11.1/lib/extras/hlg.h000066400000000000000000000010611472134335300160600ustar00rootroot00000000000000// 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_HLG_H_ #define LIB_EXTRAS_HLG_H_ #include "lib/jxl/image_bundle.h" namespace jxl { float GetHlgGamma(float peak_luminance, float surround_luminance = 5.f); Status HlgOOTF(ImageBundle* ib, float gamma, ThreadPool* pool = nullptr); Status HlgInverseOOTF(ImageBundle* ib, float gamma, ThreadPool* pool = nullptr); } // namespace jxl #endif // LIB_EXTRAS_HLG_H_ libjxl-0.11.1/lib/extras/jpegli_test.cc000066400000000000000000000343001472134335300177570ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/extras/metrics.cc000066400000000000000000000157671472134335300171340ustar00rootroot00000000000000// 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/metrics.h" #include #include #include #undef HWY_TARGET_INCLUDE #define HWY_TARGET_INCLUDE "lib/extras/metrics.cc" #include #include #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/rect.h" #include "lib/jxl/base/status.h" #include "lib/jxl/color_encoding_internal.h" HWY_BEFORE_NAMESPACE(); namespace jxl { namespace HWY_NAMESPACE { // These templates are not found via ADL. using hwy::HWY_NAMESPACE::Add; using hwy::HWY_NAMESPACE::GetLane; using hwy::HWY_NAMESPACE::Mul; using hwy::HWY_NAMESPACE::Rebind; double ComputeDistanceP(const ImageF& distmap, const ButteraugliParams& params, double p) { const double onePerPixels = 1.0 / (distmap.ysize() * distmap.xsize()); if (std::abs(p - 3.0) < 1E-6) { double sum1[3] = {0.0}; // Prefer double if possible, but otherwise use float rather than scalar. #if HWY_CAP_FLOAT64 using T = double; const Rebind df; #else using T = float; #endif const HWY_FULL(T) d; 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}; HWY_ALIGN T sum_totals1[N] = {0}; HWY_ALIGN T sum_totals2[N] = {0}; for (size_t y = 0; y < distmap.ysize(); ++y) { const float* JXL_RESTRICT row = distmap.ConstRow(y); auto sums0 = Zero(d); auto sums1 = Zero(d); auto sums2 = Zero(d); size_t x = 0; for (; x + Lanes(d) <= distmap.xsize(); x += Lanes(d)) { #if HWY_CAP_FLOAT64 const auto d1 = PromoteTo(d, Load(df, row + x)); #else const auto d1 = Load(d, row + x); #endif const auto d2 = Mul(d1, Mul(d1, d1)); sums0 = Add(sums0, d2); const auto d3 = Mul(d2, d2); sums1 = Add(sums1, d3); const auto d4 = Mul(d3, d3); sums2 = Add(sums2, d4); } Store(Add(sums0, Load(d, sum_totals0)), d, sum_totals0); Store(Add(sums1, Load(d, sum_totals1)), d, sum_totals1); Store(Add(sums2, Load(d, sum_totals2)), d, sum_totals2); for (; x < distmap.xsize(); ++x) { const double d1 = row[x]; double d2 = d1 * d1 * d1; sum1[0] += d2; d2 *= d2; sum1[1] += d2; d2 *= d2; sum1[2] += d2; } } double v = 0; v += pow( onePerPixels * (sum1[0] + GetLane(SumOfLanes(d, Load(d, sum_totals0)))), 1.0 / (p * 1.0)); v += pow( onePerPixels * (sum1[1] + GetLane(SumOfLanes(d, Load(d, sum_totals1)))), 1.0 / (p * 2.0)); v += pow( onePerPixels * (sum1[2] + GetLane(SumOfLanes(d, Load(d, sum_totals2)))), 1.0 / (p * 4.0)); v /= 3.0; return v; } else { static std::atomic once{0}; if (once.fetch_add(1, std::memory_order_relaxed) == 0) { JXL_WARNING("WARNING: using slow ComputeDistanceP"); } double sum1[3] = {0.0}; for (size_t y = 0; y < distmap.ysize(); ++y) { const float* JXL_RESTRICT row = distmap.ConstRow(y); for (size_t x = 0; x < distmap.xsize(); ++x) { double d2 = std::pow(row[x], p); sum1[0] += d2; d2 *= d2; sum1[1] += d2; d2 *= d2; sum1[2] += d2; } } double v = 0; for (int i = 0; i < 3; ++i) { v += pow(onePerPixels * (sum1[i]), 1.0 / (p * (1 << i))); } v /= 3.0; return v; } } 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()) { if (!ib1.CopyTo(Rect(ib1), ColorEncoding::SRGB(ib1.IsGray()), cms, ©1)) return; srgb1 = ©1; } const Image3F* srgb2 = &ib2.color(); Image3F copy2; if (!ib2.IsSRGB()) { if (!ib2.CopyTo(Rect(ib2), ColorEncoding::SRGB(ib2.IsGray()), cms, ©2)) return; srgb2 = ©2; } 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}}; for (size_t y = 0; y < srgb1->ysize(); ++y) { const float* JXL_RESTRICT row1[3]; const float* JXL_RESTRICT row2[3]; for (size_t j = 0; j < 3; j++) { row1[j] = srgb1->ConstPlaneRow(j, y); row2[j] = srgb2->ConstPlaneRow(j, y); } for (size_t x = 0; x < srgb1->xsize(); ++x) { float cdiff[3] = {}; // YUV conversion is linear, so we can run it on the difference. for (size_t j = 0; j < 3; j++) { cdiff[j] = row1[j][x] - row2[j][x]; } float yuvdiff[3] = {}; for (size_t j = 0; j < 3; j++) { for (size_t k = 0; k < 3; k++) { yuvdiff[j] += yuvmatrix[j][k] * cdiff[k]; } } for (size_t j = 0; j < 3; j++) { sum_of_squares[j] += yuvdiff[j] * yuvdiff[j]; } } } } // NOLINTNEXTLINE(google-readability-namespace-comments) } // namespace HWY_NAMESPACE } // namespace jxl HWY_AFTER_NAMESPACE(); #if HWY_ONCE namespace jxl { HWY_EXPORT(ComputeDistanceP); double ComputeDistanceP(const ImageF& distmap, const ButteraugliParams& params, double p) { return HWY_DYNAMIC_DISPATCH(ComputeDistanceP)(distmap, params, p); } HWY_EXPORT(ComputeSumOfSquares); double ComputeDistance2(const ImageBundle& ib1, const ImageBundle& ib2, const JxlCmsInterface& 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 #endif libjxl-0.11.1/lib/extras/metrics.h000066400000000000000000000014601472134335300167570ustar00rootroot00000000000000// 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_METRICS_H_ #define LIB_EXTRAS_METRICS_H_ #include #include "lib/jxl/butteraugli/butteraugli.h" #include "lib/jxl/image_bundle.h" namespace jxl { // Computes p-norm given the butteraugli distmap. double ComputeDistanceP(const ImageF& distmap, const ButteraugliParams& params, double p); 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_EXTRAS_METRICS_H_ libjxl-0.11.1/lib/extras/mmap.cc000066400000000000000000000076521472134335300164120ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/extras/mmap.h000066400000000000000000000015221472134335300162420ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/extras/packed_image.h000066400000000000000000000222451472134335300177060ustar00rootroot00000000000000// 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_PACKED_IMAGE_H_ #define LIB_EXTRAS_PACKED_IMAGE_H_ // 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 "lib/jxl/base/byte_order.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/status.h" namespace jxl { namespace extras { // Class representing an interleaved image with a bunch of channels. class PackedImage { public: 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; // The number of bytes per row. size_t stride; // Pixel storage format and buffer size of the pixels_ pointer. JxlPixelFormat format; size_t pixels_size; 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) { switch (data_type) { case JXL_TYPE_UINT8: return 8; case JXL_TYPE_UINT16: return 16; case JXL_TYPE_FLOAT: return 32; case JXL_TYPE_FLOAT16: return 16; default: 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"); } } private: PackedImage(size_t xsize, size_t ysize, const JxlPixelFormat& format, size_t stride) : xsize(xsize), ysize(ysize), stride(stride), format(format), pixels_size(ysize * stride), 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) * format.num_channels / jxl::kBitsPerByte); if (format.align > 1) { stride = jxl::DivCeil(stride, format.align) * format.align; } return stride; } size_t bytes_per_channel_; size_t pixel_stride_; bool swap_endianness_; std::unique_ptr pixels_; }; // Helper class representing a frame, as seen from the API. Animations will have // multiple frames, but a single frame can have a color/grayscale channel and // multiple extra channels. The order of the extra channels should be the same // as all other frames in the same image. class PackedFrame { public: 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 = {}; std::string name; // The pixel data for the color (or grayscale) channels. PackedImage color; // Extra channel image data. 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 = {}; std::vector extra_channels_info; // Color information of the decoded pixels. // `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 } // namespace jxl #endif // LIB_EXTRAS_PACKED_IMAGE_H_ libjxl-0.11.1/lib/extras/packed_image_convert.cc000066400000000000000000000363221472134335300216050ustar00rootroot00000000000000// 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/packed_image_convert.h" #include #include #include #include #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/dec_external_image.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) { 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_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_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) .IsInside(Rect(0, 0, info.xsize, info.ysize))); if (info.have_animation) { bundle->duration = frame.frame_info.duration; bundle->blend = frame.frame_info.layer_info.blend_info.blendmode > 0; bundle->use_for_next_frame = frame.frame_info.layer_info.save_as_reference > 0; bundle->origin.x0 = frame.frame_info.layer_info.crop_x0; bundle->origin.y0 = frame.frame_info.layer_info.crop_y0; } bundle->name = frame.name; // frame.frame_info.name_length is ignored here. 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_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]; 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_ENSURE(!ppf.frames.empty()); if (has_alpha) { 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_ENSURE(ppf.info.num_color_channels == 1 || ppf.info.num_color_channels == 3); // Convert the image metadata 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; io->metadata.m.bit_depth.floating_point_sample = ppf.info.exponent_bits_per_sample != 0; io->metadata.m.modular_16_bit_buffer_sufficient = ppf.info.exponent_bits_per_sample == 0 && ppf.info.bits_per_sample <= 12; io->metadata.m.SetAlphaBits(ppf.info.alpha_bits, 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 = !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_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.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( io->metadata.m.color_encoding.FromExternal(ppf.color_encoding)); if (io->metadata.m.color_encoding.ICC().empty()) { return JXL_FAILURE("Failed to serialize ICC"); } } // 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 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; out.bit_depth.exponent_bits_per_sample = info.ec_info.exponent_bits_per_sample; out.bit_depth.floating_point_sample = info.ec_info.exponent_bits_per_sample != 0; out.dim_shift = info.ec_info.dim_shift; out.name = info.name; out.alpha_associated = (info.ec_info.alpha_premultiplied != 0); out.spot_color[0] = info.ec_info.spot_color[0]; out.spot_color[1] = info.ec_info.spot_color[1]; out.spot_color[2] = info.ec_info.spot_color[2]; out.spot_color[3] = info.ec_info.spot_color[3]; io->metadata.m.extra_channel_info.push_back(std::move(out)); } // Convert the preview if (ppf.preview_frame) { size_t preview_xsize = ppf.preview_frame->color.xsize; size_t preview_ysize = ppf.preview_frame->color.ysize; io->metadata.m.have_preview = true; JXL_RETURN_IF_ERROR( io->metadata.m.preview_size.Set(preview_xsize, preview_ysize)); JXL_RETURN_IF_ERROR(ConvertPackedFrameToImageBundle( ppf.info, ppf.input_bitdepth, *ppf.preview_frame, *io, pool, &io->preview_frame)); } // Convert the pixels io->frames.clear(); for (const auto& frame : ppf.frames) { 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)); } if (ppf.info.exponent_bits_per_sample == 0) { // uint case. io->metadata.m.bit_depth.bits_per_sample = io->Main().DetectRealBitdepth(); } if (ppf.info.intensity_target != 0) { io->metadata.m.SetIntensityTarget(ppf.info.intensity_target); } else { SetIntensityTarget(&io->metadata.m); } 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(); JXL_ENSURE(!io.frames.empty()); if (has_alpha) { 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_ENSURE(alpha_channel->bit_depth.exponent_bits_per_sample == io.metadata.m.bit_depth.exponent_bits_per_sample); 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 ppf->info.xsize = io.metadata.size.xsize(); ppf->info.ysize = io.metadata.size.ysize(); ppf->info.num_color_channels = io.metadata.m.color_encoding.Channels(); ppf->info.bits_per_sample = io.metadata.m.bit_depth.bits_per_sample; ppf->info.exponent_bits_per_sample = io.metadata.m.bit_depth.exponent_bits_per_sample; 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 = 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_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()); 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 || pixel_format.data_type == JXL_TYPE_FLOAT16; // Convert the pixels ppf->frames.clear(); for (const auto& frame : io.frames) { 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() + alpha_channels; JxlPixelFormat format{/*num_channels=*/num_channels, /*data_type=*/pixel_format.data_type, /*endianness=*/pixel_format.endianness, /*align=*/pixel_format.align}; 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 JXL_ASSIGN_OR_RETURN(ImageBundle ib, frame.Copy()); const ImageBundle* to_color_transform = &ib; ImageMetadata metadata = io.metadata.m; ImageBundle store(memory_manager, &metadata); const ImageBundle* transformed; // TODO(firsching): handle the transform here. JXL_RETURN_IF_ERROR(TransformIfNeeded(*to_color_transform, c_desired, *JxlGetDefaultCms(), pool, &store, &transformed)); JXL_RETURN_IF_ERROR(ConvertToExternal( *transformed, bits_per_sample, float_out, format.num_channels, format.endianness, /* stride_out=*/packed_frame.color.stride, pool, packed_frame.color.pixels(), packed_frame.color.pixels_size, /*out_callback=*/{}, frame.metadata()->GetOrientation())); // TODO(firsching): Convert the extra channels, beside one potential alpha // channel. FIXME! JXL_ENSURE(frame.extra_channels().size() <= (has_alpha ? 1 : 0)); ppf->frames.push_back(std::move(packed_frame)); } return true; } } // namespace extras } // namespace jxl libjxl-0.11.1/lib/extras/packed_image_convert.h000066400000000000000000000027541472134335300214510ustar00rootroot00000000000000// 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_PACKED_IMAGE_CONVERT_H_ #define LIB_EXTRAS_PACKED_IMAGE_CONVERT_H_ // Helper functions to convert from the external image types to the internal // CodecInOut to help transitioning to the external types. #include #include "lib/extras/packed_image.h" #include "lib/jxl/base/status.h" #include "lib/jxl/codec_in_out.h" namespace jxl { namespace extras { // Converts an external PackedPixelFile to the internal CodecInOut for use with // internal functions directly. Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf, ThreadPool* pool, CodecInOut* io); // Converts an internal CodecInOut for use with internal function to an external // PackedPixelFile. Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io, const JxlPixelFormat& pixel_format, 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 #endif // LIB_EXTRAS_PACKED_IMAGE_CONVERT_H_ libjxl-0.11.1/lib/extras/size_constraints.h000066400000000000000000000024611472134335300207140ustar00rootroot00000000000000// 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_SIZE_CONSTRAINTS_H_ #define LIB_JXL_SIZE_CONSTRAINTS_H_ #include #include #include "lib/jxl/base/status.h" namespace jxl { struct SizeConstraints { // Upper limit on pixel dimensions/area, enforced by VerifyDimensions // (called from decoders). Fuzzers set smaller values to limit memory use. uint32_t dec_max_xsize = 0xFFFFFFFFu; uint32_t dec_max_ysize = 0xFFFFFFFFu; 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_ libjxl-0.11.1/lib/extras/time.cc000066400000000000000000000026421472134335300164100ustar00rootroot00000000000000// 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/time.h" #include #include #include #include "lib/jxl/base/os_macros.h" // for JXL_OS_* #if JXL_OS_WIN #ifndef NOMINMAX #define NOMINMAX #endif // NOMINMAX #include #endif // JXL_OS_WIN #if JXL_OS_MAC #include #include #endif // JXL_OS_MAC #if JXL_OS_HAIKU #include #endif // JXL_OS_HAIKU namespace jxl { double Now() { #if JXL_OS_WIN LARGE_INTEGER counter; (void)QueryPerformanceCounter(&counter); LARGE_INTEGER freq; (void)QueryPerformanceFrequency(&freq); return double(counter.QuadPart) / freq.QuadPart; #elif JXL_OS_MAC const auto t = mach_absolute_time(); // On OSX/iOS platform the elapsed time is cpu time unit // We have to query the time base information to convert it back // See https://developer.apple.com/library/mac/qa/qa1398/_index.html static mach_timebase_info_data_t timebase; if (timebase.denom == 0) { (void)mach_timebase_info(&timebase); } return double(t) * timebase.numer / timebase.denom * 1E-9; // notypo #elif JXL_OS_HAIKU return double(system_time_nsecs()) * 1E-9; #else timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec + t.tv_nsec * 1E-9; #endif } } // namespace jxl libjxl-0.11.1/lib/extras/time.h000066400000000000000000000007461472134335300162550ustar00rootroot00000000000000// 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_TIME_H_ #define LIB_EXTRAS_TIME_H_ // OS-specific function for timing. namespace jxl { // Returns current time [seconds] from a monotonic clock with unspecified // starting point - only suitable for computing elapsed time. double Now(); } // namespace jxl #endif // LIB_EXTRAS_TIME_H_ libjxl-0.11.1/lib/extras/tone_mapping.cc000066400000000000000000000112021472134335300201220ustar00rootroot00000000000000// 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/tone_mapping.h" #undef HWY_TARGET_INCLUDE #define HWY_TARGET_INCLUDE "lib/extras/tone_mapping.cc" #include #include #include #include "lib/jxl/cms/tone_mapping-inl.h" #include "lib/jxl/image_bundle.h" HWY_BEFORE_NAMESPACE(); namespace jxl { namespace HWY_NAMESPACE { static constexpr Vector3 rec2020_luminances{0.2627f, 0.6780f, 0.0593f}; Status ToneMapFrame(const std::pair display_nits, ImageBundle* const ib, ThreadPool* const pool) { // Perform tone mapping as described in Report ITU-R BT.2390-8, section 5.4 // (pp. 23-25). // https://www.itu.int/pub/R-REP-BT.2390-8-2020 HWY_FULL(float) df; using V = decltype(Zero(df)); ColorEncoding linear_rec2020; linear_rec2020.SetColorSpace(ColorSpace::kRGB); 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, *JxlGetDefaultCms(), pool)); Rec2408ToneMapper tone_mapper( {ib->metadata()->tone_mapping.min_nits, ib->metadata()->IntensityTarget()}, display_nits, rec2020_luminances); 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, ThreadPool* const pool) { HWY_FULL(float) df; using V = decltype(Zero(df)); ColorEncoding linear_rec2020; linear_rec2020.SetColorSpace(ColorSpace::kRGB); 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, *JxlGetDefaultCms(), pool)); 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; } // NOLINTNEXTLINE(google-readability-namespace-comments) } // namespace HWY_NAMESPACE } // namespace jxl HWY_AFTER_NAMESPACE(); #if HWY_ONCE namespace jxl { namespace { HWY_EXPORT(ToneMapFrame); HWY_EXPORT(GamutMapFrame); } // namespace Status ToneMapTo(const std::pair display_nits, CodecInOut* const io, ThreadPool* const pool) { const auto tone_map_frame = HWY_DYNAMIC_DISPATCH(ToneMapFrame); for (ImageBundle& ib : io->frames) { JXL_RETURN_IF_ERROR(tone_map_frame(display_nits, &ib, pool)); } io->metadata.m.SetIntensityTarget(display_nits.second); return true; } Status GamutMap(CodecInOut* const io, float preserve_saturation, ThreadPool* const pool) { const auto gamut_map_frame = HWY_DYNAMIC_DISPATCH(GamutMapFrame); for (ImageBundle& ib : io->frames) { JXL_RETURN_IF_ERROR(gamut_map_frame(&ib, preserve_saturation, pool)); } return true; } } // namespace jxl #endif libjxl-0.11.1/lib/extras/tone_mapping.h000066400000000000000000000022011472134335300177630ustar00rootroot00000000000000// 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_TONE_MAPPING_H_ #define LIB_EXTRAS_TONE_MAPPING_H_ #include "lib/jxl/codec_in_out.h" namespace jxl { // Important: after calling this, the result will contain many out-of-gamut // colors. It is very strongly recommended to call GamutMap afterwards to // rectify this. Status ToneMapTo(std::pair display_nits, CodecInOut* io, ThreadPool* pool = nullptr); // `preserve_saturation` indicates to what extent to favor saturation over // luminance when mapping out-of-gamut colors to Rec. 2020. 0 preserves // luminance at the complete expense of saturation, while 1 gives the most // saturated color with the same hue that Rec. 2020 can represent even if it // means lowering the luminance. Values in between correspond to linear mixtures // of those two extremes. Status GamutMap(CodecInOut* io, float preserve_saturation, ThreadPool* pool = nullptr); } // namespace jxl #endif // LIB_EXTRAS_TONE_MAPPING_H_ libjxl-0.11.1/lib/extras/tone_mapping_gbench.cc000066400000000000000000000036451472134335300214440ustar00rootroot00000000000000// 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 "benchmark/benchmark.h" #include "lib/extras/tone_mapping.h" #include "lib/jxl/base/status.h" #include "lib/jxl/image.h" #include "tools/no_memory_manager.h" namespace jxl { #define QUIT(M) \ state.SkipWithError(M); \ return; #define BM_CHECK(C) \ if (!(C)) { \ QUIT(#C) \ } 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); } // namespace jxl libjxl-0.11.1/lib/gbench_main.cc000066400000000000000000000003431472134335300163720ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include "benchmark/benchmark.h" BENCHMARK_MAIN(); libjxl-0.11.1/lib/include/000077500000000000000000000000001472134335300152545ustar00rootroot00000000000000libjxl-0.11.1/lib/include/jxl/000077500000000000000000000000001472134335300160515ustar00rootroot00000000000000libjxl-0.11.1/lib/include/jxl/cms.h000066400000000000000000000007321472134335300170060ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/include/jxl/cms_interface.h000066400000000000000000000237321472134335300210330ustar00rootroot00000000000000/* 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_color * @{ * @file cms_interface.h * @brief Interface to allow the injection of different color management systems * (CMSes, also called color management modules, or CMMs) in JPEG XL. * * A CMS is needed by the JPEG XL encoder and decoder to perform colorspace * conversions. This defines an interface that can be implemented for different * CMSes and then passed to the library. */ #ifndef JXL_CMS_INTERFACE_H_ #define JXL_CMS_INTERFACE_H_ #include #include #include #include #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 { /** The serialized ICC profile. This is guaranteed to be present and valid. */ struct { const uint8_t* data; size_t size; } icc; /** Structured representation of the colorspace, if applicable. If all fields * are different from their "unknown" value, then this is equivalent to the * ICC representation of the colorspace. If some are "unknown", those that are * not are still valid and can still be used on their own if they are useful. */ JxlColorEncoding color_encoding; /** Number of components per pixel. This can be deduced from the other * representations of the colorspace but is provided for convenience and * validation. */ size_t num_channels; } JxlColorProfile; /** 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 @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 @ref JxlCmsInterface::init_data passed as-is. * @param num_threads the maximum number of threads from which * @ref JxlCmsInterface::run will be called. * @param pixels_per_thread the maximum number of pixels that each call to * @ref JxlCmsInterface::run will have to transform. * @param input_profile the input colorspace for the transform. * @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 * luminance colorspace, in either direction: @p intensity_target cd/m² * in PQ should map to and from (1, 1, 1) in the relative one.\n * It is also used for conversions to and from HLG, as it is * scene-referred while other colorspaces are assumed to be * display-referred. That is, conversions from HLG should apply the OOTF * for a peak display luminance of @p intensity_target, and conversions * to HLG should undo it. The OOTF is a gamma function applied to the * luminance channel (https://www.itu.int/rec/R-REC-BT.2100-2-201807-I * page 7), with the gamma value computed as * 1.2 * 1.111^log2(intensity_target / 1000) (footnote 2 page 8 * of the same document). * @return The data needed for the transform, or @c NULL in case of failure. * This will be passed to the other functions as @c user_data. */ typedef void* (*jpegxl_cms_init_func)(void* init_data, size_t num_threads, size_t pixels_per_thread, const JxlColorProfile* input_profile, const JxlColorProfile* output_profile, float intensity_target); /** Returns a buffer that can be used by callers of the interface to store the * input of the conversion or read its result, if they pass it as the input or * output of the @c run function. * @param user_data the data returned by @c init. * @param thread the index of the thread for which to return a buffer. * @return A buffer that can be used by the caller for passing to @c run. */ typedef float* (*jpegxl_cms_get_buffer_func)(void* user_data, size_t thread); /** Executes one transform and returns true on success or false on error. It * must be possible to call this from different threads with different values * for @p thread, all between 0 (inclusive) and the value of @p num_threads * passed to @c init (exclusive). It is allowed to implement this by locking * such that the transforms are essentially performed sequentially, if such a * performance profile is acceptable. @p user_data is the data returned by * @c init. * The buffers each contain @p num_pixels × @c num_channels interleaved floating * point (0..1) samples where @c num_channels is the number of color channels of * their respective color profiles. It is guaranteed that the only case in which * they might overlap is if the output has fewer channels than the input, in * which case the pointers may be identical. * For CMYK data, 0 represents the maximum amount of ink while 1 represents no * ink. * @param user_data the data returned by @c init. * @param thread the index of the thread from which the function is being * called. * @param input_buffer the buffer containing the pixel data to be transformed. * @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. */ typedef JXL_BOOL (*jpegxl_cms_run_func)(void* user_data, size_t thread, const float* input_buffer, float* output_buffer, size_t num_pixels); /** Performs the necessary clean-up and frees the memory allocated for user * data. */ typedef void (*jpegxl_cms_destroy_func)(void*); /** * Interface for performing colorspace transforms. The @c init function can be * called several times to instantiate several transforms, including before * other transforms have been destroyed. * * The call sequence for a given colorspace transform could look like the * following: * @dot * digraph calls { * newrank = true * node [shape = box, fontname = monospace] * init [label = "user_data <- init(\l\ * init_data = data,\l\ * num_threads = 3,\l\ * pixels_per_thread = 20,\l\ * input = (sRGB, 3 channels),\l\ * output = (Display-P3, 3 channels),\l\ * intensity_target = 255\l\ * )\l"] * subgraph cluster_0 { * color = lightgrey * label = "thread 1" * labeljust = "c" * run_1_1 [label = "run(\l\ * user_data,\l\ * thread = 1,\l\ * input = in[0],\l\ * output = out[0],\l\ * num_pixels = 20\l\ * )\l"] * run_1_2 [label = "run(\l\ * user_data,\l\ * thread = 1,\l\ * input = in[3],\l\ * output = out[3],\l\ * num_pixels = 20\l\ * )\l"] * } * subgraph cluster_1 { * color = lightgrey * label = "thread 2" * labeljust = "l" * run_2_1 [label = "run(\l\ * user_data,\l\ * thread = 2,\l\ * input = in[1],\l\ * output = out[1],\l\ * num_pixels = 20\l\ * )\l"] * run_2_2 [label = "run(\l\ * user_data,\l\ * thread = 2,\l\ * input = in[4],\l\ * output = out[4],\l\ * num_pixels = 13\l\ * )\l"] * } * subgraph cluster_3 { * color = lightgrey * label = "thread 3" * labeljust = "c" * run_3_1 [label = "run(\l\ * user_data,\l\ * thread = 3,\l\ * input = in[2],\l\ * output = out[2],\l\ * num_pixels = 20\l\ * )\l"] * } * init -> {run_1_1; run_2_1; run_3_1; rank = same} * run_1_1 -> run_1_2 * run_2_1 -> run_2_2 * {run_1_2; run_2_2, run_3_1} -> "destroy(user_data)" * } * @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 * jpegxl_cms_init_func. */ jpegxl_cms_init_func init; /** Returns a buffer that can be used as input to @c run. */ jpegxl_cms_get_buffer_func get_src_buf; /** Returns a buffer that can be used as output from @c run. */ jpegxl_cms_get_buffer_func get_dst_buf; /** Executes the transform on a batch of pixels, per @ref jpegxl_cms_run_func. */ jpegxl_cms_run_func run; /** Cleans up the transform. */ jpegxl_cms_destroy_func destroy; } JxlCmsInterface; #ifdef __cplusplus } #endif #endif /* JXL_CMS_INTERFACE_H_ */ /** @} */ libjxl-0.11.1/lib/include/jxl/codestream_header.h000066400000000000000000000354651472134335300216750ustar00rootroot00000000000000/* 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 codestream_header.h * @brief Definitions of structs and enums for the metadata from the JPEG XL * codestream headers (signature, metadata, preview dimensions, ...), excluding * color encoding which is in color_encoding.h. */ #ifndef JXL_CODESTREAM_HEADER_H_ #define JXL_CODESTREAM_HEADER_H_ #include #include #include #ifdef __cplusplus extern "C" { #endif /** Image orientation metadata. * Values 1..8 match the EXIF definitions. * The name indicates the operation to perform to transform from the encoded * image to the display image. */ typedef enum { JXL_ORIENT_IDENTITY = 1, JXL_ORIENT_FLIP_HORIZONTAL = 2, JXL_ORIENT_ROTATE_180 = 3, JXL_ORIENT_FLIP_VERTICAL = 4, JXL_ORIENT_TRANSPOSE = 5, JXL_ORIENT_ROTATE_90_CW = 6, JXL_ORIENT_ANTI_TRANSPOSE = 7, JXL_ORIENT_ROTATE_90_CCW = 8, } JxlOrientation; /** Given type of an extra channel. */ typedef enum { JXL_CHANNEL_ALPHA, JXL_CHANNEL_DEPTH, JXL_CHANNEL_SPOT_COLOR, JXL_CHANNEL_SELECTION_MASK, JXL_CHANNEL_BLACK, JXL_CHANNEL_CFA, JXL_CHANNEL_THERMAL, JXL_CHANNEL_RESERVED0, JXL_CHANNEL_RESERVED1, JXL_CHANNEL_RESERVED2, JXL_CHANNEL_RESERVED3, JXL_CHANNEL_RESERVED4, JXL_CHANNEL_RESERVED5, JXL_CHANNEL_RESERVED6, JXL_CHANNEL_RESERVED7, JXL_CHANNEL_UNKNOWN, JXL_CHANNEL_OPTIONAL } JxlExtraChannelType; /** The codestream preview header */ typedef struct { /** Preview width in pixels */ uint32_t xsize; /** Preview height in pixels */ uint32_t ysize; } JxlPreviewHeader; /** The codestream animation header, optionally present in the beginning of * the codestream, and if it is it applies to all animation frames, unlike @ref * JxlFrameHeader which applies to an individual frame. */ typedef struct { /** Numerator of ticks per second of a single animation frame time unit */ uint32_t tps_numerator; /** Denominator of ticks per second of a single animation frame time unit */ uint32_t tps_denominator; /** Amount of animation loops, or 0 to repeat infinitely */ uint32_t num_loops; /** Whether animation time codes are present at animation frames in the * codestream */ JXL_BOOL have_timecodes; } JxlAnimationHeader; /** Basic image information. This information is available from the file * signature and first part of the codestream header. */ typedef struct { /* TODO(lode): need additional fields for (transcoded) JPEG? For reusable * fields orientation must be read from Exif APP1. For has_icc_profile: must * look up where ICC profile is guaranteed to be in a JPEG file to be able to * indicate this. */ /* TODO(lode): make struct packed, and/or make this opaque struct with getter * functions (still separate struct from opaque decoder) */ /** Whether the codestream is embedded in the container format. If true, * metadata information and extensions may be available in addition to the * codestream. */ JXL_BOOL have_container; /** Width of the image in pixels, before applying orientation. */ uint32_t xsize; /** Height of the image in pixels, before applying orientation. */ uint32_t ysize; /** Original image color channel bit depth. */ uint32_t bits_per_sample; /** Original image color channel floating point exponent bits, or 0 if they * are unsigned integer. For example, if the original data is half-precision * (binary16) floating point, bits_per_sample is 16 and * exponent_bits_per_sample is 5, and so on for other floating point * precisions. */ uint32_t exponent_bits_per_sample; /** Upper bound on the intensity level present in the image in nits. For * unsigned integer pixel encodings, this is the brightness of the largest * representable value. The image does not necessarily contain a pixel * actually this bright. An encoder is allowed to set 255 for SDR images * without computing a histogram. * Leaving this set to its default of 0 lets libjxl choose a sensible default * value based on the color encoding. */ float intensity_target; /** Lower bound on the intensity level present in the image. This may be * loose, i.e. lower than the actual darkest pixel. When tone mapping, a * decoder will map [min_nits, intensity_target] to the display range. */ float min_nits; /** See the description of @see linear_below. */ JXL_BOOL relative_to_max_display; /** The tone mapping will leave unchanged (linear mapping) any pixels whose * brightness is strictly below this. The interpretation depends on * relative_to_max_display. If true, this is a ratio [0, 1] of the maximum * display brightness [nits], otherwise an absolute brightness [nits]. */ float linear_below; /** Whether the data in the codestream is encoded in the original color * profile that is attached to the codestream metadata header, or is * encoded in an internally supported absolute color space (which the decoder * can always convert to linear or non-linear sRGB or to XYB). If the original * profile is used, the decoder outputs pixel data in the color space matching * that profile, but doesn't convert it to any other color space. If the * original profile is not used, the decoder only outputs the data as sRGB * (linear if outputting to floating point, nonlinear with standard sRGB * transfer function if outputting to unsigned integers) but will not convert * 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 * ::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; /** Indicates a preview image exists near the beginning of the codestream. * The preview itself or its dimensions are not included in the basic info. */ JXL_BOOL have_preview; /** Indicates animation frames exist in the codestream. The animation * information is not included in the basic info. */ JXL_BOOL have_animation; /** Image orientation, value 1-8 matching the values used by JEITA CP-3451C * (Exif version 2.3). */ JxlOrientation orientation; /** Number of color channels encoded in the image, this is either 1 for * 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 @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 @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; /** Bit depth of the encoded alpha channel, or 0 if there is no alpha channel. * If present, matches the alpha_bits value of the JxlExtraChannelInfo * associated with this alpha channel. */ uint32_t alpha_bits; /** Alpha channel floating point exponent bits, or 0 if they are unsigned. If * present, matches the alpha_bits value of the JxlExtraChannelInfo associated * with this alpha channel. integer. */ uint32_t alpha_exponent_bits; /** Whether the alpha channel is premultiplied. Only used if there is a main * alpha channel. Matches the alpha_premultiplied value of the * JxlExtraChannelInfo associated with this alpha channel. */ JXL_BOOL alpha_premultiplied; /** Dimensions of encoded preview image, only used if have_preview is * JXL_TRUE. */ JxlPreviewHeader preview; /** Animation header with global animation properties for all frames, only * used if have_animation is JXL_TRUE. */ JxlAnimationHeader animation; /** Intrinsic width 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 * decoded image to the intrinsic dimensions. */ uint32_t intrinsic_xsize; /** 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 * decoded image to the intrinsic dimensions. */ uint32_t intrinsic_ysize; /** Padding for forwards-compatibility, in case more fields are exposed * in a future version of the library. */ uint8_t padding[100]; } JxlBasicInfo; /** Information for a single extra channel. */ typedef struct { /** Given type of an extra channel. */ JxlExtraChannelType type; /** Total bits per sample for this channel. */ uint32_t bits_per_sample; /** Floating point exponent bits per channel, or 0 if they are unsigned * integer. */ uint32_t exponent_bits_per_sample; /** The exponent the channel is downsampled by on each axis. * TODO(lode): expand this comment to match the JPEG XL specification, * specify how to upscale, how to round the size computation, and to which * extra channels this field applies. */ uint32_t dim_shift; /** Length of the extra channel name in bytes, or 0 if no name. * Excludes null termination character. */ uint32_t name_length; /** Whether alpha channel uses premultiplied alpha. Only applicable if * type is JXL_CHANNEL_ALPHA. */ JXL_BOOL alpha_premultiplied; /** Spot color of the current spot channel in linear RGBA. Only applicable if * type is JXL_CHANNEL_SPOT_COLOR. */ float spot_color[4]; /** Only applicable if type is JXL_CHANNEL_CFA. * TODO(lode): add comment about the meaning of this field. */ uint32_t cfa_channel; } JxlExtraChannelInfo; /* TODO(lode): add API to get the codestream header extensions. */ /** Extensions in the codestream header. */ typedef struct { /** Extension bits. */ uint64_t extensions; } JxlHeaderExtensions; /** Frame blend modes. * When decoding, if coalescing is enabled (default), this can be ignored. */ typedef enum { JXL_BLEND_REPLACE = 0, JXL_BLEND_ADD = 1, JXL_BLEND_BLEND = 2, JXL_BLEND_MULADD = 3, JXL_BLEND_MUL = 4, } JxlBlendMode; /** The information about blending the color channels or a single extra channel. * When decoding, if coalescing is enabled (default), this can be ignored and * the blend mode is considered to be JXL_BLEND_REPLACE. * When encoding, these settings apply to the pixel data given to the encoder. */ typedef struct { /** Blend mode. */ JxlBlendMode blendmode; /** Reference frame ID to use as the 'bottom' layer (0-3). */ uint32_t source; /** Which extra channel to use as the 'alpha' channel for blend modes * JXL_BLEND_BLEND and JXL_BLEND_MULADD. */ uint32_t alpha; /** Clamp values to [0,1] for the purpose of blending. */ JXL_BOOL clamp; } JxlBlendInfo; /** The information about layers. * When decoding, if coalescing is enabled (default), this can be ignored. * When encoding, these settings apply to the pixel data given to the encoder, * the encoder could choose an internal representation that differs. */ typedef struct { /** Whether cropping is applied for this frame. When decoding, if false, * crop_x0 and crop_y0 are set to zero, and xsize and ysize to the main * image dimensions. When encoding and this is false, those fields are * ignored. When decoding, if coalescing is enabled (default), this is always * false, regardless of the internal encoding in the JPEG XL codestream. */ JXL_BOOL have_crop; /** Horizontal offset of the frame (can be negative). */ int32_t crop_x0; /** Vertical offset of the frame (can be negative). */ int32_t crop_y0; /** Width of the frame (number of columns). */ uint32_t xsize; /** Height of the frame (number of rows). */ uint32_t ysize; /** The blending info for the color channels. Blending info for extra channels * has to be retrieved separately using JxlDecoderGetExtraChannelBlendInfo. */ JxlBlendInfo blend_info; /** 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; /** 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 @ref * JxlAnimationHeader. */ uint32_t duration; /** SMPTE timecode of the current frame in form 0xHHMMSSFF, or 0. The bits are * 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 @ref JxlAnimationHeader is ::JXL_TRUE. * This value is only used if have_timecodes in @ref JxlAnimationHeader is * ::JXL_TRUE. */ uint32_t timecode; /** Length of the frame name in bytes, or 0 if no name. * Excludes null termination character. This value is set by the decoder. * For the encoder, this value is ignored and @ref JxlEncoderSetFrameName is * used instead to set the name and the length. */ uint32_t name_length; /** Indicates this is the last animation frame. This value is set by the * decoder to indicate no further frames follow. For the encoder, it is not * required to set this value and it is ignored, @ref JxlEncoderCloseFrames is * used to indicate the last frame to the encoder instead. */ JXL_BOOL is_last; /** Information about the layer in case of no coalescing. */ JxlLayerInfo layer_info; } JxlFrameHeader; #ifdef __cplusplus } #endif #endif /* JXL_CODESTREAM_HEADER_H_ */ /** @}*/ libjxl-0.11.1/lib/include/jxl/color_encoding.h000066400000000000000000000130241472134335300212060ustar00rootroot00000000000000/* 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_color * @{ * @file color_encoding.h * @brief Color Encoding definitions used by JPEG XL. * All CIE units are for the standard 1931 2 degree observer. */ #ifndef JXL_COLOR_ENCODING_H_ #define JXL_COLOR_ENCODING_H_ #ifdef __cplusplus extern "C" { #endif /** Color space of the image data. */ typedef enum { /** Tristimulus RGB */ JXL_COLOR_SPACE_RGB, /** 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, /** None of the other table entries describe the color space appropriately */ JXL_COLOR_SPACE_UNKNOWN, } JxlColorSpace; /** 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 @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, /** DCI-P3 from SMPTE RP 431-2: 0.314, 0.351 */ JXL_WHITE_POINT_DCI = 11, } JxlWhitePoint; /** Built-in primaries for color encoding. When decoding, the primaries can be * 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 * 23091-2:2019(E)), however the white point and RGB primaries are separate * enums here. */ 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 @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, /** As specified in Rec. ITU-R BT.2100-1 */ JXL_PRIMARIES_2100 = 9, /** As specified in SMPTE RP 431-2 */ JXL_PRIMARIES_P3 = 11, } JxlPrimaries; /** Built-in transfer functions for color encoding. Enum values match a subset * of CICP (Rec. ITU-T H.273 | ISO/IEC 23091-2:2019(E)) unless specified * otherwise. */ typedef enum { /** 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, /** The gamma exponent is 1 */ JXL_TRANSFER_FUNCTION_LINEAR = 8, /** As specified in IEC 61966-2-1 sRGB */ JXL_TRANSFER_FUNCTION_SRGB = 13, /** As specified in SMPTE ST 2084 */ JXL_TRANSFER_FUNCTION_PQ = 16, /** As specified in SMPTE ST 428-1 */ 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 @ref JxlColorEncoding. Not a CICP value. */ JXL_TRANSFER_FUNCTION_GAMMA = 65535, } JxlTransferFunction; /** Rendering intent for color encoding, as specified in ISO 15076-1:2010 */ typedef enum { /** vendor-specific */ JXL_RENDERING_INTENT_PERCEPTUAL = 0, /** media-relative */ JXL_RENDERING_INTENT_RELATIVE, /** vendor-specific */ JXL_RENDERING_INTENT_SATURATION, /** ICC-absolute */ JXL_RENDERING_INTENT_ABSOLUTE, } JxlRenderingIntent; /** Color encoding of the image as structured information. */ typedef struct { /** Color space of the image data. */ JxlColorSpace color_space; /** 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 * 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. */ JxlPrimaries primaries; /** Numerical red primary values in CIE xy space. */ double primaries_red_xy[2]; /** Numerical green primary values in CIE xy space. */ double primaries_green_xy[2]; /** Numerical blue primary values in CIE xy space. */ double primaries_blue_xy[2]; /** Transfer function if have_gamma is 0 */ JxlTransferFunction transfer_function; /** Gamma value used when transfer_function is @ref * JXL_TRANSFER_FUNCTION_GAMMA */ double gamma; /** Rendering intent defined for the color profile. */ JxlRenderingIntent rendering_intent; } JxlColorEncoding; #ifdef __cplusplus } #endif #endif /* JXL_COLOR_ENCODING_H_ */ /** @}*/ libjxl-0.11.1/lib/include/jxl/compressed_icc.h000066400000000000000000000052611472134335300212100ustar00rootroot00000000000000/* 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_ */ /** @} */ libjxl-0.11.1/lib/include/jxl/decode.h000066400000000000000000002121521472134335300174500ustar00rootroot00000000000000/* 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_decoder * @{ * @file decode.h * @brief Decoding API for JPEG XL. */ #ifndef JXL_DECODE_H_ #define JXL_DECODE_H_ #include #include #include #include #include #include #include #include // TODO(eustas): remove before v1.0 #include #include #ifdef __cplusplus extern "C" { #endif /** * Decoder library version. * * @return the decoder library version as an integer: * MAJOR_VERSION * 1000000 + MINOR_VERSION * 1000 + PATCH_VERSION. For example, * version 1.2.3 would return 1002003. */ JXL_EXPORT uint32_t JxlDecoderVersion(void); /** The result of @ref JxlSignatureCheck. */ typedef enum { /** Not enough bytes were passed to determine if a valid signature was found. */ JXL_SIG_NOT_ENOUGH_BYTES = 0, /** No valid JPEG XL header was found. */ JXL_SIG_INVALID = 1, /** A valid JPEG XL codestream signature was found, that is a JPEG XL image * without container. */ JXL_SIG_CODESTREAM = 2, /** A valid container signature was found, that is a JPEG XL image embedded * in a box format container. */ JXL_SIG_CONTAINER = 3, } JxlSignature; /** * JPEG XL signature identification. * * Checks if the passed buffer contains a valid JPEG XL signature. The passed @p * buf of size * @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. * - ::JXL_SIG_NOT_ENOUGH_BYTES if not enough bytes were passed to * determine if a valid signature is there. * - ::JXL_SIG_INVALID if no valid signature found for JPEG XL decoding. * - ::JXL_SIG_CODESTREAM if a valid JPEG XL codestream 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); /** * Opaque structure that holds the JPEG XL decoder. * * Allocated and initialized with @ref JxlDecoderCreate(). * Cleaned up and deallocated with @ref JxlDecoderDestroy(). */ typedef struct JxlDecoderStruct JxlDecoder; /** * Creates an instance of @ref JxlDecoder 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 @ref JxlDecoder otherwise */ JXL_EXPORT JxlDecoder* JxlDecoderCreate(const JxlMemoryManager* memory_manager); /** * Re-initializes a @ref JxlDecoder instance, so it can be re-used for decoding * another image. All state and settings are reset as if the object was * newly created with @ref JxlDecoderCreate, but the memory manager is kept. * * @param dec instance to be re-initialized. */ JXL_EXPORT void JxlDecoderReset(JxlDecoder* dec); /** * Deinitializes and frees @ref JxlDecoder instance. * * @param dec instance to be cleaned up and deallocated. */ JXL_EXPORT void JxlDecoderDestroy(JxlDecoder* dec); /** * Return value for @ref JxlDecoderProcessInput. * 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. */ 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 * 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 * 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. */ JXL_DEC_SUCCESS = 0, /** An error occurred, for example invalid input file or out of memory. * TODO(lode): add function to get error information from decoder. */ JXL_DEC_ERROR = 1, /** The decoder needs more input bytes to continue. Before the next @ref * JxlDecoderProcessInput call, more input data must be set, by calling @ref * JxlDecoderReleaseInput (if input was set previously) and then calling @ref * JxlDecoderSetInput. @ref JxlDecoderReleaseInput returns how many bytes * are not yet processed, before a next call to @ref JxlDecoderProcessInput * all unprocessed bytes must be provided again (the address need not match, * but the contents must), and more bytes must be concatenated after the * unprocessed bytes. * 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. */ 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 ::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 * end of the frame header (including ToC) of the preview frame as * unprocessed. */ JXL_DEC_NEED_PREVIEW_OUT_BUFFER = 3, /** 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 * there are multiple animation frames and requires setting an output again. * In this case, @ref JxlDecoderReleaseInput will return all bytes from the * end of the frame header (including ToC) as unprocessed. */ JXL_DEC_NEED_IMAGE_OUT_BUFFER = 5, /** 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 ::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 ::JXL_DEC_BOX. */ JXL_DEC_BOX_NEED_MORE_OUTPUT = 7, /** Informative event by @ref JxlDecoderProcessInput * "JxlDecoderProcessInput": Basic information such as image dimensions and * extra channels. This event occurs max once per image. * In this case, @ref JxlDecoderReleaseInput will return all bytes from the * end of the basic info as unprocessed (including the last byte of basic info * if it did not end on a byte boundary). */ JXL_DEC_BASIC_INFO = 0x40, /** 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 ::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. */ JXL_DEC_COLOR_ENCODING = 0x100, /** Informative event by @ref JxlDecoderProcessInput * "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 ::JXL_DEC_FRAME. * In this case, @ref JxlDecoderReleaseInput will return all bytes from the * end of the preview frame as unprocessed. */ JXL_DEC_PREVIEW_IMAGE = 0x200, /** Informative event by @ref JxlDecoderProcessInput * "JxlDecoderProcessInput": Beginning of a frame. @ref * 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 @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": 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. * In this case, @ref JxlDecoderReleaseInput will return all bytes from the * 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. */ JXL_DEC_FULL_IMAGE = 0x1000, /** Informative event by @ref JxlDecoderProcessInput * "JxlDecoderProcessInput": JPEG reconstruction data decoded. @ref * JxlDecoderSetJPEGBuffer may be used to set a JPEG reconstruction buffer * after getting the JPEG reconstruction data. If a JPEG reconstruction buffer * 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 ::JXL_DEC_FULL_IMAGE. * In this case, @ref JxlDecoderReleaseInput will return all bytes from the * end of the `jbrd` box as unprocessed. */ JXL_DEC_JPEG_RECONSTRUCTION = 0x2000, /** Informative event by @ref JxlDecoderProcessInput * "JxlDecoderProcessInput": The header of a box of the container format * (BMFF) is decoded. The following API functions related to boxes can be used * after this event: * - @ref JxlDecoderSetBoxBuffer and @ref JxlDecoderReleaseBoxBuffer * "JxlDecoderReleaseBoxBuffer": set and release a buffer to get the box * data. * - @ref JxlDecoderGetBoxType get the 4-character box typename. * - @ref JxlDecoderGetBoxSizeRaw get the size of the box as it appears in * the container file, not decompressed. * - @ref JxlDecoderSetDecompressBoxes to configure whether to get the box * data decompressed, or possibly compressed. * * Boxes can be compressed. This is so when their box type is * "brob". In that case, they have an underlying decompressed box * type and decompressed data. @ref JxlDecoderSetDecompressBoxes allows * configuring which data to get. Decompressing requires * Brotli. @ref JxlDecoderGetBoxType has a flag to get the compressed box * type, which can be "brob", or the decompressed box type. If a box * is not compressed (its compressed type is not "brob"), then * the output decompressed box type and data is independent of what * setting is configured. * * 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 * ::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. */ JXL_DEC_BOX = 0x4000, /** Informative event by @ref JxlDecoderProcessInput * "JxlDecoderProcessInput": a progressive step in decoding the frame is * reached. When calling @ref JxlDecoderFlushImage at this point, the flushed * image will correspond exactly to this point in decoding, and not yet * contain partial results (such as partially more fine detail) of a next * step. By default, this event will trigger maximum once per frame, when a * 8x8th resolution (DC) image is ready (the image data is still returned at * full resolution, giving upscaled DC). Use @ref * JxlDecoderSetProgressiveDetail to configure more fine-grainedness. The * event is not guaranteed to trigger, not all images have progressive steps * or DC encoded. * In this case, @ref JxlDecoderReleaseInput will return all bytes from the * end of the section that was needed to produce this progressive event as * 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 * keep state about the image, which it can use to skip to a requested frame * 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 ::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 * - @ref JxlDecoderSetCoalescing, * - @ref JxlDecoderSetDesiredIntensityTarget, * - @ref JxlDecoderSetDecompressBoxes, * - @ref JxlDecoderSetKeepOrientation, * - @ref JxlDecoderSetUnpremultiplyAlpha, * - @ref JxlDecoderSetParallelRunner, * - @ref JxlDecoderSetRenderSpotcolors, and * - @ref JxlDecoderSubscribeEvents. * * @param dec decoder object */ JXL_EXPORT void JxlDecoderRewind(JxlDecoder* dec); /** Makes the decoder skip the next `amount` frames. It still needs to process * 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 ::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 ::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. * * @param dec decoder object * @param amount the amount of frames to skip */ 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 ::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 ::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); /** * Set the parallel runner for multithreading. May only be set before starting * decoding. * * @param dec decoder object * @param parallel_runner function pointer to runner for multithreading. It may * 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_DEC_SUCCESS if the runner was set, ::JXL_DEC_ERROR * otherwise (the previous runner remains set). */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetParallelRunner(JxlDecoder* dec, JxlParallelRunner parallel_runner, void* parallel_runner_opaque); /** * Returns a hint indicating how many more bytes the decoder is expected to * need to make @ref JxlDecoderGetBasicInfo available after the next @ref * JxlDecoderProcessInput call. This is a suggested large enough value for * the amount of bytes to provide in the next @ref JxlDecoderSetInput call, but * it is not guaranteed to be an upper bound nor a lower bound. This number does * not include bytes that have already been released from the input. Can be used * before the first @ref JxlDecoderProcessInput call, and is correct the first * time in most cases. If not, @ref JxlDecoderSizeHintBasicInfo can be called * again to get an updated hint. * * @param dec decoder object * @return the size hint in bytes if the basic info is not yet fully decoded. * @return 0 when the basic info is already available. */ JXL_EXPORT size_t JxlDecoderSizeHintBasicInfo(const JxlDecoder* dec); /** 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 * the decoder will then only return when it cannot continue because it needs * more input data or more output buffer. This function may only be be called * before using @ref JxlDecoderProcessInput. * * @param dec decoder object * @param events_wanted bitfield of desired events. * @return ::JXL_DEC_SUCCESS if no error, ::JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSubscribeEvents(JxlDecoder* dec, int events_wanted); /** Enables or disables preserving of as-in-bitstream pixeldata * orientation. Some images are encoded with an Orientation tag * 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 * will apply the transformation from the orientation setting, hence * rendering the image according to its specified intent. When * 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 "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 * transformation, but can cause wrong display of the image if the * orientation tag is not correctly taken into account by the user. * * By default, this option is disabled, and the returned pixel data is * re-oriented according to the image's Orientation setting. * * This function must be called at the beginning, before decoding is performed. * * @see JxlBasicInfo for the orientation field, and @ref JxlOrientation for the * possible values. * * @param dec decoder object * @param skip_reorientation JXL_TRUE to enable, JXL_FALSE to disable. * @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 @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". * * This function must be called at the beginning, before decoding is performed. * * @param dec decoder object * @param unpremul_alpha JXL_TRUE to enable, JXL_FALSE to disable. * @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. * * @param dec decoder object * @param render_spotcolors JXL_TRUE to enable (default), JXL_FALSE to disable. * @return ::JXL_DEC_SUCCESS if no error, ::JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetRenderSpotcolors(JxlDecoder* dec, JXL_BOOL render_spotcolors); /** Enables or disables coalescing of zero-duration frames. By default, frames * are returned with coalescing enabled, i.e. all frames have the image * dimensions, and are blended if needed. When coalescing is disabled, frames * can have arbitrary dimensions, a non-zero crop offset, and blending is not * performed. For display, coalescing is recommended. For loading a multi-layer * still image as separate layers (as opposed to the merged image), coalescing * has to be disabled. * * @param dec decoder object * @param coalescing JXL_TRUE to enable coalescing (default), JXL_FALSE to * disable it. * @return ::JXL_DEC_SUCCESS if no error, ::JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetCoalescing(JxlDecoder* dec, JXL_BOOL coalescing); /** * Decodes JPEG XL file using the available bytes. Requires input has been * set with @ref JxlDecoderSetInput. After @ref JxlDecoderProcessInput, input * can optionally be released with @ref JxlDecoderReleaseInput and then set * again to next bytes in the stream. @ref JxlDecoderReleaseInput returns how * many bytes are not yet processed, before a next call to @ref * JxlDecoderProcessInput all unprocessed bytes must be provided again (the * address need not match, but the contents must), and more bytes may be * concatenated after the unprocessed bytes. * * 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 ::JXL_DEC_ERROR), new information, such * as @ref JxlDecoderGetBasicInfo, may have become available after this call. * 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 ::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. ::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 ::JXL_DEC_ERROR when decoding failed, e.g. invalid codestream. * TODO(lode): document the input data mechanism * @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 ::JXL_DEC_COLOR_ENCODING when color profile information is * available and this informative event is subscribed to. * @return ::JXL_DEC_PREVIEW_IMAGE when preview pixel information is * available and output in the preview buffer. * @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); /** * Sets input data for @ref JxlDecoderProcessInput. The data is owned by the * caller and may be used by the decoder until @ref JxlDecoderReleaseInput is * called or the decoder is destroyed or reset so must be kept alive until then. * Cannot be called if @ref JxlDecoderSetInput was already called and @ref * JxlDecoderReleaseInput was not yet called, and cannot be called after @ref * JxlDecoderCloseInput indicating the end of input was called. * * @param dec decoder object * @param data pointer to next bytes to read from * @param size amount of bytes available starting from data * @return ::JXL_DEC_ERROR if input was already set without releasing or @ref * JxlDecoderCloseInput was already called, ::JXL_DEC_SUCCESS otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetInput(JxlDecoder* dec, const uint8_t* data, size_t size); /** * Releases input which was provided with @ref JxlDecoderSetInput. Between @ref * JxlDecoderProcessInput and @ref JxlDecoderReleaseInput, the user may not * alter the data in the buffer. Calling @ref JxlDecoderReleaseInput is required * 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`. * * @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. */ JXL_EXPORT size_t JxlDecoderReleaseInput(JxlDecoder* dec); /** * Marks the input as finished, indicates that no more @ref JxlDecoderSetInput * will be called. This function allows the decoder to determine correctly if it * should return success, need more input or error in certain cases. For * backwards compatibility with a previous version of the API, using this * function is optional when not using the ::JXL_DEC_BOX event (the decoder * is able to determine the end of the image frames without marking the end), * but using this function is required when using ::JXL_DEC_BOX for getting * metadata box contents. This function does not replace @ref * JxlDecoderReleaseInput, that function should still be called if its return * value is needed. * * @ref JxlDecoderCloseInput should be called as soon as all known input bytes * are set (e.g. at the beginning when not streaming but setting all input * at once), before the final @ref JxlDecoderProcessInput calls. * * @param dec decoder object */ JXL_EXPORT void JxlDecoderCloseInput(JxlDecoder* dec); /** * Outputs the basic image information, such as image dimensions, bit depth and * all other JxlBasicInfo fields, if available. * * @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 ::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, JxlBasicInfo* info); /** * Outputs information for extra channel at the given index. The index must be * 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 ::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( const JxlDecoder* dec, size_t index, JxlExtraChannelInfo* info); /** * Outputs name for extra channel at the given index in UTF-8. The index must be * 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 ::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, size_t index, char* name, size_t size); /** Defines which color profile to get: the profile from the codestream * metadata header, which represents the color profile of the original image, * or the color profile from the pixel data produced by the decoder. Both are * the same if the JxlBasicInfo has uses_original_profile set. */ typedef enum { /** Get the color profile of the original image from the metadata. */ JXL_COLOR_PROFILE_TARGET_ORIGINAL = 0, /** Get the color profile of the pixel data the decoder outputs. */ JXL_COLOR_PROFILE_TARGET_DATA = 1, } JxlColorProfileTarget; /** * Outputs the color profile as JPEG XL encoded structured data, if available. * This is an alternative to an ICC Profile, which can represent a more limited * amount of color spaces, but represents them exactly through enum values. * * 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 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. * You can still use @ref JxlDecoderGetColorAsICCProfile as well as an * alternative if desired, though depending on which RGB color space is * 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 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 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 ::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, JxlColorProfileTarget target, JxlColorEncoding* color_encoding); /** * Outputs the size in bytes of the ICC profile returned by @ref * JxlDecoderGetColorAsICCProfile, if available, or indicates there is none * available. In most cases, the image will have an ICC profile available, but * if it does not, @ref JxlDecoderGetColorAsEncodedProfile must be used instead. * * @see JxlDecoderGetColorAsEncodedProfile for more information. The ICC * profile is either the exact ICC profile attached to the codestream metadata, * or a close approximation generated from JPEG XL encoded structured data, * depending of what is encoded in the codestream. * * @param dec decoder object * @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 ::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, ::JXL_DEC_ERROR in case the ICC profile is not available and * cannot be generated. */ JXL_EXPORT JxlDecoderStatus JxlDecoderGetICCProfileSize( const JxlDecoder* dec, JxlColorProfileTarget target, size_t* size); /** * Outputs ICC profile if available. The profile is only available if @ref * JxlDecoderGetICCProfileSize returns success. The output buffer must have * at least as many bytes as given by @ref JxlDecoderGetICCProfileSize. * * @param dec decoder object * @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 ::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, JxlColorProfileTarget target, uint8_t* icc_profile, size_t size); /** 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 ::JXL_DEC_SUCCESS if the preference was set successfully, @ref * JXL_DEC_ERROR otherwise. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetPreferredColorProfile( JxlDecoder* dec, const JxlColorEncoding* color_encoding); /** Requests that the decoder perform tone mapping to the peak display luminance * passed as @c desired_intensity_target, if appropriate. * @note This is provided for convenience and the exact tone mapping that is * performed is not meant to be considered authoritative in any way. It may * change from version to version. * @param dec decoder object * @param desired_intensity_target the intended target peak luminance * @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 * JxlDecoderSetPreviewOutBuffer. Requires the preview header information is * available in the decoder. * * @param dec decoder object * @param format format of pixels * @param size output value, buffer size in bytes * @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 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 @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 ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such as * size too small. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetPreviewOutBuffer( JxlDecoder* dec, const JxlPixelFormat* format, void* buffer, size_t size); /** * Outputs the information from the frame, such as duration when have_animation. * 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 ::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, JxlFrameHeader* header); /** * Outputs name for the current frame. The buffer for name must have at least * `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 @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, char* name, size_t size); /** * Outputs the blend information for the current frame for a specific extra * 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 ::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 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 ::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 ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such as * information not available yet. */ JXL_EXPORT JxlDecoderStatus JxlDecoderImageOutBufferSize( const JxlDecoder* dec, const JxlPixelFormat* format, size_t* size); /** * Sets the buffer to write the full resolution image to. This can be set when * 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 @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 ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such as * size too small. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetImageOutBuffer( JxlDecoder* dec, const JxlPixelFormat* format, void* buffer, size_t size); /** * Function type for @ref JxlDecoderSetImageOutCallback. * * The callback may be called simultaneously by different threads when using a * threaded parallel runner, on different pixels. * * @param opaque optional user data, as given to @ref * JxlDecoderSetImageOutCallback. * @param x horizontal position of leftmost pixel of the pixel data. * @param y vertical position of the pixel data. * @param num_pixels amount of pixels included in the pixel data, horizontally. * This is not the same as xsize of the full image, it may be smaller. * @param pixels pixel data as a horizontal stripe, in the format passed to @ref * JxlDecoderSetImageOutCallback. The memory is not owned by the user, and * is only valid during the time the callback is running. */ typedef void (*JxlImageOutCallback)(void* opaque, size_t x, size_t y, size_t num_pixels, const void* pixels); /** * Initialization callback for @ref JxlDecoderSetMultithreadedImageOutCallback. * * @param init_opaque optional user data, as given to @ref * JxlDecoderSetMultithreadedImageOutCallback. * @param num_threads maximum number of threads that will call the @c run * callback concurrently. * @param num_pixels_per_thread maximum number of pixels that will be passed in * one call to @c run. * @return a pointer to data that will be passed to the @c run callback, or * @c NULL if initialization failed. */ typedef void* (*JxlImageOutInitCallback)(void* init_opaque, size_t num_threads, size_t num_pixels_per_thread); /** * Worker callback for @ref JxlDecoderSetMultithreadedImageOutCallback. * * @param run_opaque user data returned by the @c init callback. * @param thread_id number in `[0, num_threads)` identifying the thread of the * current invocation of the callback. * @param x horizontal position of the first (leftmost) pixel of the pixel data. * @param y vertical position of the pixel data. * @param num_pixels number of pixels in the pixel data. May be less than the * full @c xsize of the image, and will be at most equal to the @c * num_pixels_per_thread that was passed to @c init. * @param pixels pixel data as a horizontal stripe, in the format passed to @ref * JxlDecoderSetMultithreadedImageOutCallback. The data pointed to * remains owned by the caller and is only guaranteed to outlive the current * callback invocation. */ typedef void (*JxlImageOutRunCallback)(void* run_opaque, size_t thread_id, size_t x, size_t y, size_t num_pixels, const void* pixels); /** * Destruction callback for @ref JxlDecoderSetMultithreadedImageOutCallback, * called after all invocations of the @c run callback to perform any * appropriate clean-up of the @c run_opaque data returned by @c init. * * @param run_opaque user data returned by the @c init callback. */ typedef void (*JxlImageOutDestroyCallback)(void* run_opaque); /** * Sets pixel output callback. This is an alternative to @ref * 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 * 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 * simultaneously by different threads when using a threaded parallel runner, on * different pixels. * * If @ref JxlDecoderFlushImage is not used, then each pixel will be visited * exactly once by the different callback calls, during processing with one or * more @ref JxlDecoderProcessInput calls. These pixels are decoded to full * detail, they are not part of a lower resolution or lower quality progressive * pass, but the final pass. * * If @ref JxlDecoderFlushImage is used, then in addition each pixel will be * visited zero or one times during the blocking @ref JxlDecoderFlushImage call. * Pixels visited as a result of @ref JxlDecoderFlushImage may represent a lower * resolution or lower quality intermediate progressive pass of the image. Any * visited pixel will be of a quality at least as good or better than previous * visits of this pixel. A pixel may be visited zero times if it cannot be * decoded yet or if it was already decoded to full precision (this behavior is * not guaranteed). * * @param dec decoder object * @param format format of the pixels. Object owned by user; its contents are * copied internally. * @param callback the callback function receiving partial scanlines of pixel * data. * @param opaque optional user data, which will be passed on to the callback, * may be NULL. * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such * as @ref JxlDecoderSetImageOutBuffer already set. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetImageOutCallback(JxlDecoder* dec, const JxlPixelFormat* format, JxlImageOutCallback callback, void* opaque); /** Similar to @ref JxlDecoderSetImageOutCallback except that the callback is * allowed an initialization phase during which it is informed of how many * threads will call it concurrently, and those calls are further informed of * which thread they are occurring in. * * @param dec decoder object * @param format format of the pixels. Object owned by user; its contents are * copied internally. * @param init_callback initialization callback. * @param run_callback the callback function receiving partial scanlines of * pixel data. * @param destroy_callback clean-up callback invoked after all calls to @c * run_callback. May be NULL if no clean-up is necessary. * @param init_opaque optional user data passed to @c init_callback, may be NULL * (unlike the return value from @c init_callback which may only be NULL if * initialization failed). * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such * as @ref JxlDecoderSetImageOutBuffer having already been called. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetMultithreadedImageOutCallback( JxlDecoder* dec, const JxlPixelFormat* format, JxlImageOutInitCallback init_callback, JxlImageOutRunCallback run_callback, JxlImageOutDestroyCallback destroy_callback, void* init_opaque); /** * Returns the minimum size in bytes of an extra channel pixel buffer for the * given format. This is the buffer for @ref JxlDecoderSetExtraChannelBuffer. * Requires the basic image information is available in the decoder. * * @param dec decoder object * @param format format of the pixels. The num_channels value is ignored and is * 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 @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( const JxlDecoder* dec, const JxlPixelFormat* format, size_t* size, uint32_t index); /** * Sets the buffer to write an extra channel to. This can be set when * 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 @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 * as part of the color channels when using an RGBA pixel buffer with @ref * JxlDecoderSetImageOutBuffer, but additionally also can be gotten * separately as extra channel. The color channels themselves cannot be gotten * this way. * * * @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`. * @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 @ref JxlBasicInfo. * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such as * size too small or invalid index. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetExtraChannelBuffer(JxlDecoder* dec, const JxlPixelFormat* format, void* buffer, size_t size, uint32_t index); /** * Sets output buffer for reconstructed JPEG codestream. * * The data is owned by the caller and may be used by the decoder until @ref * JxlDecoderReleaseJPEGBuffer is called or the decoder is destroyed or * reset so must be kept alive until then. * * If a JPEG buffer was set before and released with @ref * JxlDecoderReleaseJPEGBuffer, bytes that the decoder has already output * should not be included, only the remaining bytes output must be set. * * @param dec decoder object * @param data pointer to next bytes to write to * @param size amount of bytes available starting from data * @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, uint8_t* data, size_t size); /** * Releases buffer which was provided with @ref JxlDecoderSetJPEGBuffer. * * Calling @ref JxlDecoderReleaseJPEGBuffer is required whenever * a buffer is already set and a new buffer needs to be added with @ref * JxlDecoderSetJPEGBuffer, but is not required before @ref * JxlDecoderDestroy or @ref JxlDecoderReset. * * Calling @ref JxlDecoderReleaseJPEGBuffer when no buffer is set is * 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 * JxlDecoderReleaseJPEGBuffer was already called. */ JXL_EXPORT size_t JxlDecoderReleaseJPEGBuffer(JxlDecoder* dec); /** * Sets output buffer for box output codestream. * * The data is owned by the caller and may be used by the decoder until @ref * JxlDecoderReleaseBoxBuffer is called or the decoder is destroyed or * reset so must be kept alive until then. * * If for the current box a box buffer was set before and released with @ref * 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 ::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 ::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, uint8_t* data, size_t size); /** * Releases buffer which was provided with @ref JxlDecoderSetBoxBuffer. * * Calling @ref JxlDecoderReleaseBoxBuffer is required whenever * a buffer is already set and a new buffer needs to be added with @ref * JxlDecoderSetBoxBuffer, but is not required before @ref * JxlDecoderDestroy or @ref JxlDecoderReset. * * Calling @ref JxlDecoderReleaseBoxBuffer when no buffer is set is * 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 * JxlDecoderReleaseBoxBuffer was already called. */ JXL_EXPORT size_t JxlDecoderReleaseBoxBuffer(JxlDecoder* dec); /** * Configures whether to get boxes in raw mode or in decompressed mode. In raw * mode, boxes are output as their bytes appear in the container file, which may * be decompressed, or compressed if their type is "brob". In decompressed mode, * "brob" boxes are decompressed with Brotli before outputting them. The size of * the decompressed stream is not known before the decompression has already * finished. * * The default mode is raw. This setting can only be changed before decoding, or * 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 ::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 ::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. * * The following box types are currently described in ISO/IEC 18181-2: * - "Exif": a box with EXIF metadata. Starts with a 4-byte tiff header offset * (big-endian uint32) that indicates the start of the actual EXIF data * (which starts with a tiff header). Usually the offset will be zero and the * EXIF data starts immediately after the offset field. The Exif orientation * should be ignored by applications; the JPEG XL codestream orientation * takes precedence and libjxl will by default apply the correct orientation * automatically (see @ref JxlDecoderSetKeepOrientation). * - "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 * @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 * profile, and all the pixel data of all the image frames. * - "jxlp": a codestream box in case it is split across multiple boxes. * The contents are the same as in case of a jxlc box, when concatenated. * - "brob": a Brotli-compressed box, which otherwise represents an existing * type of box such as Exif or "xml ". When @ref JxlDecoderSetDecompressBoxes * is set to JXL_TRUE, these boxes will be transparently decompressed by the * decoder. * - "jxli": frame index box, can list the keyframes in case of a JPEG XL * animation allowing the decoder to jump to individual frames more * efficiently. * - "jbrd": JPEG reconstruction box, contains the information required to * 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 * contains information such as the remaining app markers of the JPEG-1 file * and everything else required to fit the information together into the * exact original JPEG file. * * Other application-specific boxes can exist. Their typename should not begin * with "jxl" or "JXL" or conflict with other existing typenames. * * The signature, jxl* and jbrd boxes are processed by the decoder and would * typically be ignored by applications. The typical way to use this function is * to check if an encountered box contains metadata that the application is * interested in (e.g. EXIF or XMP metadata), in order to conditionally set a * box buffer. * * @param dec decoder object * @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 ::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, JXL_BOOL decompressed); /** * Returns the size of a box as it appears in the container file, after the @ref * JXL_DEC_BOX event. This includes all the box headers. * * @param dec decoder object * @param size raw size of the box in bytes * @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 * of detail if this function is never called is `kDC`. * * @param dec decoder object * @param detail at which level of detail to trigger @ref * JXL_DEC_FRAME_PROGRESSION * @return ::JXL_DEC_SUCCESS on success, ::JXL_DEC_ERROR on error, such as * an invalid value for the progressive detail. */ JXL_EXPORT JxlDecoderStatus JxlDecoderSetProgressiveDetail(JxlDecoder* dec, JxlProgressiveDetail detail); /** * Returns the intended downsampling ratio for the progressive frame produced * 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`. */ JXL_EXPORT size_t JxlDecoderGetIntendedDownsamplingRatio(JxlDecoder* dec); /** * Outputs progressive step towards the decoded image so far when only partial * input was received. If the flush was successful, the buffer set with @ref * JxlDecoderSetImageOutBuffer will contain partial image data. * * Can be called when @ref JxlDecoderProcessInput returns @ref * 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 ::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); /** * 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 #endif /* JXL_DECODE_H_ */ /** @}*/ libjxl-0.11.1/lib/include/jxl/decode_cxx.h000066400000000000000000000035271472134335300203360ustar00rootroot00000000000000// 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_cpp /// @{ /// /// @file decode_cxx.h /// @brief C++ header-only helper for @ref decode.h. /// /// There's no binary library associated with the header since this is a header /// only library. #ifndef JXL_DECODE_CXX_H_ #define JXL_DECODE_CXX_H_ #include #include #include #ifndef __cplusplus #error "This a C++ only header. Use jxl/decode.h from C sources." #endif /// Struct to call JxlDecoderDestroy from the JxlDecoderPtr unique_ptr. struct JxlDecoderDestroyStruct { /// Calls @ref JxlDecoderDestroy() on the passed decoder. void operator()(JxlDecoder* decoder) { JxlDecoderDestroy(decoder); } }; /// std::unique_ptr<> type that calls JxlDecoderDestroy() when releasing the /// decoder. /// /// Use this helper type from C++ sources to ensure the decoder is destroyed and /// their internal resources released. typedef std::unique_ptr JxlDecoderPtr; /// Creates an instance of JxlDecoder into a JxlDecoderPtr and initializes it. /// /// This function returns a unique_ptr that will call JxlDecoderDestroy() when /// releasing the pointer. See @ref JxlDecoderCreate for details on the /// instance creation. /// /// @param memory_manager custom allocator function. It may be NULL. The memory /// manager will be copied internally. /// @return a @c NULL JxlDecoderPtr if the instance can not be allocated or /// initialized /// @return initialized JxlDecoderPtr instance otherwise. static inline JxlDecoderPtr JxlDecoderMake( const JxlMemoryManager* memory_manager) { return JxlDecoderPtr(JxlDecoderCreate(memory_manager)); } #endif // JXL_DECODE_CXX_H_ /// @} libjxl-0.11.1/lib/include/jxl/encode.h000066400000000000000000002106441472134335300174660ustar00rootroot00000000000000/* 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 encode.h * @brief Encoding API for JPEG XL. */ #ifndef JXL_ENCODE_H_ #define JXL_ENCODE_H_ #include #include #include #include #include #include #include #include #include // TODO(eustas): remove before v1.0 #include #include #ifdef __cplusplus extern "C" { #endif /** * Encoder library version. * * @return the encoder library version as an integer: * MAJOR_VERSION * 1000000 + MINOR_VERSION * 1000 + PATCH_VERSION. For example, * version 1.2.3 would return 1002003. */ JXL_EXPORT uint32_t JxlEncoderVersion(void); /** * Opaque structure that holds the JPEG XL encoder. * * Allocated and initialized with @ref JxlEncoderCreate(). * Cleaned up and deallocated with @ref JxlEncoderDestroy(). */ 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 @ref JxlEncoderFrameSettingsCreate(). * Cleaned up and deallocated when the encoder is destroyed with * @ref JxlEncoderDestroy(). */ typedef struct JxlEncoderFrameSettingsStruct JxlEncoderFrameSettings; /** * Return value for multiple encoder functions. */ typedef enum { /** Function call finished successfully, or encoding is finished and there is * nothing more to be done. */ JXL_ENC_SUCCESS = 0, /** An error occurred, for example out of memory. */ JXL_ENC_ERROR = 1, /** The encoder needs more output buffer to continue encoding. */ JXL_ENC_NEED_MORE_OUTPUT = 2, } JxlEncoderStatus; /** * Error conditions: * API usage errors have the 0x80 bit set to 1 * Other errors have the 0x80 bit set to 0 */ typedef enum { /** No error */ JXL_ENC_ERR_OK = 0, /** Generic encoder error due to unspecified cause */ JXL_ENC_ERR_GENERIC = 1, /** Out of memory * TODO(jon): actually catch this and return this error */ JXL_ENC_ERR_OOM = 2, /** JPEG bitstream reconstruction data could not be * represented (e.g. too much tail data) */ JXL_ENC_ERR_JBRD = 3, /** Input is invalid (e.g. corrupt JPEG file or ICC profile) */ JXL_ENC_ERR_BAD_INPUT = 4, /** The encoder doesn't (yet) support this. Either no version of libjxl * supports this, and the API is used incorrectly, or the libjxl version * should have been checked before trying to do this. */ JXL_ENC_ERR_NOT_SUPPORTED = 0x80, /** The encoder API is used in an incorrect way. * In this case, a debug build of libjxl should output a specific error * message. (if not, please open an issue about it) */ JXL_ENC_ERR_API_USAGE = 0x81, } JxlEncoderError; /** * Id of encoder options for a frame. This includes options such as setting * encoding effort/speed or overriding the use of certain coding tools, for this * frame. This does not include non-frame related encoder options such as for * boxes. */ 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 10:glacier. * Default: squirrel (7). */ JXL_ENC_FRAME_SETTING_EFFORT = 0, /** Sets the decoding speed tier for the provided options. Minimum is 0 * (slowest to decode, best quality/density), and maximum is 4 (fastest to * decode, at the cost of some quality/density). Default is 0. */ JXL_ENC_FRAME_SETTING_DECODING_SPEED = 1, /** Sets resampling option. If enabled, the image is downsampled before * compression, and upsampled to original size in the decoder. Integer option, * use -1 for the default behavior (resampling only applied for low quality), * 1 for no downsampling (1x1), 2 for 2x2 downsampling, 4 for 4x4 * downsampling, 8 for 8x8 downsampling. */ JXL_ENC_FRAME_SETTING_RESAMPLING = 2, /** 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. */ JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING = 3, /** Indicates the frame added with @ref JxlEncoderAddImageFrame is already * downsampled by the downsampling factor set with @ref * JXL_ENC_FRAME_SETTING_RESAMPLING. The input frame must then be given in the * 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 ::JXL_ENC_FRAME_SETTING_RESAMPLING. * Use 0 to disable, 1 to enable. Default value is 0. */ JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED = 4, /** Adds noise to the image emulating photographic film noise, the higher the * given number, the grainier the image will be. As an example, a value of 100 * gives low noise whereas a value of 3200 gives a lot of noise. The default * value is 0. */ JXL_ENC_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. */ JXL_ENC_FRAME_SETTING_NOISE = 6, /** Enables or disables dots generation. Use -1 for the default (encoder * chooses), 0 to disable, 1 to enable. */ JXL_ENC_FRAME_SETTING_DOTS = 7, /** Enables or disables patches generation. Use -1 for the default (encoder * chooses), 0 to disable, 1 to enable. */ JXL_ENC_FRAME_SETTING_PATCHES = 8, /** Edge preserving filter level, -1 to 3. Use -1 for the default (encoder * chooses), 0 to 3 to set a strength. */ JXL_ENC_FRAME_SETTING_EPF = 9, /** Enables or disables the gaborish filter. Use -1 for the default (encoder * chooses), 0 to disable, 1 to enable. */ JXL_ENC_FRAME_SETTING_GABORISH = 10, /** Enables modular encoding. Use -1 for default (encoder * chooses), 0 to enforce VarDCT mode (e.g. for photographic images), 1 to * enforce modular mode (e.g. for lossless images). */ JXL_ENC_FRAME_SETTING_MODULAR = 11, /** Enables or disables preserving color of invisible pixels. Use -1 for the * default (1 if lossless, 0 if lossy), 0 to disable, 1 to enable. */ JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE = 12, /** Determines the order in which 256x256 regions are stored in the codestream * for progressive rendering. Use -1 for the encoder * default, 0 for scanline order, 1 for center-first order. */ JXL_ENC_FRAME_SETTING_GROUP_ORDER = 13, /** Determines the horizontal position of center for the center-first group * order. Use -1 to automatically use the middle of the image, 0..xsize to * specifically set it. */ JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X = 14, /** Determines the center for the center-first group order. Use -1 to * automatically use the middle of the image, 0..ysize to specifically set it. */ JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y = 15, /** Enables or disables progressive encoding for modular mode. Use -1 for the * encoder default, 0 to disable, 1 to enable. */ JXL_ENC_FRAME_SETTING_RESPONSIVE = 16, /** Set the progressive mode for the AC coefficients of VarDCT, using spectral * progression from the DCT coefficients. Use -1 for the encoder default, 0 to * disable, 1 to enable. */ JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC = 17, /** Set the progressive mode for the AC coefficients of VarDCT, using * quantization of the least significant bits. Use -1 for the encoder default, * 0 to disable, 1 to enable. */ JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC = 18, /** Set the progressive mode using lower-resolution DC images for VarDCT. Use * -1 for the encoder default, 0 to disable, 1 to have an extra 64x64 lower * resolution pass, 2 to have a 512x512 and 64x64 lower resolution pass. */ JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC = 19, /** Use Global channel palette if the amount of colors is smaller than this * percentage of range. Use 0-100 to set an explicit percentage, -1 to use the * encoder default. Used for modular encoding. */ JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT = 20, /** Use Local (per-group) channel palette if the amount of colors is smaller * than this percentage of range. Use 0-100 to set an explicit percentage, -1 * to use the encoder default. Used for modular encoding. */ JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT = 21, /** Use color palette if amount of colors is smaller than or equal to this * amount, or -1 to use the encoder default. Used for modular encoding. */ JXL_ENC_FRAME_SETTING_PALETTE_COLORS = 22, /** Enables or disables delta palette. Use -1 for the default (encoder * chooses), 0 to disable, 1 to enable. Used in modular mode. */ JXL_ENC_FRAME_SETTING_LOSSY_PALETTE = 23, /** Color transform for internal encoding: -1 = default, 0=XYB, 1=none (RGB), * 2=YCbCr. The XYB setting performs the forward XYB transform. None and * YCbCr both perform no transform, but YCbCr is used to indicate that the * encoded data losslessly represents YCbCr values. */ JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM = 24, /** Reversible color transform for modular encoding: -1=default, 0-41=RCT * index, e.g. index 0 = none, index 6 = YCoCg. * If this option is set to a non-default value, the RCT will be globally * applied to the whole frame. * The default behavior is to try several RCTs locally per modular group, * depending on the speed and distance setting. */ JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE = 25, /** Group size for modular encoding: -1=default, 0=128, 1=256, 2=512, 3=1024. */ JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE = 26, /** Predictor for modular encoding. -1 = default, 0=zero, 1=left, 2=top, * 3=avg0, 4=select, 5=gradient, 6=weighted, 7=topright, 8=topleft, * 9=leftleft, 10=avg1, 11=avg2, 12=avg3, 13=toptop predictive average 14=mix * 5 and 6, 15=mix everything. */ JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR = 27, /** Fraction of pixels used to learn MA trees as a percentage. -1 = default, * 0 = no MA and fast decode, 50 = default value, 100 = all, values above * 100 are also permitted. Higher values use more encoder memory. */ JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT = 28, /** Number of extra (previous-channel) MA tree properties to use. -1 = * default, 0-11 = valid values. Recommended values are in the range 0 to 3, * or 0 to amount of channels minus 1 (including all extra channels, and * excluding color channels when using VarDCT mode). Higher value gives slower * encoding and slower decoding. */ JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS = 29, /** Enable or disable CFL (chroma-from-luma) for lossless JPEG recompression. * -1 = default, 0 = disable CFL, 1 = enable CFL. */ JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL = 30, /** Prepare the frame for indexing in the frame index box. * 0 = ignore this frame (same as not setting a value), * 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. * If non-keyframes, i.e., frames with cropping, blending or patches are * attempted to be indexed, ::JXL_ENC_ERROR will occur. */ JXL_ENC_FRAME_INDEX_BOX = 31, /** Sets brotli encode effort for use in JPEG recompression and * compressed metadata boxes (brob). Can be -1 (default) or 0 (fastest) to 11 * (slowest). Default is based on the general encode effort in case of JPEG * recompression, and 4 for brob boxes. */ JXL_ENC_FRAME_SETTING_BROTLI_EFFORT = 32, /** 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. */ JXL_ENC_FRAME_SETTING_FILL_ENUM = 65535, } JxlEncoderFrameSettingId; /** * 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 * allocator will be used. See jpegxl/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 @ref JxlEncoder otherwise */ JXL_EXPORT JxlEncoder* JxlEncoderCreate(const JxlMemoryManager* memory_manager); /** * 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 @ref JxlEncoderCreate, but the memory manager is kept. * * @param enc instance to be re-initialized. */ JXL_EXPORT void JxlEncoderReset(JxlEncoder* enc); /** * Deinitializes and frees a @ref JxlEncoder instance. * * @param enc instance to be cleaned up and deallocated. */ JXL_EXPORT void JxlEncoderDestroy(JxlEncoder* enc); /** * Sets the color management system (CMS) that will be used for color conversion * (if applicable) during encoding. May only be set before starting encoding. If * left unset, the default CMS implementation will be used. * * @param enc encoder object. * @param cms structure representing a CMS implementation. See @ref * JxlCmsInterface for more details. */ JXL_EXPORT void JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms); /** * Set the parallel runner for multithreading. May only be set before starting * encoding. * * @param enc encoder object. * @param parallel_runner function pointer to runner for multithreading. It may * 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 * otherwise (the previous runner remains set). */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetParallelRunner(JxlEncoder* enc, JxlParallelRunner parallel_runner, void* parallel_runner_opaque); /** * Get the (last) error code in case ::JXL_ENC_ERROR was returned. * * @param enc encoder object. * @return the @ref JxlEncoderError that caused the (last) ::JXL_ENC_ERROR to * be returned. */ JXL_EXPORT JxlEncoderError JxlEncoderGetError(JxlEncoder* enc); /** * 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 * amount, so *next_out will now point at the amount of *avail_out unprocessed * 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 @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 * and/or @ref JxlEncoderCloseBoxes must be called before the next * @ref JxlEncoderProcessOutput call, or the codestream won't be encoded * correctly. * * @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. */ JXL_EXPORT JxlEncoderStatus JxlEncoderProcessOutput(JxlEncoder* enc, uint8_t** next_out, size_t* avail_out); /** * Sets the frame information for this frame to the encoder. This includes * animation information such as frame duration to store in the frame header. * The frame header fields represent the frame as passed to the encoder, but not * necessarily the exact values as they will be encoded file format: the encoder * could change crop and blending options of a frame for more efficient encoding * or introduce additional internal frames. Animation duration and time code * information is not altered since those are immutable metadata of the frame. * * It is not required to use this function, however if have_animation is set * to true in the basic info, then this function should be used to set the * time duration of this individual frame. By default individual frames have a * time duration of 0, making them form a composite still. See @ref * JxlFrameHeader for more information. * * 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 @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 * JxlEncoderSetFrameName. * * @param frame_settings set of options and metadata for this frame. Also * 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 */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameHeader(JxlEncoderFrameSettings* frame_settings, const JxlFrameHeader* frame_header); /** * Sets blend info of an extra channel. The blend info of extra channels is set * separately from that of the color channels, the color channels are set with * @ref JxlEncoderSetFrameHeader. * * @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 use. * @param blend_info blend info to set for the extra channel * @return ::JXL_ENC_SUCCESS on success, ::JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBlendInfo( JxlEncoderFrameSettings* frame_settings, size_t index, const JxlBlendInfo* blend_info); /** * 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 @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). * * Calling @ref JxlEncoderSetFrameHeader clears any name that was * previously set. * * @param frame_settings set of options and metadata for this frame. Also * 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 */ 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 @ref JxlEncoderSetBasicInfo has not yet been called, calling * @ref 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 * possible to losslessly reconstruct the JPEG codestream. * * If this is the last frame, @ref JxlEncoderCloseInput or @ref * 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 */ JXL_EXPORT JxlEncoderStatus JxlEncoderAddJPEGFrame(const JxlEncoderFrameSettings* frame_settings, const uint8_t* buffer, size_t size); /** * Sets the buffer to read pixels from for the next image to encode. Must call * @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 * * Note: the sample data type in pixel_format is allowed to be different from * 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 @ref JxlPixelFormat * "JxlPixelFormat": * - single-channel data, e.g. grayscale * - single-channel + alpha * - trichromatic, e.g. RGB * - trichromatic + alpha * * Extra channels not handled here need to be set by @ref * JxlEncoderSetExtraChannelBuffer. * If the image has alpha, and alpha is not passed here, it will implicitly be * set to all-opaque (an alpha value of 1.0 everywhere). * * The pixels are assumed to be encoded in the original profile that is set with * @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 * uses_original_profile=false case. They are however not allowed to be NaN or * +-infinity. * * If this is the last frame, @ref JxlEncoderCloseInput or @ref * JxlEncoderCloseFrames must be called before the next * @ref JxlEncoderProcessOutput call. * * @param frame_settings set of options and metadata for this frame. Also * includes reference to the encoder object. * @param pixel_format format for pixels. Object owned by the caller and its * contents are copied internally. * @param buffer buffer type to input the pixel data from. Owned by the caller * and its contents are copied internally. * @param size size of buffer in bytes. This size should match what is implied * by the frame dimensions and the pixel format. * @return ::JXL_ENC_SUCCESS on success, ::JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderAddImageFrame( const JxlEncoderFrameSettings* frame_settings, const JxlPixelFormat* pixel_format, const void* buffer, size_t size); /** * 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 * @ref JxlBasicInfo. Must call @ref JxlEncoderSetExtraChannelInfo before @ref * JxlEncoderSetExtraChannelBuffer. * * TODO(firsching): mention what data types in pixel formats are supported. * * It is required to call this function for every extra channel, except for the * alpha channel if that was already set through @ref JxlEncoderAddImageFrame. * * @param frame_settings set of options and metadata for this frame. Also * includes reference to the encoder object. * @param pixel_format format for pixels. Object owned by the caller and its * contents are copied internally. The num_channels value is ignored, since the * number of channels for an extra channel is always assumed to be one. * @param buffer buffer type to input the pixel data from. Owned by the caller * and its contents are copied internally. * @param size size of buffer in bytes. This size should match what is implied * by the frame dimensions and the pixel format. * @param index index of the extra channel to use. * @return ::JXL_ENC_SUCCESS on success, ::JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer( const JxlEncoderFrameSettings* frame_settings, const JxlPixelFormat* pixel_format, const void* buffer, size_t size, uint32_t index); /** 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, * JUMBF and user defined boxes). * * The box format follows ISO BMFF and shares features and box types with other * image and video formats, including the Exif, XML and JUMBF boxes. The box * format for JPEG XL is specified in ISO/IEC 18181-2. * * Boxes in general don't contain other boxes inside, except a JUMBF superbox. * Boxes follow each other sequentially and are byte-aligned. If the container * format is used, the JXL stream consists of concatenated boxes. * It is also possible to use a direct codestream without boxes, but in that * case metadata cannot be added. * * Each box generally has the following byte structure in the file: * - 4 bytes: box size including box header (Big endian. If set to 0, an * 8-byte 64-bit size follows instead). * - 4 bytes: type, e.g. "JXL " for the signature box, "jxlc" for a codestream * box. * - N bytes: box contents. * * Only the box contents are provided to the contents argument of this function, * the encoder encodes the size header itself. 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 @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: * - "Exif": a box with EXIF metadata, can be added by libjxl users, or is * automatically added when needed for JPEG reconstruction. The contents of * this box must be prepended by a 4-byte tiff header offset, which may * be 4 zero bytes in case the tiff header follows immediately. * The EXIF metadata must be in sync with what is encoded in the JPEG XL * codestream, specifically the image orientation. While this is not * recommended in practice, in case of conflicting metadata, the JPEG XL * codestream takes precedence. * - "xml ": a box with XML data, in particular XMP metadata, can be added by * libjxl users, or is automatically added when needed for JPEG reconstruction * - "jumb": a JUMBF superbox, which can contain boxes with different types of * metadata inside. This box type can be added by the encoder transparently, * and other libraries to create and handle JUMBF content exist. * - Application-specific boxes. Their typename should not begin with "jxl" or * "JXL" or conflict with other existing typenames, and they should be * registered with MP4RA (mp4ra.org). * * These boxes can be stored uncompressed or Brotli-compressed (using a "brob" * box), depending on the compress_box parameter. * * @param enc encoder object. * @param type the box type, e.g. "Exif" for EXIF metadata, "xml " for XMP or * IPTC metadata, "jumb" for JUMBF metadata. * @param contents the full contents of the box, for example EXIF * data. ISO BMFF box header must not be included, only the contents. Owned by * the caller and its contents are copied internally. * @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 @ref JxlEncoderUseContainer, or adding a box * type that would result in an invalid file format. */ JXL_EXPORT JxlEncoderStatus JxlEncoderAddBox(JxlEncoder* enc, const JxlBoxType type, const uint8_t* contents, size_t size, JXL_BOOL compress_box); /** * Indicates the intention to add metadata boxes. This allows @ref * JxlEncoderAddBox to be used. When using this function, then it is required * to use @ref JxlEncoderCloseBoxes at the end. * * By default the encoder assumes no metadata boxes will be added. * * This setting can only be set at the beginning, before encoding starts. * * @param enc encoder object. */ JXL_EXPORT JxlEncoderStatus JxlEncoderUseBoxes(JxlEncoder* enc); /** * Declares that no further boxes will be added with @ref JxlEncoderAddBox. * This function must be called after the last box is added so the encoder knows * 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 @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. * * @param enc encoder object. */ JXL_EXPORT void JxlEncoderCloseBoxes(JxlEncoder* enc); /** * Declares that no frames will be added and @ref JxlEncoderAddImageFrame and * @ref JxlEncoderAddJPEGFrame won't be called anymore. Further metadata boxes * may still be added. This function or @ref JxlEncoderCloseInput must be called * after adding the last frame and the next call to * @ref JxlEncoderProcessOutput, or the frame won't be properly marked as last. * * 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. * * @param enc encoder object. */ JXL_EXPORT void JxlEncoderCloseFrames(JxlEncoder* enc); /** * 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 * other two must be called after the final frame and/or box, and the next * @ref JxlEncoderProcessOutput call, or the codestream won't be encoded * correctly. * * @param enc encoder object. */ JXL_EXPORT void JxlEncoderCloseInput(JxlEncoder* enc); /** * Sets the original color encoding of the image encoded by this encoder. This * 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, @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 @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, @ref * JXL_ENC_ERROR otherwise */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetICCProfile(JxlEncoder* enc, const uint8_t* icc_profile, size_t size); /** * 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 * other extra channels. * * @param info global image metadata. Object owned by the caller. */ JXL_EXPORT void JxlEncoderInitBasicInfo(JxlBasicInfo* info); /** * 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 * 'replace' blend mode. After using this function, For animation duration must * be set, for composite still blend settings must be set. * * @param frame_header frame metadata. Object owned by the caller. */ JXL_EXPORT void JxlEncoderInitFrameHeader(JxlFrameHeader* frame_header); /** * 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. * * @param blend_info blending info. Object owned by the caller. */ JXL_EXPORT void JxlEncoderInitBlendInfo(JxlBlendInfo* blend_info); /** * Sets the global metadata of the image encoded by this encoder. * * 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 otherwise */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc, const JxlBasicInfo* info); /** * 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. * * @param type type of the extra channel. * @param info global extra channel metadata. Object owned by the caller and its * contents are copied internally. */ JXL_EXPORT void JxlEncoderInitExtraChannelInfo(JxlExtraChannelType type, JxlExtraChannelInfo* info); /** * Sets information for the extra channel at the given index. The index * 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 */ 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 @ref * JxlBasicInfo. * * TODO(lode): remove size parameter for consistency with * @ref JxlEncoderSetFrameName * * @param enc encoder object * @param index index of the extra channel to set. * @param name buffer with the name of the extra channel. * @param size size of the name buffer in bytes, not counting the terminating * character. * @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelName(JxlEncoder* enc, size_t index, const char* name, size_t size); /** * Sets a frame-specific option of integer type to the encoder options. * 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, @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, int64_t value); /** * Sets a frame-specific option of float type to the encoder options. * 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, @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, float value); /** Forces the encoder to use the box-based container format (BMFF) even * when not necessary. * * 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 * @ref JxlEncoderUseContainer for those use cases. * * By default this setting is disabled. * * This setting can only be set at the beginning, before encoding starts. * * @param enc encoder object. * @param use_container true if the encoder should always output the JPEG XL * container format, false to only output it when necessary. * @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR * otherwise. */ JXL_EXPORT JxlEncoderStatus JxlEncoderUseContainer(JxlEncoder* enc, JXL_BOOL use_container); /** * Configure the encoder to store JPEG reconstruction metadata in the JPEG XL * container. * * If this is set to true and a single JPEG frame is added, it will be * possible to losslessly reconstruct the JPEG codestream. * * This setting can only be set at the beginning, before encoding starts. * * @param enc encoder object. * @param store_jpeg_metadata true if the encoder should store JPEG metadata. * @return ::JXL_ENC_SUCCESS if the operation was successful, @ref * JXL_ENC_ERROR otherwise. */ JXL_EXPORT JxlEncoderStatus JxlEncoderStoreJPEGMetadata(JxlEncoder* enc, JXL_BOOL store_jpeg_metadata); /** Sets the feature level of the JPEG XL codestream. Valid values are 5 and * 10, or -1 (to choose automatically). Using the minimum required level, or * level 5 in most cases, is recommended for compatibility with all decoders. * * Level 5: for end-user image delivery, this level is the most widely * supported level by image decoders and the recommended level to use unless a * level 10 feature is absolutely necessary. Supports a maximum resolution * 268435456 pixels total with a maximum width or height of 262144 pixels, * maximum 16-bit color channel depth, maximum 120 frames per second for * animation, maximum ICC color profile size of 4 MiB, it allows all color * models and extra channel types except CMYK and the JXL_CHANNEL_BLACK * extra channel, and a maximum of 4 extra channels in addition to the 3 color * channels. It also sets boundaries to certain internally used coding tools. * * Level 10: this level removes or increases the bounds of most of the level * 5 limitations, allows CMYK color and up to 32 bits per color channel, but * may be less widely supported. * * The default value is -1. This means the encoder will automatically choose * between level 5 and level 10 based on what information is inside the @ref * JxlBasicInfo structure. Do note that some level 10 features, particularly * those used by animated JPEG XL codestreams, might require level 10, even * though the @ref JxlBasicInfo only suggests level 5. In this case, the level * must be explicitly set to 10, otherwise the encoder will return an error. * The encoder will restrict internal encoding choices to those compatible with * the level setting. * * This setting can only be set at the beginning, before encoding starts. * * @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, @ref * JXL_ENC_ERROR otherwise. */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetCodestreamLevel(JxlEncoder* enc, int level); /** Returns the codestream level required to support the currently configured * settings and basic info. This function can only be used at the beginning, * before encoding starts, but after setting basic info. * * This does not support per-frame settings, only global configuration, such as * the image dimensions, that are known at the time of writing the header of * 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, @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 * larger than even level 10 supports), 5 if level 5 is supported, 10 if setting * the codestream level to 10 is required. * */ JXL_EXPORT int JxlEncoderGetRequiredCodestreamLevel(const JxlEncoder* enc); /** * Enables lossless encoding. * * This is not an option like the others on itself, but rather while enabled it * overrides a set of existing options (such as distance, modular mode and * color transform) that enables bit-for-bit lossless encoding. * * When disabled, those options are not overridden, but since those options * could still have been manually set to a combination that operates losslessly, * using this function with lossless set to ::JXL_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, @ref * JXL_ENC_ERROR otherwise. */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameLossless( JxlEncoderFrameSettings* frame_settings, JXL_BOOL lossless); /** * Sets the distance level for lossy compression: target max butteraugli * 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. * * @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, @ref * JXL_ENC_ERROR otherwise. */ JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameDistance( JxlEncoderFrameSettings* frame_settings, float distance); /** * 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 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 @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 * defaults initialized to defaults. * @return the opaque struct pointer identifying a new set of encoder options. */ JXL_EXPORT JxlEncoderFrameSettings* JxlEncoderFrameSettingsCreate( JxlEncoder* enc, const JxlEncoderFrameSettings* source); /** * Sets a color encoding to be sRGB. * * @param color_encoding color encoding instance. * @param is_gray whether the color encoding should be gray scale or color. */ JXL_EXPORT void JxlColorEncodingSetToSRGB(JxlColorEncoding* color_encoding, JXL_BOOL is_gray); /** * Sets a color encoding to be linear sRGB. * * @param color_encoding color encoding instance. * @param is_gray whether the color encoding should be gray scale or color. */ JXL_EXPORT void JxlColorEncodingSetToLinearSRGB( JxlColorEncoding* color_encoding, JXL_BOOL is_gray); /** * 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 #endif /* JXL_ENCODE_H_ */ /** @}*/ libjxl-0.11.1/lib/include/jxl/encode_cxx.h000066400000000000000000000035261472134335300203470ustar00rootroot00000000000000// 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_cpp ///@{ /// /// @file encode_cxx.h /// @brief C++ header-only helper for @ref encode.h. /// /// There's no binary library associated with the header since this is a header /// only library. #ifndef JXL_ENCODE_CXX_H_ #define JXL_ENCODE_CXX_H_ #include #include #include #ifndef __cplusplus #error "This a C++ only header. Use jxl/encode.h from C sources." #endif /// Struct to call JxlEncoderDestroy from the JxlEncoderPtr unique_ptr. struct JxlEncoderDestroyStruct { /// Calls @ref JxlEncoderDestroy() on the passed encoder. void operator()(JxlEncoder* encoder) { JxlEncoderDestroy(encoder); } }; /// std::unique_ptr<> type that calls JxlEncoderDestroy() when releasing the /// encoder. /// /// Use this helper type from C++ sources to ensure the encoder is destroyed and /// their internal resources released. typedef std::unique_ptr JxlEncoderPtr; /// Creates an instance of JxlEncoder into a JxlEncoderPtr and initializes it. /// /// This function returns a unique_ptr that will call JxlEncoderDestroy() when /// releasing the pointer. See @ref JxlEncoderCreate for details on the /// instance creation. /// /// @param memory_manager custom allocator function. It may be NULL. The memory /// manager will be copied internally. /// @return a @c NULL JxlEncoderPtr if the instance can not be allocated or /// initialized /// @return initialized JxlEncoderPtr instance otherwise. static inline JxlEncoderPtr JxlEncoderMake( const JxlMemoryManager* memory_manager) { return JxlEncoderPtr(JxlEncoderCreate(memory_manager)); } #endif // JXL_ENCODE_CXX_H_ /// @} libjxl-0.11.1/lib/include/jxl/gain_map.h000066400000000000000000000122141472134335300177750ustar00rootroot00000000000000/* 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_ */ /** @} */ libjxl-0.11.1/lib/include/jxl/memory_manager.h000066400000000000000000000040751472134335300212320ustar00rootroot00000000000000/* 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_common * @{ * @file memory_manager.h * @brief Abstraction functions used by JPEG XL to allocate memory. */ #ifndef JXL_MEMORY_MANAGER_H_ #define JXL_MEMORY_MANAGER_H_ #include #ifdef __cplusplus extern "C" { #endif /** * Allocating function for a memory region of a given size. * * Allocates a contiguous memory region of size @p size bytes. The returned * memory may not be aligned to a specific size or initialized at all. * * @param opaque custom memory manager handle provided by the caller. * @param size in bytes of the requested memory region. * @return @c NULL if the memory can not be allocated, * @return pointer to the memory otherwise. */ typedef void* (*jpegxl_alloc_func)(void* opaque, size_t size); /** * Deallocating function pointer type. * * This function @b MUST do nothing if @p address is @c NULL. * * @param opaque custom memory manager handle provided by the caller. * @param address memory region pointer returned by ::jpegxl_alloc_func, or @c * NULL. */ typedef void (*jpegxl_free_func)(void* opaque, void* address); /** * Memory Manager struct. * These functions, when provided by the caller, will be used to handle memory * allocations. */ typedef struct JxlMemoryManagerStruct { /** The opaque pointer that will be passed as the first parameter to all the * functions in this struct. */ void* opaque; /** 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, otherwise with the * standard malloc/free. */ jpegxl_alloc_func alloc; /** Free function matching the alloc() member. */ jpegxl_free_func free; /* TODO(deymo): Add cache-aligned alloc/free functions here. */ } JxlMemoryManager; #ifdef __cplusplus } #endif #endif /* JXL_MEMORY_MANAGER_H_ */ /** @}*/ libjxl-0.11.1/lib/include/jxl/parallel_runner.h000066400000000000000000000152731472134335300214170ustar00rootroot00000000000000/* 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_threads * @{ */ /** * @file parallel_runner.h */ /** API for running data operations in parallel in a multi-threaded environment. * This module allows the JPEG XL caller to define their own way of creating and * assigning threads. * * The JxlParallelRunner function type defines a parallel data processing * runner that may be implemented by the caller to allow the library to process * in multiple threads. The multi-threaded processing in this library only * requires to run the same function over each number of a range, possibly * running each call in a different thread. The JPEG XL caller is responsible * for implementing this logic using the thread APIs available in their system. * For convenience, a C++ implementation based on std::thread is provided in * jpegxl/parallel_runner_thread.h (part of the jpegxl_threads library). * * Thread pools usually store small numbers of heterogeneous tasks in a queue. * When tasks are identical or differ only by an integer input parameter, it is * much faster to store just one function of an integer parameter and call it * for each value. Conventional vector-of-tasks can be run in parallel using a * lambda function adapter that simply calls task_funcs[task]. * * If no multi-threading is desired, a @c NULL value of JxlParallelRunner * will use an internal implementation without multi-threading. */ #ifndef JXL_PARALLEL_RUNNER_H_ #define JXL_PARALLEL_RUNNER_H_ #include #include #ifdef __cplusplus extern "C" { #endif /** Return code used in the JxlParallel* functions as return value. A value * 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; /** * 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 @ref JxlParallelRunner for details. * * This function MUST be called by the JxlParallelRunner only once, on the * 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 * @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 * @ref JxlParallelRunner(). */ typedef JxlParallelRetCode (*JxlParallelRunInit)(void* jpegxl_opaque, size_t num_threads); /** * 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 * number as the @p value. Calls for different value may be executed from * different threads in parallel. * * @param jpegxl_opaque the @p jpegxl_opaque handle provided to * @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 * @ref JxlParallelRunInit. */ typedef void (*JxlParallelRunFunction)(void* jpegxl_opaque, uint32_t value, size_t thread_id); /** * JxlParallelRunner function type. A parallel runner implementation can be * provided by a JPEG XL caller to allow running computations in multiple * threads. This function must call the initialization function @p init in the * same thread that called it and then call the passed @p func once for every * number in the range [start_range, end_range) (including start_range but not * including end_range) possibly from different multiple threads in parallel. * * 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 @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 * occurred in the runner code. * @return JXL_PARALLEL_RET_RUNNER_ERROR if an error occurred in the runner * code, for example, setting up the threads. * @return the return value of @p init() if non-zero. */ 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 @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. JxlParallelRetCode ExampleSequentialRunner(void* runner_opaque, void* jpegxl_opaque, JxlParallelRunInit init, JxlParallelRunFunction func, uint32_t start_range, uint32_t end_range) { // We only use one thread (the currently running thread). JxlParallelRetCode init_ret = (*init)(jpegxl_opaque, 1); if (init_ret != 0) return init_ret; // In case of other initialization error (for example when initializing the // threads) one can return JXL_PARALLEL_RET_RUNNER_ERROR. for (uint32_t i = start_range; i < end_range; i++) { // Every call is in the thread number 0. These don't need to be in any // order. (*func)(jpegxl_opaque, i, 0); } return JXL_PARALLEL_RET_SUCCESS; } */ #ifdef __cplusplus } #endif #endif /* JXL_PARALLEL_RUNNER_H_ */ /** @}*/ libjxl-0.11.1/lib/include/jxl/resizable_parallel_runner.h000066400000000000000000000052531472134335300234540ustar00rootroot00000000000000/* 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_threads * @{ * @file resizable_parallel_runner.h * @brief implementation using std::thread of a resizeable ::JxlParallelRunner. */ /** Implementation of JxlParallelRunner than can be used to enable * multithreading when using the JPEG XL library. This uses std::thread * internally and related synchronization functions. The number of threads * 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 * @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 * know when all tasks have completed. * * Compared to the implementation in @ref thread_parallel_runner.h, this * implementation is tuned for execution on lower-powered systems, including * for example ARM CPUs with big.LITTLE computation models. */ #ifndef JXL_RESIZABLE_PARALLEL_RUNNER_H_ #define JXL_RESIZABLE_PARALLEL_RUNNER_H_ #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** 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 @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 @ref JxlResizableParallelRunner. */ JXL_THREADS_EXPORT void JxlResizableParallelRunnerSetThreads( void* runner_opaque, size_t num_threads); /** Suggests a number of threads to use for an image of given size. */ JXL_THREADS_EXPORT uint32_t JxlResizableParallelRunnerSuggestThreads(uint64_t xsize, uint64_t ysize); /** Destroys the runner created by @ref JxlResizableParallelRunnerCreate. */ JXL_THREADS_EXPORT void JxlResizableParallelRunnerDestroy(void* runner_opaque); #ifdef __cplusplus } #endif #endif /* JXL_RESIZABLE_PARALLEL_RUNNER_H_ */ /** @}*/ libjxl-0.11.1/lib/include/jxl/resizable_parallel_runner_cxx.h000066400000000000000000000044441472134335300243370ustar00rootroot00000000000000// 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_cpp /// @{ /// /// @file resizable_parallel_runner_cxx.h /// @ingroup libjxl_threads /// @brief C++ header-only helper for @ref resizable_parallel_runner.h. /// /// There's no binary library associated with the header since this is a header /// only library. #ifndef JXL_RESIZABLE_PARALLEL_RUNNER_CXX_H_ #define JXL_RESIZABLE_PARALLEL_RUNNER_CXX_H_ #include #include #include #ifndef __cplusplus #error \ "This a C++ only header. Use jxl/jxl_resizable_parallel_runner.h from C" \ "sources." #endif /// Struct to call JxlResizableParallelRunnerDestroy from the /// JxlResizableParallelRunnerPtr unique_ptr. struct JxlResizableParallelRunnerDestroyStruct { /// Calls @ref JxlResizableParallelRunnerDestroy() on the passed runner. void operator()(void* runner) { JxlResizableParallelRunnerDestroy(runner); } }; /// std::unique_ptr<> type that calls JxlResizableParallelRunnerDestroy() when /// releasing the runner. /// /// Use this helper type from C++ sources to ensure the runner is destroyed and /// their internal resources released. typedef std::unique_ptr JxlResizableParallelRunnerPtr; /// Creates an instance of JxlResizableParallelRunner into a /// JxlResizableParallelRunnerPtr and initializes it. /// /// This function returns a unique_ptr that will call /// JxlResizableParallelRunnerDestroy() when releasing the pointer. See @ref /// JxlResizableParallelRunnerCreate for details on the instance creation. /// /// @param memory_manager custom allocator function. It may be NULL. The memory /// manager will be copied internally. /// @return a @c NULL JxlResizableParallelRunnerPtr if the instance can not be /// allocated or initialized /// @return initialized JxlResizableParallelRunnerPtr instance otherwise. static inline JxlResizableParallelRunnerPtr JxlResizableParallelRunnerMake( const JxlMemoryManager* memory_manager) { return JxlResizableParallelRunnerPtr( JxlResizableParallelRunnerCreate(memory_manager)); } #endif // JXL_RESIZABLE_PARALLEL_RUNNER_CXX_H_ /// @} libjxl-0.11.1/lib/include/jxl/stats.h000066400000000000000000000054741472134335300173720ustar00rootroot00000000000000/* 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_ */ /** @}*/ libjxl-0.11.1/lib/include/jxl/thread_parallel_runner.h000066400000000000000000000046331472134335300227440ustar00rootroot00000000000000/* 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_threads * @{ * @file thread_parallel_runner.h * @brief implementation using std::thread of a ::JxlParallelRunner. */ /** Implementation of JxlParallelRunner than can be used to enable * multithreading when using the JPEG XL library. This uses std::thread * internally and related synchronization functions. The number of threads * created is fixed at construction time and the threads are re-used for every * ThreadParallelRunner::Runner call. Only one concurrent * JxlThreadParallelRunner 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 * know when all tasks have completed. * * This thread pool can efficiently load-balance millions of tasks using an * atomic counter, thus avoiding per-task virtual or system calls. With 48 * hyperthreads and 1M tasks that add to an atomic counter, overall runtime is * 10-20x higher when using std::async, and ~200x for a queue-based thread */ #ifndef JXL_THREAD_PARALLEL_RUNNER_H_ #define JXL_THREAD_PARALLEL_RUNNER_H_ #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** 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 @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 @ref JxlThreadParallelRunnerCreate. */ JXL_THREADS_EXPORT void JxlThreadParallelRunnerDestroy(void* runner_opaque); /** Returns a default num_worker_threads value for * @ref JxlThreadParallelRunnerCreate. */ JXL_THREADS_EXPORT size_t JxlThreadParallelRunnerDefaultNumWorkerThreads(void); #ifdef __cplusplus } #endif #endif /* JXL_THREAD_PARALLEL_RUNNER_H_ */ /** @}*/ libjxl-0.11.1/lib/include/jxl/thread_parallel_runner_cxx.h000066400000000000000000000045051472134335300236240ustar00rootroot00000000000000// 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_cpp /// @{ /// /// @file thread_parallel_runner_cxx.h /// @brief C++ header-only helper for @ref thread_parallel_runner.h. /// /// There's no binary library associated with the header since this is a header /// only library. #ifndef JXL_THREAD_PARALLEL_RUNNER_CXX_H_ #define JXL_THREAD_PARALLEL_RUNNER_CXX_H_ #include #include #include #include #ifndef __cplusplus #error \ "This a C++ only header. Use jxl/jxl_thread_parallel_runner.h from C" \ "sources." #endif /// Struct to call JxlThreadParallelRunnerDestroy from the /// JxlThreadParallelRunnerPtr unique_ptr. struct JxlThreadParallelRunnerDestroyStruct { /// Calls @ref JxlThreadParallelRunnerDestroy() on the passed runner. void operator()(void* runner) { JxlThreadParallelRunnerDestroy(runner); } }; /// std::unique_ptr<> type that calls JxlThreadParallelRunnerDestroy() when /// releasing the runner. /// /// Use this helper type from C++ sources to ensure the runner is destroyed and /// their internal resources released. typedef std::unique_ptr JxlThreadParallelRunnerPtr; /// Creates an instance of JxlThreadParallelRunner into a /// JxlThreadParallelRunnerPtr and initializes it. /// /// This function returns a unique_ptr that will call /// JxlThreadParallelRunnerDestroy() when releasing the pointer. See @ref /// JxlThreadParallelRunnerCreate for details on the instance creation. /// /// @param memory_manager custom allocator function. It may be NULL. The memory /// manager will be copied internally. /// @param num_worker_threads the number of worker threads to create. /// @return a @c NULL JxlThreadParallelRunnerPtr if the instance can not be /// allocated or initialized /// @return initialized JxlThreadParallelRunnerPtr instance otherwise. static inline JxlThreadParallelRunnerPtr JxlThreadParallelRunnerMake( const JxlMemoryManager* memory_manager, size_t num_worker_threads) { return JxlThreadParallelRunnerPtr( JxlThreadParallelRunnerCreate(memory_manager, num_worker_threads)); } #endif // JXL_THREAD_PARALLEL_RUNNER_CXX_H_ /// @} libjxl-0.11.1/lib/include/jxl/types.h000066400000000000000000000117651472134335300174000ustar00rootroot00000000000000/* 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_common * @{ * @file types.h * @brief Data types for the JPEG XL API, for both encoding and decoding. */ #ifndef JXL_TYPES_H_ #define JXL_TYPES_H_ #include #include #ifdef __cplusplus extern "C" { #endif /** * A portable @c bool replacement. * * ::JXL_BOOL is a "documentation" type: actually it is @c int, but in API it * denotes a type, whose only values are ::JXL_TRUE and ::JXL_FALSE. */ #define JXL_BOOL int /** Portable @c true replacement. */ #define JXL_TRUE 1 /** Portable @c false replacement. */ #define JXL_FALSE 0 /** Converts of bool-like value to either ::JXL_TRUE or ::JXL_FALSE. */ #define TO_JXL_BOOL(C) (!!(C) ? JXL_TRUE : JXL_FALSE) /** 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 * for HDR and wide gamut images when color profile conversion is required. */ JXL_TYPE_FLOAT = 0, /** Use type uint8_t. May clip wide color gamut data. */ JXL_TYPE_UINT8 = 2, /** Use type uint16_t. May clip wide color gamut data. */ JXL_TYPE_UINT16 = 3, /** Use 16-bit IEEE 754 half-precision floating point values */ JXL_TYPE_FLOAT16 = 5, } JxlDataType; /** Ordering of multi-byte data. */ typedef enum { /** Use the endianness of the system, either little endian or big endian, * without forcing either specific endianness. Do not use if pixel data * should be exported to a well defined format. */ JXL_NATIVE_ENDIAN = 0, /** Force little endian */ JXL_LITTLE_ENDIAN = 1, /** Force big endian */ JXL_BIG_ENDIAN = 2, } JxlEndianness; /** Data type for the sample values per channel per pixel for the output buffer * 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): support different channel orders if needed (RGB, BGR, ...) */ typedef struct { /** Amount of channels available in a pixel buffer. * 1: single-channel data, e.g. grayscale or a single extra channel * 2: single-channel + alpha * 3: trichromatic, e.g. RGB * 4: trichromatic + alpha * TODO(lode): this needs finetuning. It is not yet defined how the user * chooses output color space. CMYK+alpha needs 5 channels. */ uint32_t num_channels; /** Data type of each channel. */ JxlDataType data_type; /** Whether multi-byte data types are represented in big endian or little * endian format. This applies to ::JXL_TYPE_UINT16 and ::JXL_TYPE_FLOAT. */ JxlEndianness endianness; /** Align scanlines to a multiple of align bytes, or 0 to require no * alignment at all (which has the same effect as value 1) */ 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]; #ifdef __cplusplus } #endif #endif /* JXL_TYPES_H_ */ /** @}*/ libjxl-0.11.1/lib/jpegli.cmake000066400000000000000000000134371472134335300161150ustar00rootroot00000000000000# 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() libjxl-0.11.1/lib/jpegli/000077500000000000000000000000001472134335300151035ustar00rootroot00000000000000libjxl-0.11.1/lib/jpegli/README.md000066400000000000000000000042431472134335300163650ustar00rootroot00000000000000: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). libjxl-0.11.1/lib/jpegli/adaptive_quantization.cc000066400000000000000000000522051472134335300220210ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/adaptive_quantization.h000066400000000000000000000006721472134335300216640ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/bit_writer.cc000066400000000000000000000032671472134335300175740ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/bit_writer.h000066400000000000000000000061031472134335300174260ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/bitstream.cc000066400000000000000000000357471472134335300174240ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/bitstream.h000066400000000000000000000030441472134335300172470ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/color_quantize.cc000066400000000000000000000435721472134335300204630ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/color_quantize.h000066400000000000000000000012701472134335300203120ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/color_transform.cc000066400000000000000000000413771472134335300206370ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/color_transform.h000066400000000000000000000010031472134335300204570ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/common.cc000066400000000000000000000031021472134335300166760ustar00rootroot00000000000000// 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; } } libjxl-0.11.1/lib/jpegli/common.h000066400000000000000000000025461472134335300165530ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/common_internal.h000066400000000000000000000107111472134335300204400ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/dct-inl.h000066400000000000000000000177471472134335300166260ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/decode.cc000066400000000000000000001120621472134335300166370ustar00rootroot00000000000000// 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); } } libjxl-0.11.1/lib/jpegli/decode.h000066400000000000000000000071651472134335300165100ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/decode_api_test.cc000066400000000000000000001365051472134335300205370ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/decode_internal.h000066400000000000000000000107611472134335300204000ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/decode_marker.cc000066400000000000000000000507521472134335300202070ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/decode_marker.h000066400000000000000000000022611472134335300200410ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/decode_scan.cc000066400000000000000000000413151472134335300176450ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/decode_scan.h000066400000000000000000000020641472134335300175050ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/destination_manager.cc000066400000000000000000000124741472134335300214350ustar00rootroot00000000000000// 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; } libjxl-0.11.1/lib/jpegli/downsample.cc000066400000000000000000000300541472134335300175650ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/downsample.h000066400000000000000000000007731472134335300174340ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/encode.cc000066400000000000000000001321311472134335300166500ustar00rootroot00000000000000// 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)); } libjxl-0.11.1/lib/jpegli/encode.h000066400000000000000000000142261472134335300165160ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/encode_api_test.cc000066400000000000000000000713701472134335300205470ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/encode_finish.cc000066400000000000000000000166451472134335300202230ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/encode_finish.h000066400000000000000000000006401472134335300200510ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/encode_internal.h000066400000000000000000000105211472134335300204040ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/encode_streaming.cc000066400000000000000000000207171472134335300207270ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/encode_streaming.h000066400000000000000000000010271472134335300205620ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/entropy_coding-inl.h000066400000000000000000000161721472134335300210660ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/entropy_coding.cc000066400000000000000000000726731472134335300204540ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/entropy_coding.h000066400000000000000000000013561472134335300203040ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/error.cc000066400000000000000000000061661472134335300165540ustar00rootroot00000000000000// 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; } libjxl-0.11.1/lib/jpegli/error.h000066400000000000000000000036211472134335300164070ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/error_handling_test.cc000066400000000000000000001215501472134335300214520ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/fuzztest.h000066400000000000000000000010461472134335300171530ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/huffman.cc000066400000000000000000000272741472134335300170520ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/huffman.h000066400000000000000000000037251472134335300167070ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/idct.cc000066400000000000000000000644351472134335300163510ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/idct.h000066400000000000000000000006561472134335300162060ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/input.cc000066400000000000000000000400541472134335300165540ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/input.h000066400000000000000000000006021472134335300164110ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/input_suspension_test.cc000066400000000000000000000542771472134335300221150ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/jpeg.version.62000066400000000000000000000001661472134335300176700ustar00rootroot00000000000000LIBJPEG_6.2 { global: jpeg*; }; LIBJPEGTURBO_6.2 { global: jpeg_mem_src*; jpeg_mem_dest*; tj*; };libjxl-0.11.1/lib/jpegli/jpeg.version.8000066400000000000000000000001201472134335300175760ustar00rootroot00000000000000LIBJPEG_8.0 { global: jpeg*; }; LIBJPEGTURBO_8.0 { global: tj*; }; libjxl-0.11.1/lib/jpegli/libjpeg_test_util.cc000066400000000000000000000245551472134335300211350ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/libjpeg_test_util.h000066400000000000000000000026211472134335300207650ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/libjpeg_wrapper.cc000066400000000000000000000174241472134335300205760ustar00rootroot00000000000000// 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); } libjxl-0.11.1/lib/jpegli/memory_manager.cc000066400000000000000000000140131472134335300204130ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/memory_manager.h000066400000000000000000000025571472134335300202670ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/output_suspension_test.cc000066400000000000000000000167071472134335300223120ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/quant.cc000066400000000000000000000602561472134335300165530ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/quant.h000066400000000000000000000011341472134335300164030ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/render.cc000066400000000000000000000720231472134335300166750ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/render.h000066400000000000000000000011431472134335300165320ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/simd.cc000066400000000000000000000015251472134335300163510ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/simd.h000066400000000000000000000006011472134335300162050ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/source_manager.cc000066400000000000000000000062311472134335300204060ustar00rootroot00000000000000// 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; } libjxl-0.11.1/lib/jpegli/source_manager_test.cc000066400000000000000000000110241472134335300214410ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/streaming_test.cc000066400000000000000000000211721472134335300204450ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/test_params.h000066400000000000000000000113061472134335300175770ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/test_utils-inl.h000066400000000000000000000362731472134335300202460ustar00rootroot00000000000000// 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)); } } libjxl-0.11.1/lib/jpegli/test_utils.cc000066400000000000000000000735541472134335300176270ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/test_utils.h000066400000000000000000000106511472134335300174560ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/testing.h000066400000000000000000000022011472134335300167240ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/transcode_api_test.cc000066400000000000000000000107241472134335300212700ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/transpose-inl.h000066400000000000000000000071441472134335300200600ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/types.h000066400000000000000000000014421472134335300164210ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jpegli/upsample.cc000066400000000000000000000110471472134335300172430ustar00rootroot00000000000000// 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 libjxl-0.11.1/lib/jpegli/upsample.h000066400000000000000000000014441472134335300171050ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jxl.cmake000066400000000000000000000237711472134335300154420ustar00rootroot00000000000000# 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) 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 hwy Threads::Threads ${ATOMICS_LIBRARIES} ) 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 ) 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 # 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_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 BEFORE PUBLIC "$" "${JXL_HWY_INCLUDE_DIRS}" "$>" ) target_compile_definitions(jxl_dec-obj PUBLIC ${OBJ_COMPILE_DEFINITIONS} ) 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_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 BEFORE PUBLIC ${PROJECT_SOURCE_DIR} ${JXL_HWY_INCLUDE_DIRS} $ ) target_compile_definitions(jxl_enc-obj PUBLIC ${OBJ_COMPILE_DEFINITIONS} ) target_link_libraries(jxl_enc-obj PUBLIC jxl_base) set_target_properties(jxl_dec-obj PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1 DEFINE_SYMBOL JXL_INTERNAL_LIBRARY_BUILD ) set_target_properties(jxl_enc-obj PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1 DEFINE_SYMBOL JXL_INTERNAL_LIBRARY_BUILD ) # Private static library. This exposes all the internal functions and is used # for tests. 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 ) # The list of objects in the static and shared libraries. set(JPEGXL_INTERNAL_OBJECTS $ $ ) # 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-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 "$") 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. if(JPEGXL_ENABLE_TCMALLOC) pkg_check_modules(TCMallocMinimal REQUIRED IMPORTED_TARGET libtcmalloc_minimal) # tcmalloc 2.8 has concurrency issues that makes it sometimes return nullptr # for large allocs. See https://github.com/gperftools/gperftools/issues/1204 # for details. if(TCMallocMinimal_VERSION VERSION_EQUAL 2.8) message(FATAL_ERROR "tcmalloc version 2.8 has a concurrency bug. You have installed " "version ${TCMallocMinimal_VERSION}, please either downgrade tcmalloc " "to version 2.7, upgrade to 2.8.1 or newer or pass " "-DJPEGXL_ENABLE_TCMALLOC=OFF to jpeg-xl cmake line. See the following " "bug for details:\n" " https://github.com/gperftools/gperftools/issues/1204\n") endif() target_link_libraries(jxl-internal PUBLIC PkgConfig::TCMallocMinimal) endif() # JPEGXL_ENABLE_TCMALLOC # 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}) set_target_properties(jxl PROPERTIES VERSION ${JPEGXL_LIBRARY_VERSION} SOVERSION ${JPEGXL_LIBRARY_SOVERSION}) # 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}) set_target_properties(jxl_dec PROPERTIES VERSION ${JPEGXL_LIBRARY_VERSION} SOVERSION ${JPEGXL_LIBRARY_SOVERSION}) # Check whether the linker support excluding libs set(LINKER_EXCLUDE_LIBS_FLAG "-Wl,--exclude-libs=ALL") include(CheckCSourceCompiles) 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. foreach(target IN ITEMS jxl jxl_dec) set_target_properties(${target} PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/jxl/jxl.version) if(APPLE) set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/jxl/jxl_osx.syms") elseif(WIN32) # Nothing needed here, we use __declspec(dllexport) (jxl_export.h) else() set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS " -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/jxl/jxl.version") endif() # APPLE # This hides the default visibility symbols from static libraries bundled into # the shared library. In particular this prevents exposing symbols from hwy # and skcms in the shared library. if(LINKER_SUPPORT_EXCLUDE_LIBS) set_property(TARGET ${target} APPEND_STRING PROPERTY LINK_FLAGS " ${LINKER_EXCLUDE_LIBS_FLAG}") endif() endforeach() # 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}) # Add a pkg-config file for libjxl. set(JPEGXL_LIBRARY_REQUIRES "libhwy libbrotlienc libbrotlidec libjxl_cms") if (BUILD_SHARED_LIBS) set(JPEGXL_REQUIRES_TYPE "Requires.private") set(JPEGXL_PRIVATE_LIBS "-lm ${PKGCONFIG_CXX_LIB}") else() set(JPEGXL_REQUIRES_TYPE "Requires") set(JPEGXL_PUBLIC_LIBS "-lm ${PKGCONFIG_CXX_LIB}") endif() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/jxl/libjxl.pc.in" "libjxl.pc" @ONLY) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libjxl.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") libjxl-0.11.1/lib/jxl/000077500000000000000000000000001472134335300144265ustar00rootroot00000000000000libjxl-0.11.1/lib/jxl/ac_context.h000066400000000000000000000142241472134335300167310ustar00rootroot00000000000000// 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_AC_CONTEXT_H_ #define LIB_JXL_AC_CONTEXT_H_ #include #include #include "lib/jxl/base/bits.h" #include "lib/jxl/base/status.h" #include "lib/jxl/coeff_order_fwd.h" namespace jxl { // Block context used for scanning order, number of non-zeros, AC coefficients. // Equal to the channel. constexpr uint32_t kDCTOrderContextStart = 0; // The number of predicted nonzeros goes from 0 to 1008. We use // ceil(log2(predicted+1)) as a context for the number of nonzeros, so from 0 to // 10, inclusive. constexpr uint32_t kNonZeroBuckets = 37; static const uint16_t kCoeffFreqContext[64] = { 0xBAD, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 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, }; static const uint16_t kCoeffNumNonzeroContext[64] = { 0xBAD, 0, 31, 62, 62, 93, 93, 93, 93, 123, 123, 123, 123, 152, 152, 152, 152, 152, 152, 152, 152, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, }; // Supremum of ZeroDensityContext(x, y) + 1, when x + y < 64. constexpr int kZeroDensityContextCount = 458; // Supremum of ZeroDensityContext(x, y) + 1. constexpr int kZeroDensityContextLimit = 474; /* This function is used for entropy-sources pre-clustering. * * Ideally, each combination of |nonzeros_left| and |k| should go to its own * bucket; but it implies (64 * 63 / 2) == 2016 buckets. If there is other * dimension (e.g. block context), then number of primary clusters becomes too * big. * * To solve this problem, |nonzeros_left| and |k| values are clustered. It is * known that their sum is at most 64, consequently, the total number buckets * is at most A(64) * B(64). */ // TODO(user): investigate, why disabling pre-clustering makes entropy code // less dense. Perhaps we would need to add HQ clustering algorithm that would // be able to squeeze better by spending more CPU cycles. 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((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); JXL_DASSERT(k < 64); JXL_DASSERT(nonzeros_left > 0); // Asserting nonzeros_left + k < 65 here causes crashes in debug mode with // invalid input, since the (hot) decoding loop does not check this condition. // As no out-of-bound memory reads are issued even if that condition is // broken, we check this simpler condition which holds anyway. The decoder // will still mark a file in which that condition happens as not valid at the // end of the decoding loop, as `nzeros` will not be `0`. JXL_DASSERT(nonzeros_left < 64); return (kCoeffNumNonzeroContext[nonzeros_left] + kCoeffFreqContext[k]) * 2 + prev; } struct BlockCtxMap { std::vector dc_thresholds[3]; std::vector qf_thresholds; std::vector ctx_map; size_t num_ctxs, num_dc_ctxs; static constexpr uint8_t kDefaultCtxMap[] = { // Default ctx map clusters all the large transforms together. 0, 1, 2, 2, 3, 3, 4, 5, 6, 6, 6, 6, 6, // 7, 8, 9, 9, 10, 11, 12, 13, 14, 14, 14, 14, 14, // 7, 8, 9, 9, 10, 11, 12, 13, 14, 14, 14, 14, 14, // }; static_assert(3 * kNumOrders == sizeof(kDefaultCtxMap) / sizeof *kDefaultCtxMap, "Update default context map"); size_t Context(int dc_idx, uint32_t qf, size_t ord, size_t c) const { size_t qf_idx = 0; for (uint32_t t : qf_thresholds) { if (qf > t) qf_idx++; } size_t idx = c < 2 ? c ^ 1 : 2; idx = idx * kNumOrders + ord; idx = idx * (qf_thresholds.size() + 1) + qf_idx; idx = idx * num_dc_ctxs + dc_idx; return ctx_map[idx]; } // 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 static_cast(num_ctxs * kNonZeroBuckets + kZeroDensityContextCount * block_ctx); } // Context map for AC coefficients consists of 2 blocks: // |num_ctxs x : context for number of non-zeros in the block // kNonZeroBuckets| computed from block context and predicted // value (based top and left values) // |num_ctxs x : context for AC coefficient symbols, // kZeroDensityContextCount| computed from block context, // number of non-zeros left and // index in scan order constexpr uint32_t NumACContexts() const { return static_cast(num_ctxs * (kNonZeroBuckets + kZeroDensityContextCount)); } // 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. inline uint32_t NonZeroContext(uint32_t non_zeros, uint32_t block_ctx) const { uint32_t ctx; if (non_zeros >= 64) non_zeros = 64; if (non_zeros < 8) { ctx = non_zeros; } else { ctx = 4 + non_zeros / 2; } return static_cast(ctx * num_ctxs + block_ctx); } BlockCtxMap() { ctx_map.assign(std::begin(kDefaultCtxMap), std::end(kDefaultCtxMap)); num_ctxs = *std::max_element(ctx_map.begin(), ctx_map.end()) + 1; num_dc_ctxs = 1; } }; } // namespace jxl #endif // LIB_JXL_AC_CONTEXT_H_ libjxl-0.11.1/lib/jxl/ac_strategy.cc000066400000000000000000000063161472134335300172500ustar00rootroot00000000000000// 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/ac_strategy.h" #include #include #include #include #include "lib/jxl/base/bits.h" #include "lib/jxl/base/compiler_specific.h" namespace jxl { // Tries to generalize zig-zag order to non-square blocks. Surprisingly, in // square block frequency along the (i + j == const) diagonals is roughly the // same. For historical reasons, consecutive diagonals are traversed // in alternating directions - so called "zig-zag" (or "snake") order. template static void CoeffOrderAndLut(AcStrategy acs, coeff_order_t* out) { size_t cx = acs.covered_blocks_x(); size_t cy = acs.covered_blocks_y(); CoefficientLayout(&cy, &cx); // CoefficientLayout ensures cx >= cy. // We compute the zigzag order for a cx x cx block, then discard all the // lines that are not multiple of the ratio between cx and cy. size_t xs = cx / cy; size_t xsm = xs - 1; size_t xss = CeilLog2Nonzero(xs); // First half of the block size_t cur = cx * cy; for (size_t i = 0; i < cx * kBlockDim; i++) { for (size_t j = 0; j <= i; j++) { size_t x = j; size_t y = i - j; if (i % 2) std::swap(x, y); if ((y & xsm) != 0) continue; y >>= xss; size_t val = 0; if (x < cx && y < cy) { val = y * cx + x; } else { val = cur++; } if (is_lut) { out[y * cx * kBlockDim + x] = val; } else { out[val] = y * cx * kBlockDim + x; } } } // Second half for (size_t ip = cx * kBlockDim - 1; ip > 0; ip--) { size_t i = ip - 1; for (size_t j = 0; j <= i; j++) { size_t x = cx * kBlockDim - 1 - (i - j); size_t y = cx * kBlockDim - 1 - j; if (i % 2) std::swap(x, y); if ((y & xsm) != 0) continue; y >>= xss; size_t val = cur++; if (is_lut) { out[y * cx * kBlockDim + x] = val; } else { out[val] = y * cx * kBlockDim + x; } } } } void AcStrategy::ComputeNaturalCoeffOrder(coeff_order_t* order) const { CoeffOrderAndLut(*this, order); } void AcStrategy::ComputeNaturalCoeffOrderLut(coeff_order_t* lut) const { CoeffOrderAndLut(*this, lut); } #if JXL_CXX_LANG < JXL_CXX_17 constexpr size_t AcStrategy::kMaxCoeffBlocks; constexpr size_t AcStrategy::kMaxBlockDim; constexpr size_t AcStrategy::kMaxCoeffArea; #endif 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(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); for (size_t x = 0; x < layers_.xsize(); x++) { if (row[x] == ((static_cast(type) << 1) | 1)) ret++; } } return ret; } } // namespace jxl libjxl-0.11.1/lib/jxl/ac_strategy.h000066400000000000000000000224111472134335300171040ustar00rootroot00000000000000// 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_AC_STRATEGY_H_ #define LIB_JXL_AC_STRATEGY_H_ #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/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 // them. // `AcStrategy` represents what transform should be used, and which sub-block of // that transform we are currently in. Note that DCT4x4 is applied on all four // 4x4 sub-blocks of an 8x8 block. // `AcStrategyImage` defines which strategy should be used for each 8x8 block // of the image. The highest 4 bits represent the strategy to be used, the // lowest 4 represent the index of the block inside that strategy. 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. static constexpr size_t kMaxCoeffBlocks = 32; static constexpr size_t kMaxBlockDim = kBlockDim * kMaxCoeffBlocks; // Maximum number of coefficients in a block. Guaranteed to be a multiple of // the vector size. static constexpr size_t kMaxCoeffArea = kMaxBlockDim * kMaxBlockDim; static_assert((kMaxCoeffArea * sizeof(float)) % hwy::kMaxVectorSize == 0, "Coefficient area is not a multiple of vector size"); static constexpr uint8_t kNumValidStrategies = static_cast(AcStrategyType::DCT128X256) + 1; static constexpr uint32_t TypeBit(const AcStrategyType type) { return 1u << static_cast(type); } // Returns true if this block is the first 8x8 block (i.e. top-left) of a // possibly multi-block strategy. JXL_INLINE bool IsFirstBlock() const { return is_first_; } JXL_INLINE bool IsMultiblock() const { constexpr uint32_t bits = 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; } // Returns the raw strategy value. Should only be used for tokenization. JXL_INLINE uint8_t RawStrategy() const { return static_cast(strategy_); } JXL_INLINE AcStrategyType Strategy() const { return strategy_; } // Inverse check static JXL_INLINE constexpr bool IsRawStrategyValid(int raw_strategy) { return raw_strategy < kNumValidStrategies && raw_strategy >= 0; } static JXL_INLINE AcStrategy FromRawStrategy(uint8_t raw_strategy) { return FromRawStrategy(static_cast(raw_strategy)); } static JXL_INLINE AcStrategy FromRawStrategy(AcStrategyType raw_strategy) { JXL_DASSERT(IsRawStrategyValid(static_cast(raw_strategy))); return AcStrategy(raw_strategy, /*is_first=*/true); } // "Natural order" means the order of increasing of "anisotropic" frequency of // continuous version of DCT basis. // Round-trip, for any given strategy s: // X = NaturalCoeffOrder(s)[NaturalCoeffOrderLutN(s)[X]] // X = NaturalCoeffOrderLut(s)[NaturalCoeffOrderN(s)[X]] void ComputeNaturalCoeffOrder(coeff_order_t* order) const; void ComputeNaturalCoeffOrderLut(coeff_order_t* lut) const; // Number of 8x8 blocks that this strategy will cover. 0 for non-top-left // blocks inside a multi-block transform. JXL_INLINE size_t covered_blocks_x() const { static constexpr uint8_t kLut[] = {1, 1, 1, 1, 2, 4, 1, 2, 1, 4, 2, 4, 1, 1, 1, 1, 1, 1, 8, 4, 8, 16, 8, 16, 32, 16, 32}; static_assert(sizeof(kLut) / sizeof(*kLut) == kNumValidStrategies, "Update LUT"); return kLut[static_cast(strategy_)]; } JXL_INLINE size_t covered_blocks_y() const { static constexpr uint8_t kLut[] = {1, 1, 1, 1, 2, 4, 2, 1, 4, 1, 4, 2, 1, 1, 1, 1, 1, 1, 8, 8, 4, 16, 16, 8, 32, 32, 16}; static_assert(sizeof(kLut) / sizeof(*kLut) == kNumValidStrategies, "Update LUT"); return kLut[static_cast(strategy_)]; } JXL_INLINE size_t log2_covered_blocks() const { static constexpr uint8_t kLut[] = {0, 0, 0, 0, 2, 4, 1, 1, 2, 2, 3, 3, 0, 0, 0, 0, 0, 0, 6, 5, 5, 8, 7, 7, 10, 9, 9}; static_assert(sizeof(kLut) / sizeof(*kLut) == kNumValidStrategies, "Update LUT"); return kLut[static_cast(strategy_)]; } private: friend class AcStrategyRow; JXL_INLINE AcStrategy(AcStrategyType strategy, bool is_first) : strategy_(strategy), is_first_(is_first) { JXL_DASSERT(IsMultiblock() || is_first == true); } AcStrategyType strategy_; bool is_first_; }; // Class to use a certain row of the AC strategy. class AcStrategyRow { public: explicit AcStrategyRow(const uint8_t* row) : row_(row) {} AcStrategy operator[](size_t x) const { AcStrategyType strategy = static_cast(row_[x] >> 1); bool is_first = static_cast(row_[x] & 1); return AcStrategy(strategy, is_first); } private: const uint8_t* JXL_RESTRICT row_; }; class AcStrategyImage { public: AcStrategyImage() = default; 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(AcStrategyType::DCT) << 1) | 1, &layers_, rect); } void FillDCT8() { FillDCT8(Rect(layers_)); } void FillInvalid() { FillImage(INVALID, &layers_); } Status Set(size_t x, size_t y, AcStrategyType type) { #if (JXL_IS_DEBUG_BUILD) AcStrategy acs = AcStrategy::FromRawStrategy(type); 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, AcStrategyType type, bool check = true) { AcStrategy acs = AcStrategy::FromRawStrategy(type); for (size_t iy = 0; iy < acs.covered_blocks_y(); iy++) { for (size_t ix = 0; ix < acs.covered_blocks_x(); ix++) { size_t pos = (y + iy) * stride_ + x + ix; if (check && row_[pos] != INVALID) { return JXL_FAILURE("Invalid AC strategy: block overlap"); } row_[pos] = (static_cast(type) << 1) | ((iy | ix) == 0 ? 1 : 0); } } return true; } bool IsValid(size_t x, size_t y) { return row_[y * stride_ + x] != INVALID; } AcStrategyRow ConstRow(size_t y, size_t x_prefix = 0) const { return AcStrategyRow(layers_.ConstRow(y) + x_prefix); } AcStrategyRow ConstRow(const Rect& rect, size_t y) const { return ConstRow(rect.y0() + y, rect.x0()); } size_t PixelsPerRow() const { return layers_.PixelsPerRow(); } size_t xsize() const { return layers_.xsize(); } size_t ysize() const { return layers_.ysize(); } // Count the number of blocks of a given type. size_t CountBlocks(AcStrategyType type) const; JxlMemoryManager* memory_manager() const { return layers_.memory_manager(); } private: ImageB layers_; uint8_t* JXL_RESTRICT row_; size_t stride_; // A value that does not represent a valid combined AC strategy // value. Used as a sentinel. static constexpr uint8_t INVALID = 0xFF; }; } // namespace jxl #endif // LIB_JXL_AC_STRATEGY_H_ libjxl-0.11.1/lib/jxl/ac_strategy_test.cc000066400000000000000000000243431472134335300203070ustar00rootroot00000000000000// 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/ac_strategy.h" #include #include #include #include // HWY_ALIGN_MAX #include #include "lib/jxl/base/random.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 { // Test that DCT -> IDCT is a noop. class AcStrategyRoundtrip : public ::hwy::TestWithParamTargetAndT { protected: void Run() { 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 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; 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); std::fill_n(input, AcStrategy::kMaxCoeffArea, 0); input[i] = 0.2f; TransformFromPixels(type, input, acs.covered_blocks_x() * 8, coeffs, scratch_space); ASSERT_NEAR(coeffs[0], 0.2 / (64 << acs.log2_covered_blocks()), 1e-6) << " i = " << i; TransformToPixels(type, coeffs, idct, acs.covered_blocks_x() * 8, 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 " << static_cast(type); } } // Test DC. std::fill_n(idct, AcStrategy::kMaxCoeffArea, 0); for (size_t y = 0; y < acs.covered_blocks_y(); y++) { for (size_t x = 0; x < acs.covered_blocks_x(); x++) { 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, 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 " << static_cast(type); } } } } }; HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T( AcStrategyRoundtrip, ::testing::Range(0, static_cast(AcStrategy::kNumValidStrategies))); TEST_P(AcStrategyRoundtrip, Test) { Run(); } // Test that DC(2x2) -> DCT coefficients -> IDCT -> downsampled IDCT is a noop. class AcStrategyRoundtripDownsample : public ::hwy::TestWithParamTargetAndT { protected: void Run() { 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 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; 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; } 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, scratch_space); TransformToPixels(type, coeffs, idct, acs.covered_blocks_x() * 8, scratch_space); std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0.0f); std::fill_n(dc, AcStrategy::kMaxCoeffArea, 0); dc[y * acs.covered_blocks_x() * 8 + x] = 0.2f; // Downsample for (size_t dy = 0; dy < acs.covered_blocks_y(); dy++) { for (size_t dx = 0; dx < acs.covered_blocks_x(); dx++) { float sum = 0; for (size_t iy = 0; iy < 8; iy++) { for (size_t ix = 0; ix < 8; ix++) { sum += idct[(dy * 8 + iy) * 8 * acs.covered_blocks_x() + dx * 8 + ix]; } } sum /= 64.0f; ASSERT_NEAR(sum, dc[dy * 8 * acs.covered_blocks_x() + dx], 1e-6) << "acs " << static_cast(type); } } } } } }; HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T( AcStrategyRoundtripDownsample, ::testing::Range(0, static_cast(AcStrategy::kNumValidStrategies))); TEST_P(AcStrategyRoundtripDownsample, Test) { Run(); } // Test that IDCT(block with zeros in the non-topleft corner) -> downsampled // IDCT is the same as IDCT -> DC(2x2) of the same block. class AcStrategyDownsample : public ::hwy::TestWithParamTargetAndT { protected: void Run() { 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); 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; 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++) { if (x > 4 || y > 4) { if (rng.Bernoulli(0.9f)) continue; } float* coeffs = idct + AcStrategy::kMaxCoeffArea; std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0); coeffs[y * cx * 8 + x] = 0.2f; TransformToPixels(type, coeffs, idct, acs.covered_blocks_x() * 8, scratch_space); std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0); coeffs[y * cx * 8 + x] = 0.2f; DCFromLowestFrequencies(type, coeffs, idct_acs_downsampled, acs.covered_blocks_x() * 8); // Downsample for (size_t dy = 0; dy < acs.covered_blocks_y(); dy++) { for (size_t dx = 0; dx < acs.covered_blocks_x(); dx++) { float sum = 0; for (size_t iy = 0; iy < 8; iy++) { for (size_t ix = 0; ix < 8; ix++) { sum += idct[(dy * 8 + iy) * 8 * acs.covered_blocks_x() + dx * 8 + ix]; } } sum /= 64; ASSERT_NEAR( sum, idct_acs_downsampled[dy * 8 * acs.covered_blocks_x() + dx], 1e-6) << " acs " << static_cast(type); } } } } } }; HWY_TARGET_INSTANTIATE_TEST_SUITE_P_T( AcStrategyDownsample, ::testing::Range(0, static_cast(AcStrategy::kNumValidStrategies))); TEST_P(AcStrategyDownsample, Test) { Run(); } class AcStrategyTargetTest : public ::hwy::TestWithParamTarget {}; HWY_TARGET_INSTANTIATE_TEST_SUITE_P(AcStrategyTargetTest); TEST_P(AcStrategyTargetTest, RoundtripAFVDCT) { HWY_ALIGN_MAX float idct[16]; for (size_t i = 0; i < 16; i++) { HWY_ALIGN_MAX float pixels[16] = {}; pixels[i] = 1; HWY_ALIGN_MAX float coeffs[16] = {}; AFVDCT4x4(pixels, coeffs); AFVIDCT4x4(coeffs, idct); for (size_t j = 0; j < 16; j++) { EXPECT_NEAR(idct[j], pixels[j], 1e-6); } } } TEST_P(AcStrategyTargetTest, BenchmarkAFV) { JxlMemoryManager* memory_manager = test::MemoryManager(); const AcStrategyType type = AcStrategyType::AFV0; HWY_ALIGN_MAX float pixels[64] = {1}; HWY_ALIGN_MAX float coeffs[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); } EXPECT_NEAR(pixels[0], 0.0, 1E-6); } TEST_P(AcStrategyTargetTest, BenchmarkAFVDCT) { HWY_ALIGN_MAX float pixels[64] = {1}; HWY_ALIGN_MAX float coeffs[64] = {}; for (size_t i = 0; i < 1 << 14; i++) { AFVDCT4x4(pixels, coeffs); AFVIDCT4x4(coeffs, pixels); } EXPECT_NEAR(pixels[0], 1.0, 1E-6); } } // namespace } // namespace jxl libjxl-0.11.1/lib/jxl/alpha.cc000066400000000000000000000075351472134335300160340ustar00rootroot00000000000000// 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/alpha.h" #include #include namespace jxl { static float Clamp(float x) { return std::max(std::min(1.0f, x), 0.0f); } void PerformAlphaBlending(const AlphaBlendingInputLayer& bg, const AlphaBlendingInputLayer& fg, const AlphaBlendingOutput& out, size_t num_pixels, bool alpha_is_premultiplied, bool clamp) { if (alpha_is_premultiplied) { for (size_t x = 0; x < num_pixels; ++x) { float fga = clamp ? Clamp(fg.a[x]) : fg.a[x]; out.r[x] = (fg.r[x] + bg.r[x] * (1.f - fga)); out.g[x] = (fg.g[x] + bg.g[x] * (1.f - fga)); out.b[x] = (fg.b[x] + bg.b[x] * (1.f - fga)); out.a[x] = (1.f - (1.f - fga) * (1.f - bg.a[x])); } } else { for (size_t x = 0; x < num_pixels; ++x) { float fga = clamp ? Clamp(fg.a[x]) : fg.a[x]; const float new_a = 1.f - (1.f - fga) * (1.f - bg.a[x]); const float rnew_a = (new_a > 0 ? 1.f / new_a : 0.f); out.r[x] = (fg.r[x] * fga + bg.r[x] * bg.a[x] * (1.f - fga)) * rnew_a; out.g[x] = (fg.g[x] * fga + bg.g[x] * bg.a[x] * (1.f - fga)) * rnew_a; out.b[x] = (fg.b[x] * fga + bg.b[x] * bg.a[x] * (1.f - fga)) * rnew_a; out.a[x] = new_a; } } } void PerformAlphaBlending(const float* bg, const float* bga, const float* fg, const float* fga, float* out, size_t num_pixels, 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] : Clamp(fga[x]); out[x] = (1.f - (1.f - fa) * (1.f - bga[x])); } } else { if (alpha_is_premultiplied) { for (size_t x = 0; x < num_pixels; ++x) { float fa = clamp ? fga[x] : Clamp(fga[x]); out[x] = (fg[x] + bg[x] * (1.f - fa)); } } else { for (size_t x = 0; x < num_pixels; ++x) { float fa = clamp ? fga[x] : Clamp(fga[x]); const float new_a = 1.f - (1.f - fa) * (1.f - bga[x]); const float rnew_a = (new_a > 0 ? 1.f / new_a : 0.f); out[x] = (fg[x] * fa + bg[x] * bga[x] * (1.f - fa)) * rnew_a; } } } } 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 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]; } } } void PerformMulBlending(const float* bg, const float* fg, float* out, size_t num_pixels, bool clamp) { if (clamp) { for (size_t x = 0; x < num_pixels; ++x) { out[x] = bg[x] * Clamp(fg[x]); } } else { for (size_t x = 0; x < num_pixels; ++x) { out[x] = bg[x] * fg[x]; } } } void PremultiplyAlpha(float* JXL_RESTRICT r, float* JXL_RESTRICT g, float* JXL_RESTRICT b, const float* JXL_RESTRICT a, size_t num_pixels) { for (size_t x = 0; x < num_pixels; ++x) { const float multiplier = std::max(kSmallAlpha, a[x]); r[x] *= multiplier; g[x] *= multiplier; b[x] *= multiplier; } } void UnpremultiplyAlpha(float* JXL_RESTRICT r, float* JXL_RESTRICT g, float* JXL_RESTRICT b, const float* JXL_RESTRICT a, size_t num_pixels) { for (size_t x = 0; x < num_pixels; ++x) { const float multiplier = 1.f / std::max(kSmallAlpha, a[x]); r[x] *= multiplier; g[x] *= multiplier; b[x] *= multiplier; } } } // namespace jxl libjxl-0.11.1/lib/jxl/alpha.h000066400000000000000000000044541472134335300156730ustar00rootroot00000000000000// 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_ALPHA_H_ #define LIB_JXL_ALPHA_H_ #include #include #include #include "lib/jxl/base/compiler_specific.h" namespace jxl { // A very small value to avoid divisions by zero when converting to // unpremultiplied alpha. Page 21 of the technical introduction to OpenEXR // (https://www.openexr.com/documentation/TechnicalIntroduction.pdf) recommends // "a power of two" that is "less than half of the smallest positive 16-bit // floating-point value". That smallest value happens to be the denormal number // 2^-24, so 2^-26 should be a good choice. static constexpr float kSmallAlpha = 1.f / (1u << 26u); struct AlphaBlendingInputLayer { const float* r; const float* g; const float* b; const float* a; }; struct AlphaBlendingOutput { float* r; float* g; float* b; float* a; }; // Note: The pointers in `out` are allowed to alias those in `bg` or `fg`. // No pointer shall be null. void PerformAlphaBlending(const AlphaBlendingInputLayer& bg, const AlphaBlendingInputLayer& fg, const AlphaBlendingOutput& out, size_t num_pixels, bool alpha_is_premultiplied, bool clamp); // Single plane alpha blending void PerformAlphaBlending(const float* bg, const float* bga, const float* fg, const float* fga, float* out, size_t num_pixels, bool alpha_is_premultiplied, bool clamp); void PerformAlphaWeightedAdd(const float* bg, const float* fg, const float* fga, float* out, size_t num_pixels, bool clamp); void PerformMulBlending(const float* bg, const float* fg, float* out, size_t num_pixels, bool clamp); void PremultiplyAlpha(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 r, float* JXL_RESTRICT g, float* JXL_RESTRICT b, const float* JXL_RESTRICT a, size_t num_pixels); } // namespace jxl #endif // LIB_JXL_ALPHA_H_ libjxl-0.11.1/lib/jxl/alpha_test.cc000066400000000000000000000102461472134335300170640ustar00rootroot00000000000000// 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/alpha.h" #include #include "lib/jxl/base/common.h" #include "lib/jxl/testing.h" namespace jxl { namespace { 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 Color bg_rgb{100, 110, 120}; const float bg_a = 180.f / 255; const Color fg_rgb{25, 21, 23}; const float fg_a = 15420.f / 65535; const float fg_a2 = 2.0f; Color out_rgb; float out_a; PerformAlphaBlending( /*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_ARRAY_NEAR(out_rgb, (Color{77.2f, 83.0f, 90.6f}), 0.05f); EXPECT_NEAR(out_a, 3174.f / 4095, 1e-5); PerformAlphaBlending( /*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_ARRAY_NEAR(out_rgb, fg_rgb, 0.05f); EXPECT_NEAR(out_a, 1.0f, 1e-5); } TEST(AlphaTest, BlendingWithPremultiplied) { const Color bg_rgb{100, 110, 120}; const float bg_a = 180.f / 255; const Color fg_rgb{25, 21, 23}; const float fg_a = 15420.f / 65535; const float fg_a2 = 2.0f; Color out_rgb; float out_a; PerformAlphaBlending( /*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_ARRAY_NEAR(out_rgb, (Color{101.5f, 105.1f, 114.8f}), 0.05f); EXPECT_NEAR(out_a, 3174.f / 4095, 1e-5); PerformAlphaBlending( /*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_ARRAY_NEAR(out_rgb, fg_rgb, 0.05f); EXPECT_NEAR(out_a, 1.0f, 1e-5); } TEST(AlphaTest, Mul) { const float bg = 100; const float fg = 25; float out; PerformMulBlending(&bg, &fg, &out, 1, /*clamp=*/false); EXPECT_NEAR(out, fg * bg, .05f); PerformMulBlending(&bg, &fg, &out, 1, /*clamp=*/true); EXPECT_NEAR(out, bg, .05f); } TEST(AlphaTest, PremultiplyAndUnpremultiply) { 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.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.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) { 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.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.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 } // namespace jxl libjxl-0.11.1/lib/jxl/ans_common.cc000066400000000000000000000144411472134335300170720ustar00rootroot00000000000000// 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/ans_common.h" #include #include #include #include #include "lib/jxl/ans_params.h" #include "lib/jxl/base/status.h" namespace jxl { std::vector CreateFlatHistogram(int length, int 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; for (int i = 0; i < rem_counts; ++i) { ++result[i]; } return result; } // 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 // consecutive ranges of offsets, which allows us to use a compact // representation. // Each entry is initialized with only the (symbol=i, offset) pairs; then // positions for which the entry overflows (i.e. distribution[i] > entry_size) // or is not full are computed, and put into a stack in increasing order. // Missing symbols in the distribution are padded with 0 (because `table_size` // >= number of symbols). The `cutoff` value for each entry is initialized to // the number of occupied slots in that entry (i.e. `distributions[i]`). While // the overflowing-symbol stack is not empty (which implies that the // underflowing-symbol stack also is not), the top overfull and underfull // positions are popped from the stack; the empty slots in the underfull entry // are then filled with as many slots as needed from the overfull entry; such // slots are placed after the slots in the overfull entry, and `offsets[1]` is // computed accordingly. The formerly underfull entry is thus now neither // underfull nor overfull, and represents exactly two symbols. The overfull // entry might be either overfull or underfull, and is pushed into the // corresponding stack. 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(); } // Ensure that a valid table is always returned, even for an empty // alphabet. Otherwise, a specially-crafted stream might crash the // decoder. if (distribution.empty()) { distribution.emplace_back(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++) { 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); // Initialize entries. for (size_t i = 0; i < distribution.size(); i++) { cutoffs[i] = distribution[i]; if (cutoffs[i] > entry_size) { overfull_posn.push_back(i); } else if (cutoffs[i] < entry_size) { underfull_posn.push_back(i); } } for (uint32_t i = distribution.size(); i < table_size; i++) { cutoffs[i] = 0; underfull_posn.push_back(i); } // Reassign overflow/underflow values. while (!overfull_posn.empty()) { uint32_t overfull_i = overfull_posn.back(); overfull_posn.pop_back(); 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]; cutoffs[overfull_i] -= underfull_by; // overfull positions have their original symbols a[underfull_i].right_value = overfull_i; a[underfull_i].offsets1 = cutoffs[overfull_i]; // Slots in the right part of entry underfull_i were taken from the end // of the symbols in entry overfull_i. if (cutoffs[overfull_i] < entry_size) { underfull_posn.push_back(overfull_i); } else if (cutoffs[overfull_i] > entry_size) { overfull_posn.push_back(overfull_i); } } for (uint32_t i = 0; i < table_size; i++) { // cutoffs[i] is properly initialized but the clang-analyzer doesn't infer // it since it is partially initialized across two for-loops. // NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult) if (cutoffs[i] == entry_size) { a[i].right_value = i; a[i].offsets1 = 0; a[i].cutoff = 0; } else { // Note that, if cutoff is not equal to entry_size, // a[i].offsets1 was initialized with (overfull cutoff) - // (entry_size - a[i].cutoff). Thus, subtracting // a[i].cutoff cannot make it negative. a[i].offsets1 -= cutoffs[i]; a[i].cutoff = cutoffs[i]; } const size_t freq0 = i < distribution.size() ? distribution[i] : 0; const size_t i1 = a[i].right_value; const size_t freq1 = i1 < distribution.size() ? distribution[i1] : 0; a[i].freq0 = static_cast(freq0); a[i].freq1_xor_freq0 = static_cast(freq1 ^ freq0); } return true; } } // namespace jxl libjxl-0.11.1/lib/jxl/ans_common.h000066400000000000000000000135661472134335300167430ustar00rootroot00000000000000// 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_ANS_COMMON_H_ #define LIB_JXL_ANS_COMMON_H_ #include #include #include #include #include #include // Prefetch #include #include "lib/jxl/ans_params.h" #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/status.h" namespace jxl { // Returns the precision (number of bits) that should be used to store // a histogram count such that Log2Floor(count) == logcount. static JXL_MAYBE_UNUSED JXL_INLINE uint32_t GetPopulationCountPrecision(uint32_t logcount, uint32_t shift) { int32_t r = std::min( logcount, static_cast(shift) - static_cast((ANS_LOG_TAB_SIZE - logcount) >> 1)); if (r < 0) return 0; return r; } // Returns a histogram where the counts are positive, differ by at most 1, // and add up to total_count. The bigger counts (if any) are at the beginning // of the histogram. std::vector CreateFlatHistogram(int length, int total_count); // An alias table implements a mapping from the [0, ANS_TAB_SIZE) range into // the [0, ANS_MAX_ALPHABET_SIZE) range, satisfying the following conditions: // - each symbol occurs as many times as specified by any valid distribution // of frequencies of the symbols. A valid distribution here is an array of // ANS_MAX_ALPHABET_SIZE that contains numbers in the range [0, ANS_TAB_SIZE], // and whose sum is ANS_TAB_SIZE. // - lookups can be done in constant time, and also return how many smaller // input values map into the same symbol, according to some well-defined order // of input values. // - the space used by the alias table is given by a small constant times the // index of the largest symbol with nonzero probability in the distribution. // Each of the entries in the table covers a range of `entry_size` values in the // [0, ANS_TAB_SIZE) range; consecutive entries represent consecutive // sub-ranges. In the range covered by entry `i`, the first `cutoff` values map // to symbol `i`, while the others map to symbol `right_value`. // // TODO(veluca): consider making the order used for computing offsets easier to // define - it is currently defined by the algorithm to compute the alias table. // Beware of breaking the implicit assumption that symbols that come after the // cutoff value should have an offset at least as big as the cutoff. struct AliasTable { struct Symbol { size_t value; size_t offset; size_t freq; }; // Working set size matters here (~64 tables x 256 entries). // offsets0 is always zero (beginning of [0] side among the same symbol). // offsets1 is an offset of (pos >= cutoff) side decremented by cutoff. #pragma pack(push, 1) struct Entry { uint8_t cutoff; // < kEntrySizeMinus1 when used by ANS. uint8_t right_value; // < alphabet size. uint16_t freq0; // Only used if `greater` (see Lookup) uint16_t offsets1; // <= ANS_TAB_SIZE uint16_t freq1_xor_freq0; // for branchless ternary in Lookup }; #pragma pack(pop) // Dividing `value` by `entry_size` determines `i`, the entry which is // responsible for the input. If the remainder is below `cutoff`, then the // mapped symbol is `i`; since `offsets[0]` stores the number of occurrences // of `i` "before" the start of this entry, the offset of the input will be // `offsets[0] + remainder`. If the remainder is above cutoff, the mapped // symbol is `right_value`; since `offsets[1]` stores the number of // occurrences of `right_value` "before" this entry, minus the `cutoff` value, // the input offset is then `remainder + offsets[1]`. static JXL_INLINE Symbol Lookup(const Entry* JXL_RESTRICT table, size_t value, size_t log_entry_size, size_t entry_size_minus_1) { const size_t i = value >> log_entry_size; const size_t pos = value & entry_size_minus_1; #if JXL_BYTE_ORDER_LITTLE uint64_t entry; memcpy(&entry, &table[i].cutoff, sizeof(entry)); const size_t cutoff = entry & 0xFF; // = MOVZX const size_t right_value = (entry >> 8) & 0xFF; // = MOVZX const size_t freq0 = (entry >> 16) & 0xFFFF; #else // Generates multiple loads with complex addressing. const size_t cutoff = table[i].cutoff; const size_t right_value = table[i].right_value; const size_t freq0 = table[i].freq0; #endif const bool greater = pos >= cutoff; #if JXL_BYTE_ORDER_LITTLE const uint64_t conditional = greater ? entry : 0; // = CMOV const size_t offsets1_or_0 = (conditional >> 32) & 0xFFFF; const size_t freq1_xor_freq0_or_0 = conditional >> 48; #else const size_t offsets1_or_0 = greater ? table[i].offsets1 : 0; const size_t freq1_xor_freq0_or_0 = greater ? table[i].freq1_xor_freq0 : 0; #endif // WARNING: moving this code may interfere with CMOV heuristics. Symbol s; s.value = greater ? right_value : i; s.offset = offsets1_or_0 + pos; s.freq = freq0 ^ freq1_xor_freq0_or_0; // = greater ? freq1 : freq0 // XOR avoids implementation-defined conversion from unsigned to signed. // Alternatives considered: BEXTR is 2 cycles on HSW, SET+shift causes // spills, simple ternary has a long dependency chain. return s; } static HWY_INLINE void Prefetch(const Entry* JXL_RESTRICT table, size_t value, size_t log_entry_size) { const size_t i = value >> log_entry_size; hwy::Prefetch(table + i); } }; // Computes an alias table for a given distribution. Status InitAliasTable(std::vector distribution, uint32_t log_range, size_t log_alpha_size, AliasTable::Entry* JXL_RESTRICT a); } // namespace jxl #endif // LIB_JXL_ANS_COMMON_H_ libjxl-0.11.1/lib/jxl/ans_common_test.cc000066400000000000000000000027651472134335300201370ustar00rootroot00000000000000// 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/ans_common.h" #include #include "lib/jxl/ans_params.h" #include "lib/jxl/testing.h" namespace jxl { namespace { void VerifyAliasDistribution(const std::vector& distribution, uint32_t log_range) { constexpr size_t log_alpha_size = 8; AliasTable::Entry table[1 << log_alpha_size]; 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( table, i, ANS_LOG_TAB_SIZE - 8, (1 << (ANS_LOG_TAB_SIZE - 8)) - 1); offsets[s.value].push_back(s.offset); } for (uint32_t i = 0; i < distribution.size(); i++) { ASSERT_EQ(static_cast(distribution[i]), offsets[i].size()); std::sort(offsets[i].begin(), offsets[i].end()); for (uint32_t j = 0; j < offsets[i].size(); j++) { ASSERT_EQ(offsets[i][j], j); } } } TEST(ANSCommonTest, AliasDistributionSmoke) { 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 } // namespace jxl libjxl-0.11.1/lib/jxl/ans_params.h000066400000000000000000000021341472134335300167230ustar00rootroot00000000000000// 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_ANS_PARAMS_H_ #define LIB_JXL_ANS_PARAMS_H_ // Common parameters that are needed for both the ANS entropy encoding and // decoding methods. #include #include namespace jxl { // TODO(veluca): decide if 12 is the best constant here (valid range is up to // 16). This requires recomputing the Huffman tables in {enc,dec}_ans.cc // 14 gives a 0.2% improvement at d1 and makes d8 slightly worse. This is // likely not worth the increase in encoder complexity. #define ANS_LOG_TAB_SIZE 12u #define ANS_TAB_SIZE (1 << ANS_LOG_TAB_SIZE) #define ANS_TAB_MASK (ANS_TAB_SIZE - 1) // Largest possible symbol to be encoded by either ANS or prefix coding. #define PREFIX_MAX_ALPHABET_SIZE 4096 #define ANS_MAX_ALPHABET_SIZE 256 // Max number of bits for prefix coding. #define PREFIX_MAX_BITS 15 #define ANS_SIGNATURE 0x13 // Initial state, used as CRC. } // namespace jxl #endif // LIB_JXL_ANS_PARAMS_H_ libjxl-0.11.1/lib/jxl/ans_test.cc000066400000000000000000000235751472134335300165710ustar00rootroot00000000000000// 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 "lib/jxl/ans_params.h" #include "lib/jxl/base/random.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{memory_manager}; // Space for magic bytes. 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); 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 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. BitReader br(writer.GetSpan()); ASSERT_EQ(br.ReadBits(16), kMagic1); std::vector dec_context_map; ANSCode decoded_codes; ASSERT_TRUE(DecodeHistograms(memory_manager, &br, n_histograms, &decoded_codes, &dec_context_map)); ASSERT_EQ(dec_context_map, context_map); JXL_TEST_ASSIGN_OR_DIE(ANSSymbolReader reader, ANSSymbolReader::Create(&decoded_codes, &br)); for (const Token& symbol : input_values) { uint32_t read_symbol = reader.ReadHybridUint(symbol.context, &br, dec_context_map); ASSERT_EQ(read_symbol, symbol.value); } ASSERT_TRUE(reader.CheckANSFinalState()); ASSERT_EQ(br.ReadBits(16), kMagic2); EXPECT_TRUE(br.Close()); } TEST(ANSTest, EmptyRoundtrip) { RoundtripTestcase(2, ANS_MAX_ALPHABET_SIZE, std::vector()); } TEST(ANSTest, SingleSymbolRoundtrip) { for (uint32_t i = 0; i < ANS_MAX_ALPHABET_SIZE; i++) { RoundtripTestcase(2, ANS_MAX_ALPHABET_SIZE, {{0, i}}); } for (uint32_t i = 0; i < ANS_MAX_ALPHABET_SIZE; i++) { RoundtripTestcase(2, ANS_MAX_ALPHABET_SIZE, std::vector(1024, {0, i})); } } #if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \ defined(THREAD_SANITIZER) constexpr size_t kReps = 3; #else constexpr size_t kReps = 10; #endif void RoundtripRandomStream(int alphabet_size, size_t reps = kReps, size_t num = 1 << 18) { constexpr int kNumHistograms = 3; Rng rng(0); for (size_t i = 0; i < reps; i++) { std::vector symbols; for (size_t j = 0; j < num; j++) { int context = rng.UniformI(0, kNumHistograms); int value = rng.UniformU(0, alphabet_size); symbols.emplace_back(context, value); } RoundtripTestcase(kNumHistograms, alphabet_size, symbols); } } void RoundtripRandomUnbalancedStream(int alphabet_size) { constexpr int kNumHistograms = 3; constexpr int kPrecision = 1 << 10; Rng rng(0); for (size_t i = 0; i < kReps; i++) { std::vector distributions[kNumHistograms] = {}; for (auto& distr : distributions) { distr.resize(kPrecision); int symbol = 0; int remaining = 1; for (int k = 0; k < kPrecision; k++) { if (remaining == 0) { if (symbol < alphabet_size - 1) symbol++; // There is no meaning behind this distribution: it's anything that // will create a nonuniform distribution and won't have too few // symbols usually. Also we want different distributions we get to be // sufficiently dissimilar. remaining = rng.UniformU(0, kPrecision - k + 1); } distr[k] = symbol; remaining--; } } std::vector symbols; for (int j = 0; j < 1 << 18; j++) { int context = rng.UniformI(0, kNumHistograms); int value = rng.UniformU(0, kPrecision); symbols.emplace_back(context, value); } RoundtripTestcase(kNumHistograms + 1, alphabet_size, symbols); } } TEST(ANSTest, RandomStreamRoundtrip3Small) { RoundtripRandomStream(3, 1, 16); } TEST(ANSTest, RandomStreamRoundtrip3) { RoundtripRandomStream(3); } TEST(ANSTest, RandomStreamRoundtripBig) { RoundtripRandomStream(ANS_MAX_ALPHABET_SIZE); } TEST(ANSTest, RandomUnbalancedStreamRoundtrip3) { RoundtripRandomUnbalancedStream(3); } TEST(ANSTest, RandomUnbalancedStreamRoundtripBig) { RoundtripRandomUnbalancedStream(ANS_MAX_ALPHABET_SIZE); } 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; 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++) { uint_config.emplace_back(i, j, k); } } } uint_config.emplace_back(log_alpha_size, 0, 0); uint_config_dec.resize(uint_config.size()); 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)); EXPECT_TRUE(br.Close()); for (size_t i = 0; i < uint_config.size(); i++) { EXPECT_EQ(uint_config[i].split_token, uint_config_dec[i].split_token); EXPECT_EQ(uint_config[i].msb_in_token, uint_config_dec[i].msb_in_token); EXPECT_EQ(uint_config[i].lsb_in_token, uint_config_dec[i].lsb_in_token); } } } 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].emplace_back(0, i % 4); } // up to lz77 window size. for (size_t i = 0; i < (1 << 20) - 1022; i++) { input_values[0].emplace_back(0, (i % 5) + 4); } // Ensure that when the window wraps around, new values are different. input_values[0].emplace_back(0, 0); for (size_t i = 0; i < 1024; i++) { input_values[0].emplace_back(0, i % 4); } std::vector context_map; EntropyEncodingData codes; HistogramParams params; params.lz77_method = lz77 ? HistogramParams::LZ77Method::kLZ77 : HistogramParams::LZ77Method::kNone; params.force_huffman = !ans; BitWriter writer{memory_manager}; { auto input_values_copy = input_values; 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(); } // We do not truncate the output. Reading past the end reads out zeroes // anyway. BitReader br(writer.GetSpan()); Status status = true; { BitReaderScopedCloser bc(br, status); std::vector dec_context_map; ANSCode decoded_codes; ASSERT_TRUE(DecodeHistograms(memory_manager, &br, 1, &decoded_codes, &dec_context_map)); ASSERT_EQ(dec_context_map, context_map); JXL_TEST_ASSIGN_OR_DIE(ANSSymbolReader reader, ANSSymbolReader::Create(&decoded_codes, &br)); ANSSymbolReader::Checkpoint checkpoint; size_t br_pos = 0; constexpr size_t kInterval = ANSSymbolReader::kMaxCheckpointInterval - 2; for (size_t i = 0; i < input_values[0].size(); i++) { if (i % kInterval == 0 && i > 0) { reader.Restore(checkpoint); ASSERT_TRUE(br.Close()); br = BitReader(writer.GetSpan()); br.SkipBits(br_pos); for (size_t j = i - kInterval; j < i; j++) { Token symbol = input_values[0][j]; uint32_t read_symbol = reader.ReadHybridUint(symbol.context, &br, dec_context_map); ASSERT_EQ(read_symbol, symbol.value) << "j = " << j; } } if (i % kInterval == 0) { reader.Save(&checkpoint); br_pos = br.TotalBitsConsumed(); } Token symbol = input_values[0][i]; uint32_t read_symbol = reader.ReadHybridUint(symbol.context, &br, dec_context_map); ASSERT_EQ(read_symbol, symbol.value) << "i = " << i; } ASSERT_TRUE(reader.CheckANSFinalState()); } EXPECT_TRUE(status); } TEST(ANSTest, TestCheckpointingANS) { TestCheckpointing(/*ans=*/true, /*lz77=*/false); } TEST(ANSTest, TestCheckpointingPrefix) { TestCheckpointing(/*ans=*/false, /*lz77=*/false); } TEST(ANSTest, TestCheckpointingANSLZ77) { TestCheckpointing(/*ans=*/true, /*lz77=*/true); } TEST(ANSTest, TestCheckpointingPrefixLZ77) { TestCheckpointing(/*ans=*/false, /*lz77=*/true); } } // namespace } // namespace jxl libjxl-0.11.1/lib/jxl/base/000077500000000000000000000000001472134335300153405ustar00rootroot00000000000000libjxl-0.11.1/lib/jxl/base/arch_macros.h000066400000000000000000000012421472134335300177710ustar00rootroot00000000000000// 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_ARCH_MACROS_H_ #define LIB_JXL_BASE_ARCH_MACROS_H_ // Defines the JXL_ARCH_* macros. namespace jxl { #if defined(__x86_64__) || defined(_M_X64) #define JXL_ARCH_X64 1 #else #define JXL_ARCH_X64 0 #endif #if defined(__powerpc64__) || defined(_M_PPC) #define JXL_ARCH_PPC 1 #else #define JXL_ARCH_PPC 0 #endif #if defined(__aarch64__) || defined(__arm__) #define JXL_ARCH_ARM 1 #else #define JXL_ARCH_ARM 0 #endif } // namespace jxl #endif // LIB_JXL_BASE_ARCH_MACROS_H_ libjxl-0.11.1/lib/jxl/base/bits.h000066400000000000000000000100331472134335300164470ustar00rootroot00000000000000// 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_BITS_H_ #define LIB_JXL_BASE_BITS_H_ // Specialized instructions for processing register-sized bit arrays. #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/status.h" #if JXL_COMPILER_MSVC #include #endif #include #include namespace jxl { // Empty struct used as a size tag type. template struct SizeTag {}; template constexpr bool IsSigned() { // TODO(eustas): remove dupes return static_cast(0) > static_cast(-1); } // Undefined results for x == 0. static JXL_INLINE JXL_MAYBE_UNUSED size_t Num0BitsAboveMS1Bit_Nonzero(SizeTag<4> /* tag */, const uint32_t x) { JXL_DASSERT(x != 0); #if JXL_COMPILER_MSVC unsigned long index; _BitScanReverse(&index, x); return 31 - index; #else return static_cast(__builtin_clz(x)); #endif } static JXL_INLINE JXL_MAYBE_UNUSED size_t Num0BitsAboveMS1Bit_Nonzero(SizeTag<8> /* tag */, const uint64_t x) { JXL_DASSERT(x != 0); #if JXL_COMPILER_MSVC #if JXL_ARCH_X64 unsigned long index; _BitScanReverse64(&index, x); return 63 - index; #else // JXL_ARCH_X64 // _BitScanReverse64 not available uint32_t msb = static_cast(x >> 32u); unsigned long index; if (msb == 0) { uint32_t lsb = static_cast(x & 0xFFFFFFFF); _BitScanReverse(&index, lsb); return 63 - index; } else { _BitScanReverse(&index, msb); return 31 - index; } #endif // JXL_ARCH_X64 #else return static_cast(__builtin_clzll(x)); #endif } template static JXL_INLINE JXL_MAYBE_UNUSED size_t Num0BitsAboveMS1Bit_Nonzero(const T x) { static_assert(!IsSigned(), "Num0BitsAboveMS1Bit_Nonzero: use unsigned"); return Num0BitsAboveMS1Bit_Nonzero(SizeTag(), x); } // Undefined results for x == 0. static JXL_INLINE JXL_MAYBE_UNUSED size_t Num0BitsBelowLS1Bit_Nonzero(SizeTag<4> /* tag */, const uint32_t x) { JXL_DASSERT(x != 0); #if JXL_COMPILER_MSVC unsigned long index; _BitScanForward(&index, x); return index; #else return static_cast(__builtin_ctz(x)); #endif } static JXL_INLINE JXL_MAYBE_UNUSED size_t Num0BitsBelowLS1Bit_Nonzero(SizeTag<8> /* tag */, const uint64_t x) { JXL_DASSERT(x != 0); #if JXL_COMPILER_MSVC #if JXL_ARCH_X64 unsigned long index; _BitScanForward64(&index, x); return index; #else // JXL_ARCH_64 // _BitScanForward64 not available uint32_t lsb = static_cast(x & 0xFFFFFFFF); unsigned long index; if (lsb == 0) { uint32_t msb = static_cast(x >> 32u); _BitScanForward(&index, msb); return 32 + index; } else { _BitScanForward(&index, lsb); return index; } #endif // JXL_ARCH_X64 #else return static_cast(__builtin_ctzll(x)); #endif } template static JXL_INLINE JXL_MAYBE_UNUSED size_t Num0BitsBelowLS1Bit_Nonzero(T x) { static_assert(!IsSigned(), "Num0BitsBelowLS1Bit_Nonzero: use unsigned"); return Num0BitsBelowLS1Bit_Nonzero(SizeTag(), x); } // Returns bit width for x == 0. template static JXL_INLINE JXL_MAYBE_UNUSED size_t Num0BitsAboveMS1Bit(const T x) { return (x == 0) ? sizeof(T) * 8 : Num0BitsAboveMS1Bit_Nonzero(x); } // Returns bit width for x == 0. template static JXL_INLINE JXL_MAYBE_UNUSED size_t Num0BitsBelowLS1Bit(const T x) { return (x == 0) ? sizeof(T) * 8 : Num0BitsBelowLS1Bit_Nonzero(x); } // Returns base-2 logarithm, rounded down. template static JXL_INLINE JXL_MAYBE_UNUSED size_t FloorLog2Nonzero(const T x) { return (sizeof(T) * 8 - 1) ^ Num0BitsAboveMS1Bit_Nonzero(x); } // Returns base-2 logarithm, rounded up. template static JXL_INLINE JXL_MAYBE_UNUSED size_t CeilLog2Nonzero(const T x) { const size_t floor_log2 = FloorLog2Nonzero(x); if ((x & (x - 1)) == 0) return floor_log2; // power of two return floor_log2 + 1; } } // namespace jxl #endif // LIB_JXL_BASE_BITS_H_ libjxl-0.11.1/lib/jxl/base/byte_order.h000066400000000000000000000165741472134335300176640ustar00rootroot00000000000000// 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_BYTE_ORDER_H_ #define LIB_JXL_BASE_BYTE_ORDER_H_ #include #include #include // memcpy #include "lib/jxl/base/compiler_specific.h" #if JXL_COMPILER_MSVC #include // _byteswap_* #endif #if (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) #define JXL_BYTE_ORDER_LITTLE 1 #else // This means that we don't know that the byte order is little endian, in // this case we use endian-neutral code that works for both little- and // big-endian. #define JXL_BYTE_ORDER_LITTLE 0 #endif // Returns whether the system is little-endian (least-significant byte first). #if JXL_BYTE_ORDER_LITTLE static constexpr bool IsLittleEndian() { return true; } #else static inline bool IsLittleEndian() { const uint32_t multibyte = 1; uint8_t byte; memcpy(&byte, &multibyte, 1); return byte == 1; } #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 static JXL_INLINE uint32_t LoadBE16(const uint8_t* p) { const uint32_t byte1 = p[0]; const uint32_t byte0 = p[1]; return (byte1 << 8) | byte0; } static JXL_INLINE uint32_t LoadLE16(const uint8_t* p) { const uint32_t byte0 = p[0]; const uint32_t byte1 = p[1]; return (byte1 << 8) | byte0; } static JXL_INLINE uint32_t LoadBE32(const uint8_t* p) { #if JXL_BYTE_ORDER_LITTLE uint32_t big; memcpy(&big, p, 4); return JXL_BSWAP32(big); #else // Byte-order-independent - can't assume this machine is big endian. const uint32_t byte3 = p[0]; const uint32_t byte2 = p[1]; const uint32_t byte1 = p[2]; const uint32_t byte0 = p[3]; return (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0; #endif } static JXL_INLINE uint64_t LoadBE64(const uint8_t* p) { #if JXL_BYTE_ORDER_LITTLE uint64_t big; memcpy(&big, p, 8); return JXL_BSWAP64(big); #else // Byte-order-independent - can't assume this machine is big endian. const uint64_t byte7 = p[0]; const uint64_t byte6 = p[1]; const uint64_t byte5 = p[2]; const uint64_t byte4 = p[3]; const uint64_t byte3 = p[4]; const uint64_t byte2 = p[5]; const uint64_t byte1 = p[6]; const uint64_t byte0 = p[7]; return (byte7 << 56ull) | (byte6 << 48ull) | (byte5 << 40ull) | (byte4 << 32ull) | (byte3 << 24ull) | (byte2 << 16ull) | (byte1 << 8ull) | byte0; #endif } static JXL_INLINE uint32_t LoadLE32(const uint8_t* p) { #if JXL_BYTE_ORDER_LITTLE uint32_t little; memcpy(&little, p, 4); return little; #else // Byte-order-independent - can't assume this machine is big endian. const uint32_t byte0 = p[0]; const uint32_t byte1 = p[1]; const uint32_t byte2 = p[2]; const uint32_t byte3 = p[3]; return (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0; #endif } static JXL_INLINE uint64_t LoadLE64(const uint8_t* p) { #if JXL_BYTE_ORDER_LITTLE uint64_t little; memcpy(&little, p, 8); return little; #else // Byte-order-independent - can't assume this machine is big endian. const uint64_t byte0 = p[0]; const uint64_t byte1 = p[1]; const uint64_t byte2 = p[2]; const uint64_t byte3 = p[3]; const uint64_t byte4 = p[4]; const uint64_t byte5 = p[5]; const uint64_t byte6 = p[6]; const uint64_t byte7 = p[7]; return (byte7 << 56) | (byte6 << 48) | (byte5 << 40) | (byte4 << 32) | (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0; #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; } static JXL_INLINE void StoreLE16(const uint32_t native, uint8_t* p) { p[1] = (native >> 8) & 0xFF; p[0] = native & 0xFF; } static JXL_INLINE void StoreBE32(const uint32_t native, uint8_t* p) { #if JXL_BYTE_ORDER_LITTLE const uint32_t big = JXL_BSWAP32(native); memcpy(p, &big, 4); #else // Byte-order-independent - can't assume this machine is big endian. p[0] = native >> 24; p[1] = (native >> 16) & 0xFF; p[2] = (native >> 8) & 0xFF; p[3] = native & 0xFF; #endif } static JXL_INLINE void StoreBE64(const uint64_t native, uint8_t* p) { #if JXL_BYTE_ORDER_LITTLE const uint64_t big = JXL_BSWAP64(native); memcpy(p, &big, 8); #else // Byte-order-independent - can't assume this machine is big endian. p[0] = native >> 56ull; p[1] = (native >> 48ull) & 0xFF; p[2] = (native >> 40ull) & 0xFF; p[3] = (native >> 32ull) & 0xFF; p[4] = (native >> 24ull) & 0xFF; p[5] = (native >> 16ull) & 0xFF; p[6] = (native >> 8ull) & 0xFF; p[7] = native & 0xFF; #endif } static JXL_INLINE void StoreLE32(const uint32_t native, uint8_t* p) { #if JXL_BYTE_ORDER_LITTLE const uint32_t little = native; memcpy(p, &little, 4); #else // Byte-order-independent - can't assume this machine is big endian. p[3] = native >> 24; p[2] = (native >> 16) & 0xFF; p[1] = (native >> 8) & 0xFF; p[0] = native & 0xFF; #endif } static JXL_INLINE void StoreLE64(const uint64_t native, uint8_t* p) { #if JXL_BYTE_ORDER_LITTLE const uint64_t little = native; memcpy(p, &little, 8); #else // Byte-order-independent - can't assume this machine is big endian. p[7] = native >> 56; p[6] = (native >> 48) & 0xFF; p[5] = (native >> 40) & 0xFF; p[4] = (native >> 32) & 0xFF; p[3] = (native >> 24) & 0xFF; p[2] = (native >> 16) & 0xFF; p[1] = (native >> 8) & 0xFF; p[0] = native & 0xFF; #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 {}; // Wrappers for calling from generic code. static JXL_INLINE void Store16(OrderBE /*tag*/, const uint32_t native, uint8_t* p) { StoreBE16(native, p); } static JXL_INLINE void Store16(OrderLE /*tag*/, const uint32_t native, uint8_t* p) { StoreLE16(native, p); } static JXL_INLINE void Store32(OrderBE /*tag*/, const uint32_t native, uint8_t* p) { StoreBE32(native, p); } static JXL_INLINE void Store32(OrderLE /*tag*/, const uint32_t native, uint8_t* p) { StoreLE32(native, p); } static JXL_INLINE uint32_t Load16(OrderBE /*tag*/, const uint8_t* p) { return LoadBE16(p); } static JXL_INLINE uint32_t Load16(OrderLE /*tag*/, const uint8_t* p) { return LoadLE16(p); } static JXL_INLINE uint32_t Load32(OrderBE /*tag*/, const uint8_t* p) { return LoadBE32(p); } static JXL_INLINE uint32_t Load32(OrderLE /*tag*/, const uint8_t* p) { return LoadLE32(p); } #endif // LIB_JXL_BASE_BYTE_ORDER_H_ libjxl-0.11.1/lib/jxl/base/c_callback_support.h000066400000000000000000000015411472134335300213440ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jxl/base/common.h000066400000000000000000000070341472134335300170050ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jxl/base/compiler_specific.h000066400000000000000000000147121472134335300211750ustar00rootroot00000000000000// 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_COMPILER_SPECIFIC_H_ #define LIB_JXL_BASE_COMPILER_SPECIFIC_H_ // Macros for compiler version + nonstandard keywords, e.g. __builtin_expect. #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. #ifdef _MSC_VER #define JXL_COMPILER_MSVC _MSC_VER #else #define JXL_COMPILER_MSVC 0 #endif #ifdef __GNUC__ #define JXL_COMPILER_GCC (__GNUC__ * 100 + __GNUC_MINOR__) #else #define JXL_COMPILER_GCC 0 #endif #ifdef __clang__ #define JXL_COMPILER_CLANG (__clang_major__ * 100 + __clang_minor__) // Clang pretends to be GCC for compatibility. #undef JXL_COMPILER_GCC #define JXL_COMPILER_GCC 0 #else #define JXL_COMPILER_CLANG 0 #endif #if JXL_COMPILER_MSVC #define JXL_RESTRICT __restrict #elif JXL_COMPILER_GCC || JXL_COMPILER_CLANG #define JXL_RESTRICT __restrict__ #else #define JXL_RESTRICT #endif #if JXL_COMPILER_MSVC #define JXL_INLINE __forceinline #define JXL_NOINLINE __declspec(noinline) #else #define JXL_INLINE inline __attribute__((always_inline)) #define JXL_NOINLINE __attribute__((noinline)) #endif #if JXL_COMPILER_MSVC #define JXL_NORETURN __declspec(noreturn) #elif JXL_COMPILER_GCC || JXL_COMPILER_CLANG #define JXL_NORETURN __attribute__((noreturn)) #else #define JXL_NORETURN #endif #if JXL_COMPILER_MSVC #define JXL_MAYBE_UNUSED #else // Encountered "attribute list cannot appear here" when using the C++17 // [[maybe_unused]], so only use the old style attribute for now. #define JXL_MAYBE_UNUSED __attribute__((unused)) #endif // MSAN execution won't hurt if some code it not inlined, but this can greatly // improve compilation time. Unfortunately this macro can not be used just // everywhere - inside header files it leads to "multiple definition" error; // though it would be better not to have JXL_INLINE in header overall. #if JXL_MEMORY_SANITIZER || JXL_ADDRESS_SANITIZER || JXL_THREAD_SANITIZER #define JXL_MAYBE_INLINE JXL_MAYBE_UNUSED #else #define JXL_MAYBE_INLINE JXL_INLINE #endif #if JXL_COMPILER_MSVC // Unsupported, __assume is not the same. #define JXL_LIKELY(expr) expr #define JXL_UNLIKELY(expr) expr #else #define JXL_LIKELY(expr) __builtin_expect(!!(expr), 1) #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); // // The assignment semantics are required by GCC/Clang. ICC provides an in-place // __assume_aligned, whereas MSVC's __assume appears unsuitable. #if JXL_COMPILER_CLANG // Early versions of Clang did not support __builtin_assume_aligned. #define JXL_HAS_ASSUME_ALIGNED __has_builtin(__builtin_assume_aligned) #elif JXL_COMPILER_GCC #define JXL_HAS_ASSUME_ALIGNED 1 #else #define JXL_HAS_ASSUME_ALIGNED 0 #endif #if JXL_HAS_ASSUME_ALIGNED #define JXL_ASSUME_ALIGNED(ptr, align) __builtin_assume_aligned((ptr), (align)) #else #define JXL_ASSUME_ALIGNED(ptr, align) (ptr) /* not supported */ #endif #ifdef __has_attribute #define JXL_HAVE_ATTRIBUTE(x) __has_attribute(x) #else #define JXL_HAVE_ATTRIBUTE(x) 0 #endif // Raises warnings if the function return value is unused. Should appear as the // first part of a function definition/declaration. #if JXL_HAVE_ATTRIBUTE(nodiscard) #define JXL_MUST_USE_RESULT [[nodiscard]] #elif JXL_COMPILER_CLANG && JXL_HAVE_ATTRIBUTE(warn_unused_result) #define JXL_MUST_USE_RESULT __attribute__((warn_unused_result)) #else #define JXL_MUST_USE_RESULT #endif // Disable certain -fsanitize flags for functions that are expected to include // things like unsigned integer overflow. For example use in the function // declaration JXL_NO_SANITIZE("unsigned-integer-overflow") to silence unsigned // integer overflow ubsan messages. #if JXL_COMPILER_CLANG && JXL_HAVE_ATTRIBUTE(no_sanitize) #define JXL_NO_SANITIZE(X) __attribute__((no_sanitize(X))) #else #define JXL_NO_SANITIZE(X) #endif #if JXL_HAVE_ATTRIBUTE(__format__) #define JXL_FORMAT(idx_fmt, idx_arg) \ __attribute__((__format__(__printf__, idx_fmt, idx_arg))) #else #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 #define JXL_CRASH() __debugbreak(), (void)abort() #else #define JXL_CRASH() (void)__builtin_trap() #endif #endif // LIB_JXL_BASE_COMPILER_SPECIFIC_H_ libjxl-0.11.1/lib/jxl/base/data_parallel.h000066400000000000000000000114151472134335300203000ustar00rootroot00000000000000// 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_DATA_PARALLEL_H_ #define LIB_JXL_BASE_DATA_PARALLEL_H_ // Portable, low-overhead C++11 ThreadPool alternative to OpenMP for // data-parallel computations. #include #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 #pragma warning(disable : 4180) #endif namespace jxl { class ThreadPool { public: ThreadPool(JxlParallelRunner runner, void* runner_opaque) : runner_(runner), runner_opaque_(runner ? runner_opaque : static_cast(this)) {} ThreadPool(const ThreadPool&) = delete; ThreadPool& operator&(const ThreadPool&) = delete; JxlParallelRunner runner() const { return runner_; } void* runner_opaque() const { return runner_opaque_; } // Runs init_func(num_threads) followed by data_func(task, thread) on worker // thread(s) for every task in [begin, end). init_func() must return a Status // indicating whether the initialization succeeded. // "thread" is an integer smaller than num_threads. // Not thread-safe - no two calls to Run may overlap. // Subsequent calls will reuse the same threads. // // Precondition: begin <= end. template Status Run(uint32_t begin, uint32_t end, const InitFunc& init_func, 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. 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. static Status NoInit(size_t num_threads) { return true; } private: // class holding the state of a Run() call to pass to the runner_ as an // opaque_jpegxl pointer. template class RunCallState final { public: RunCallState(const InitFunc& init_func, const DataFunc& data_func) : init_func_(init_func), data_func_(data_func) {} // JxlParallelRunInit interface. static int CallInitFunc(void* jpegxl_opaque, size_t num_threads) { auto* self = static_cast*>(jpegxl_opaque); // Returns -1 when the internal init function returns false Status to // indicate an error. 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) { auto* self = static_cast*>(jpegxl_opaque); 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}; }; // The caller supplied runner function and its opaque void*. const JxlParallelRunner runner_; void* const runner_opaque_; }; template Status RunOnPool(ThreadPool* pool, const uint32_t begin, const uint32_t end, const InitFunc& init_func, const DataFunc& data_func, const char* caller) { if (pool == nullptr) { ThreadPool default_pool(nullptr, nullptr); return default_pool.Run(begin, end, init_func, data_func, caller); } else { return pool->Run(begin, end, init_func, data_func, caller); } } } // namespace jxl #if JXL_COMPILER_MSVC #pragma warning(default : 4180) #endif #endif // LIB_JXL_BASE_DATA_PARALLEL_H_ libjxl-0.11.1/lib/jxl/base/exif.h000066400000000000000000000056331472134335300164530ustar00rootroot00000000000000// 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_EXIF_H_ #define LIB_JXL_BASE_EXIF_H_ // Basic parsing of Exif (just enough for the render-impacting things // like orientation) #include #include #include #include #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/compiler_specific.h" namespace jxl { constexpr uint16_t kExifOrientationTag = 274; // Checks if a blob looks like Exif, and if so, sets bigendian // according to the tiff endianness 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) { *bigendian = true; return true; } else if (LoadLE32(t) == 0x002A4949) { *bigendian = false; return true; } return false; // not a valid tiff header } // Finds the position of an Exif tag, or 0 if it is not found 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; 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) { if (t + 12 >= exif.data() + exif.size()) return 0; uint16_t tag = (bigendian ? LoadBE16(t) : LoadLE16(t)); t += 2; if (tag == tagname) return static_cast(t - exif.data()); t += 10; nb_tags--; } return 0; } // TODO(jon): tag 1 can be used to represent Adobe RGB 1998 if it has value // "R03" // TODO(jon): set intrinsic dimensions according to // https://discourse.wicg.io/t/proposal-exif-image-resolution-auto-and-from-image/4326/24 // 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. JXL_INLINE void InterpretExif(const std::vector& exif, JxlOrientation* orientation) { bool bigendian; if (!IsExif(exif, &bigendian)) return; size_t o_pos = FindExifTagPosition(exif, kExifOrientationTag); if (o_pos) { const uint8_t* t = exif.data() + o_pos; uint16_t type = (bigendian ? LoadBE16(t) : LoadLE16(t)); t += 2; uint32_t count = (bigendian ? LoadBE32(t) : LoadLE32(t)); t += 4; uint16_t value = (bigendian ? LoadBE16(t) : LoadLE16(t)); if (type == 3 && count == 1 && value >= 1 && value <= 8) { *orientation = static_cast(value); } } } } // namespace jxl #endif // LIB_JXL_BASE_EXIF_H_ libjxl-0.11.1/lib/jxl/base/fast_math-inl.h000066400000000000000000000203051472134335300202370ustar00rootroot00000000000000// 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. // Fast SIMD math ops (log2, encoder only, cos, erf for splines) #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_BASE_FAST_MATH_INL_H_ #endif #include #include "lib/jxl/base/common.h" #include "lib/jxl/base/rational_polynomial-inl.h" HWY_BEFORE_NAMESPACE(); namespace jxl { namespace HWY_NAMESPACE { // These templates are not found via ADL. using hwy::HWY_NAMESPACE::Abs; using hwy::HWY_NAMESPACE::Add; using hwy::HWY_NAMESPACE::Eq; using hwy::HWY_NAMESPACE::Floor; using hwy::HWY_NAMESPACE::Ge; using hwy::HWY_NAMESPACE::GetLane; using hwy::HWY_NAMESPACE::IfThenElse; using hwy::HWY_NAMESPACE::IfThenZeroElse; using hwy::HWY_NAMESPACE::Le; 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::Sub; using hwy::HWY_NAMESPACE::Xor; // 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); } // max relative error ~3e-5 template V FastPowf(const DF df, V base, V exponent) { return FastPow2f(df, Mul(FastLog2f(df, base), exponent)); } // Computes cosine like std::cos. // L1 error 7e-5. template V FastCosf(const DF df, V x) { // Step 1: range reduction to [0, 2pi) const auto pi2 = Set(df, kPi * 2.0f); const auto pi2_inv = Set(df, 0.5f / kPi); const auto npi2 = Mul(Floor(Mul(x, pi2_inv)), pi2); const auto xmodpi2 = Sub(x, npi2); // Step 2: range reduction to [0, pi] const auto x_pi = Min(xmodpi2, Sub(pi2, xmodpi2)); // Step 3: range reduction to [0, pi/2] const auto above_pihalf = Ge(x_pi, Set(df, kPi / 2.0f)); const auto x_pihalf = IfThenElse(above_pihalf, Sub(Set(df, kPi), x_pi), x_pi); // Step 4: Taylor-like approximation, scaled by 2**0.75 to make angle // duplication steps faster, on x/4. const auto xs = Mul(x_pihalf, Set(df, 0.25f)); const auto x2 = Mul(xs, xs); const auto x4 = Mul(x2, x2); const auto cosx_prescaling = MulAdd(x4, Set(df, 0.06960438), MulAdd(x2, Set(df, -0.84087373), Set(df, 1.68179268))); // Step 5: angle duplication. const auto cosx_scale1 = MulAdd(cosx_prescaling, cosx_prescaling, Set(df, -1.414213562)); const auto cosx_scale2 = MulAdd(cosx_scale1, cosx_scale1, Set(df, -1)); // Step 6: change sign if needed. const Rebind du; auto signbit = ShiftLeft<31>(BitCast(du, VecFromMask(df, above_pihalf))); return BitCast(df, Xor(signbit, BitCast(du, cosx_scale2))); } // Computes the error function like std::erf. // L1 error 7e-4. template V FastErff(const DF df, V x) { // Formula from // https://en.wikipedia.org/wiki/Error_function#Numerical_approximations // but constants have been recomputed. const auto xle0 = Le(x, Zero(df)); const auto absx = Abs(x); // Compute 1 - 1 / ((((x * a + b) * x + c) * x + d) * x + 1)**4 const auto denom1 = MulAdd(absx, Set(df, 7.77394369e-02), Set(df, 2.05260015e-04)); const auto denom2 = MulAdd(denom1, absx, Set(df, 2.32120216e-01)); const auto denom3 = MulAdd(denom2, absx, Set(df, 2.77820801e-01)); const auto denom4 = MulAdd(denom3, absx, Set(df, 1.0f)); const auto denom5 = Mul(denom4, denom4); const auto inv_denom5 = Div(Set(df, 1.0f), denom5); const auto result = NegMulAdd(inv_denom5, inv_denom5, Set(df, 1.0f)); // Change sign if needed. const Rebind du; auto signbit = ShiftLeft<31>(BitCast(du, VecFromMask(df, xle0))); return BitCast(df, Xor(signbit, BitCast(du, result))); } inline float FastLog2f(float f) { HWY_CAPPED(float, 1) D; return GetLane(FastLog2f(D, Set(D, f))); } inline float FastPow2f(float f) { HWY_CAPPED(float, 1) D; return GetLane(FastPow2f(D, Set(D, f))); } inline float FastPowf(float b, float e) { HWY_CAPPED(float, 1) D; return GetLane(FastPowf(D, Set(D, b), Set(D, e))); } inline float FastCosf(float f) { HWY_CAPPED(float, 1) D; return GetLane(FastCosf(D, Set(D, f))); } inline float FastErff(float f) { HWY_CAPPED(float, 1) D; return GetLane(FastErff(D, Set(D, f))); } // Returns cbrt(x) + add with 6 ulp max error. // Modified from vectormath_exp.h, Apache 2 license. // https://www.agner.org/optimize/vectorclass.zip template V CubeRootAndAdd(const V x, const V add) { const HWY_FULL(float) df; const HWY_FULL(int32_t) di; const auto kExpBias = Set(di, 0x54800000); // cast(1.) + cast(1.) / 3 const auto kExpMul = Set(di, 0x002AAAAA); // shifted 1/3 const auto k1_3 = Set(df, 1.0f / 3); const auto k4_3 = Set(df, 4.0f / 3); const auto xa = x; // assume inputs never negative const auto xa_3 = Mul(k1_3, xa); // Multiply exponent by -1/3 const auto m1 = BitCast(di, xa); // Special case for 0. 0 is represented with an exponent of 0, so the // "kExpBias - 1/3 * exp" below gives the wrong result. The IfThenZeroElse() // sets those values as 0, which prevents having NaNs in the computations // below. // TODO(eustas): use fused op const auto m2 = IfThenZeroElse( Eq(m1, Zero(di)), Sub(kExpBias, Mul((ShiftRight<23>(m1)), kExpMul))); auto r = BitCast(df, m2); // Newton-Raphson iterations for (int i = 0; i < 3; i++) { const auto r2 = Mul(r, r); r = NegMulAdd(xa_3, Mul(r2, r2), Mul(k4_3, r)); } // Final iteration auto r2 = Mul(r, r); r = MulAdd(k1_3, NegMulAdd(xa, Mul(r2, r2), r), r); r2 = Mul(r, r); r = MulAdd(r2, x, add); return r; } // NOLINTNEXTLINE(google-readability-namespace-comments) } // namespace HWY_NAMESPACE } // namespace jxl HWY_AFTER_NAMESPACE(); #endif // LIB_JXL_BASE_FAST_MATH_INL_H_ #if HWY_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); } inline float FastPow2f(float f) { return HWY_STATIC_DISPATCH(FastPow2f)(f); } inline float FastPowf(float b, float e) { return HWY_STATIC_DISPATCH(FastPowf)(b, e); } 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 // LIB_JXL_BASE_FAST_MATH_ONCE #endif // HWY_ONCE libjxl-0.11.1/lib/jxl/base/float.h000066400000000000000000000054541472134335300166260ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jxl/base/iaca.h000066400000000000000000000036001472134335300164050ustar00rootroot00000000000000// 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_IACA_H_ #define LIB_JXL_BASE_IACA_H_ #include "lib/jxl/base/compiler_specific.h" // IACA (Intel's Code Analyzer) analyzes instruction latencies, but only for // code between special markers. These functions embed such markers in an // executable, but only for reading via IACA - they deliberately trigger a // crash if executed to ensure they are removed in normal builds. #ifndef JXL_IACA_ENABLED #define JXL_IACA_ENABLED 0 #endif namespace jxl { // Call before the region of interest. static JXL_INLINE void BeginIACA() { #if JXL_IACA_ENABLED && (JXL_COMPILER_GCC || JXL_COMPILER_CLANG) asm volatile( // UD2 "instruction" raises an invalid opcode exception. ".byte 0x0F, 0x0B\n\t" // Magic sequence recognized by IACA (MOV + addr32 fs:NOP). This actually // clobbers EBX, but we don't care because the code won't be run, and we // want IACA to observe the same code the compiler would have generated // without this marker. "movl $111, %%ebx\n\t" ".byte 0x64, 0x67, 0x90\n\t" : : // (Allegedly) clobbering memory may prevent reordering. : "memory"); #endif } // Call after the region of interest. static JXL_INLINE void EndIACA() { #if JXL_IACA_ENABLED && (JXL_COMPILER_GCC || JXL_COMPILER_CLANG) asm volatile( // See above. "movl $222, %%ebx\n\t" ".byte 0x64, 0x67, 0x90\n\t" // UD2 ".byte 0x0F, 0x0B\n\t" : : // (Allegedly) clobbering memory may prevent reordering. : "memory"); #endif } // Add to a scope to mark a region. struct ScopeIACA { JXL_INLINE ScopeIACA() { BeginIACA(); } JXL_INLINE ~ScopeIACA() { EndIACA(); } }; } // namespace jxl #endif // LIB_JXL_BASE_IACA_H_ libjxl-0.11.1/lib/jxl/base/include_jpeglib.h000066400000000000000000000011241472134335300206260ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jxl/base/matrix_ops.h000066400000000000000000000057371472134335300177120ustar00rootroot00000000000000// 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_ libjxl-0.11.1/lib/jxl/base/os_macros.h000066400000000000000000000015401472134335300174760ustar00rootroot00000000000000// 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_OS_MACROS_H_ #define LIB_JXL_BASE_OS_MACROS_H_ // Defines the JXL_OS_* macros. #if defined(_WIN32) || defined(_WIN64) #define JXL_OS_WIN 1 #else #define JXL_OS_WIN 0 #endif #ifdef __linux__ #define JXL_OS_LINUX 1 #else #define JXL_OS_LINUX 0 #endif #ifdef __APPLE__ #define JXL_OS_MAC 1 #else #define JXL_OS_MAC 0 #endif #define JXL_OS_IOS 0 #ifdef __APPLE__ #include #if TARGET_OS_IPHONE #undef JXL_OS_IOS #define JXL_OS_IOS 1 #endif #endif #ifdef __FreeBSD__ #define JXL_OS_FREEBSD 1 #else #define JXL_OS_FREEBSD 0 #endif #ifdef __HAIKU__ #define JXL_OS_HAIKU 1 #else #define JXL_OS_HAIKU 0 #endif #endif // LIB_JXL_BASE_OS_MACROS_H_ libjxl-0.11.1/lib/jxl/base/override.h000066400000000000000000000015421472134335300173320ustar00rootroot00000000000000// 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_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 : int8_t { kOn = 1, kOff = 0, kDefault = -1 }; static inline Override OverrideFromBool(bool flag) { return flag ? Override::kOn : Override::kOff; } static inline bool ApplyOverride(Override o, bool default_condition) { if (o == Override::kOn) return true; if (o == Override::kOff) return false; return default_condition; } } // namespace jxl #endif // LIB_JXL_BASE_OVERRIDE_H_ libjxl-0.11.1/lib/jxl/base/printf_macros.h000066400000000000000000000014661472134335300203660ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #ifndef LIB_JXL_BASE_PRINTF_MACROS_H_ #define LIB_JXL_BASE_PRINTF_MACROS_H_ // Format string macros. These should be included after any other system // library since those may unconditionally define these, depending on the // platform. // PRIuS and PRIdS macros to print size_t and ssize_t respectively. #if !defined(PRIdS) #if defined(_WIN64) #define PRIdS "lld" #elif defined(_WIN32) #define PRIdS "d" #else #define PRIdS "zd" #endif #endif // PRIdS #if !defined(PRIuS) #if defined(_WIN64) #define PRIuS "llu" #elif defined(_WIN32) #define PRIuS "u" #else #define PRIuS "zu" #endif #endif // PRIuS #endif // LIB_JXL_BASE_PRINTF_MACROS_H_ libjxl-0.11.1/lib/jxl/base/random.h000066400000000000000000000053621472134335300167770ustar00rootroot00000000000000// 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_RANDOM_ #define LIB_JXL_BASE_RANDOM_ // Random number generator + distributions. // We don't use because the implementation (and thus results) differs // between libstdc++ and libc++. #include #include #include #include #include "lib/jxl/base/status.h" namespace jxl { struct Rng { explicit Rng(uint64_t seed) : s{static_cast(0x94D049BB133111EBull), static_cast(0xBF58476D1CE4E5B9ull) + seed} {} // Xorshift128+ adapted from xorshift128+-inl.h uint64_t operator()() { uint64_t s1 = s[0]; const uint64_t s0 = s[1]; const uint64_t bits = s1 + s0; // b, c s[0] = s0; s1 ^= s1 << 23; s1 ^= s0 ^ (s1 >> 18) ^ (s0 >> 5); s[1] = s1; return bits; } // Uniformly distributed int64_t in [begin, end), under the assumption that // `end-begin` is significantly smaller than 1<<64, otherwise there is some // bias. int64_t UniformI(int64_t begin, int64_t end) { JXL_DASSERT(end > begin); return static_cast((*this)() % static_cast(end - begin)) + begin; } // Same as UniformI, but for uint64_t. uint64_t UniformU(uint64_t begin, uint64_t end) { JXL_DASSERT(end > begin); return (*this)() % (end - begin) + begin; } // Uniformly distributed float in [begin, end) range. Note: only 23 bits of // randomness. float UniformF(float begin, float end) { float f; // Bits of a random [1, 2) float. uint32_t u = ((*this)() >> (64 - 23)) | 0x3F800000; static_assert(sizeof(f) == sizeof(u), "Float and U32 must have the same size"); memcpy(&f, &u, sizeof(f)); // Note: (end-begin) * f + (2*begin-end) may fail to return a number >= // begin. return (end - begin) * (f - 1.0f) + begin; } // Bernoulli trial bool Bernoulli(float p) { return UniformF(0, 1) < p; } // State for geometric distributions. // The stored value is inv_log_1mp using GeometricDistribution = float; static GeometricDistribution MakeGeometric(float p) { return 1.0 / std::log(1 - p); } uint32_t Geometric(const GeometricDistribution& dist) { float f = UniformF(0, 1); float inv_log_1mp = dist; float log = std::log(1 - f) * inv_log_1mp; return static_cast(log); } template void Shuffle(T* t, size_t n) { for (size_t i = 0; i + 1 < n; i++) { size_t a = UniformU(i, n); std::swap(t[a], t[i]); } } private: uint64_t s[2]; }; } // namespace jxl #endif // LIB_JXL_BASE_RANDOM_ libjxl-0.11.1/lib/jxl/base/rational_polynomial-inl.h000066400000000000000000000076011472134335300223510ustar00rootroot00000000000000// 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. // Fast SIMD evaluation of rational polynomials for approximating functions. #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_BASE_RATIONAL_POLYNOMIAL_INL_H_ #endif #include #include #include HWY_BEFORE_NAMESPACE(); namespace jxl { namespace HWY_NAMESPACE { namespace { // These templates are not found via ADL. using hwy::HWY_NAMESPACE::Div; using hwy::HWY_NAMESPACE::MulAdd; // 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))); static_assert(kDegP < 8, "Polynomial degree is too high"); static_assert(kDegQ < 8, "Polynomial degree is too high"); return FastDivision()(yp, yq); } } // namespace // NOLINTNEXTLINE(google-readability-namespace-comments) } // namespace HWY_NAMESPACE } // namespace jxl HWY_AFTER_NAMESPACE(); #endif // LIB_JXL_BASE_RATIONAL_POLYNOMIAL_INL_H_ libjxl-0.11.1/lib/jxl/base/rect.h000066400000000000000000000141111472134335300164440ustar00rootroot00000000000000// 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