pax_global_header00006660000000000000000000000064152750472420014522gustar00rootroot0000000000000052 comment=5a7da83447212fe4900fc29ce97430b881caa67e libjxl-libjxl-5a7da83/000077500000000000000000000000001527504724200147065ustar00rootroot00000000000000libjxl-libjxl-5a7da83/.bazelignore000066400000000000000000000000141527504724200172030ustar00rootroot00000000000000third_party libjxl-libjxl-5a7da83/.clang-format000066400000000000000000000001351527504724200172600ustar00rootroot00000000000000BasedOnStyle: 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-math-missing-parentheses: Yes, we know that a / b + c means # (a / b) + c. # - 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-*, cppcoreguidelines-pro-type-member-init, 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, -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-math-missing-parentheses, -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-libjxl-5a7da83/.clangd000066400000000000000000000000751527504724200161410ustar00rootroot00000000000000Diagnostics: Suppress: readability-math-missing-parentheseslibjxl-libjxl-5a7da83/.devcontainer/000077500000000000000000000000001527504724200174455ustar00rootroot00000000000000libjxl-libjxl-5a7da83/.devcontainer/Dockerfile000066400000000000000000000020141527504724200214340ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project Authors. All rights reserved. # # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. FROM debian:trixie@sha256:6d87375016340817ac2391e670971725a9981cfc24e221c47734681ed0f6c0f5 RUN apt update RUN apt install -y cmake clang doxygen g++ extra-cmake-modules \ libgif-dev libjpeg-dev ninja-build libgoogle-perftools-dev \ graphviz RUN apt install -y qt6-base-dev libwebp-dev libopenexr-dev \ libgtest-dev libgmock-dev libbenchmark-dev libbenchmark-tools RUN apt install -y git WORKDIR $OPT # Get the emsdk repo. RUN git clone https://github.com/emscripten-core/emsdk.git # Enter that directory. WORKDIR $OPT/emsdk # Download and install the latest SDK tools. RUN ./emsdk install latest # Make the "latest" SDK "active" for the current user. (writes ~/.emscripten file) RUN ./emsdk activate latest # Install node RUN apt install -y nodejs RUN echo "NODE_JS='/usr/bin/node'" >> $OPT/emsdk/.emscripten WORKDIR /workspaces/libjxl CMD /bin/bashlibjxl-libjxl-5a7da83/.devcontainer/devcontainer.json000066400000000000000000000016611527504724200230250ustar00rootroot00000000000000// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.112.0/containers/cpp { "name": "C++", "dockerFile": "Dockerfile", // Set *default* container specific settings.json values on container create. "settings": { "terminal.integrated.shell.linux": "/bin/bash" }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ "ms-vscode.cpptools", "twxs.cmake", "flixs.vs-code-http-server-and-html-preview" ] // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "gcc -v", // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. // "remoteUser": "emscripten" } libjxl-libjxl-5a7da83/.github/000077500000000000000000000000001527504724200162465ustar00rootroot00000000000000libjxl-libjxl-5a7da83/.github/ISSUE_TEMPLATE/000077500000000000000000000000001527504724200204315ustar00rootroot00000000000000libjxl-libjxl-5a7da83/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000017251527504724200231300ustar00rootroot00000000000000--- 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-libjxl-5a7da83/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011231527504724200241530ustar00rootroot00000000000000--- 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-libjxl-5a7da83/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000015521527504724200220520ustar00rootroot00000000000000 ### 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-libjxl-5a7da83/.github/dependabot.yml000066400000000000000000000014611527504724200211000ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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" # Combine updates into single PR groups: everything: patterns: - "*" # Everything - package-ecosystem: pip directory: /doc/sphinx schedule: interval: daily libjxl-libjxl-5a7da83/.github/workflows/000077500000000000000000000000001527504724200203035ustar00rootroot00000000000000libjxl-libjxl-5a7da83/.github/workflows/build_test.yml000066400000000000000000000401121527504724200231620ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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 # Track static stack size on build and check it doesn't exceed 3 kB. emit_stack_sizes: 1 max_stack: 2400 # Conformance tooling test requires numpy. apt_pkgs: python3-numpy - name: lowprecision mode: release run_bench: true test_in_pr: true cmake_args: -DCMAKE_CXX_FLAGS=-DJXL_HIGH_PRECISION=0 - name: debug # 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 cc: clang-18 cxx: clang++-18 - name: asan skip_install: true - name: tsan skip_install: true disable_tests: # TODO(eustas): failing after #5058 (libgdk-pixbuf-xlib-2.0-dev) - pixbufloader_test_jxl - name: coverage skip_install: true # 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/test on arm64 CPU - name: release:arm64 os: ubuntu-24.04-arm mode: release run_bench: true test_in_pr: true - name: release:arm64-lowprecision os: ubuntu-24.04-arm mode: release run_bench: true test_in_pr: true cmake_args: -DCMAKE_CXX_FLAGS=-DJXL_HIGH_PRECISION=0 # 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:gcc os: ubuntu-latest mode: release cc: gcc-latest cxx: g++-latest - name: release:gcc-old os: ubuntu-24.04 mode: release cc: gcc-latest cxx: g++-latest # ubuntu-22.04 builds are flaky now #- name: release:gcc-old-old # os: ubuntu-22.04 # mode: release # cc: gcc-latest # cxx: g++-latest # Builds with old clang in release mode - name: release:clang-old os: ubuntu-24.04 mode: release cc: clang cxx: clang++ # ubuntu-22.04 builds are flaky now #- name: release:clang-old-old # os: ubuntu-22.04 # mode: release # cc: clang # cxx: clang++ # LTO builds - name: release_lto os: ubuntu-latest mode: release cc: clang-21 cxx: clang++-21 apt_pkgs: lld cmake_args: >- -DJPEGXL_ENABLE_LTO=ON -DCMAKE_LINKER_TYPE=LLD - name: release_lto:gcc os: ubuntu-latest mode: release cc: gcc-latest cxx: g++-latest cmake_args: >- -DJPEGXL_ENABLE_LTO=ON # TODO(eustas): investigate why 512KiB is required under LTO. test_stack_limit: 512 # OSX builds - name: release:osx os: macos-latest mode: release cmake_args: >- -DCMAKE_FIND_FRAMEWORK=NEVER - name: release:osx-15 os: macos-15 mode: release test_stack_limit: 128 cmake_args: >- -DCMAKE_FIND_FRAMEWORK=NEVER - name: debug:osx-15 # The last available x86_64 OSX runner os: macos-15-intel mode: debug skip_test: true # can't finish "linking" tests cmake_args: >- -DCMAKE_FIND_FRAMEWORK=NEVER # Android builds - name: release_android os: ubuntu-latest mode: release skip_install: true skip_stats: true skip_test: true # can't finish "linking" tests without emulator force_system_brotli: OFF cmake_args: >- -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=28 -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_LATEST_HOME}/build/cmake/android.toolchain.cmake -DJPEGXL_ENABLE_BENCHMARK=OFF -DJPEGXL_ENABLE_OPENEXR=OFF -DJPEGXL_ENABLE_PLUGINS=OFF -DJPEGXL_ENABLE_VIEWERS=OFF -DSJPEG_ANDROID_NDK_PATH=${ANDROID_NDK_LATEST_HOME} -DJPEGXL_BUNDLE_LIBPNG=ON - name: release_ios os: macos-latest mode: release skip_install: true skip_stats: true skip_test: true # can't finish "linking" tests without emulator force_system_brotli: OFF cmake_args: >- -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64 -DJPEGXL_ENABLE_BENCHMARK=OFF -DJPEGXL_ENABLE_OPENEXR=OFF -DJPEGXL_ENABLE_PLUGINS=OFF -DJPEGXL_ENABLE_VIEWERS=OFF -DJPEGXL_BUNDLE_LIBPNG=ON env: CCACHE_DIR: ${{ github.workspace }}/.ccache # Whether we track the stack size. EMIT_STACK_SIZES: ${{ matrix.emit_stack_sizes }} TEST_STACK_LIMIT: ${{ matrix.test_stack_limit }} WILL_TEST: ${{ !matrix.skip_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 || false }} WILL_COV: ${{ github.event_name == 'push' && matrix.name == 'coverage' }} FASTER_MSAN_BUILD: 1 steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Check workflow configuration run: | if [[ "${{ matrix.run_bench && !matrix.test_in_pr }}" == "true" ]]; then echo "::error title::Configuration Error::'run_bench' requires 'test_in_pr'" exit 1 fi - 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 graphviz brew install binutils ccache coreutils googletest libavif ninja openexr sdl2 webp if [[ "${WILL_BENCH}" == "true" ]]; then brew install google-benchmark fi - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 2 persist-credentials: false - name: Install build deps Ubuntu if: startsWith(matrix.os, 'macos-') == false run: | INSTALL_BENCHMARK='' if [[ "${WILL_BENCH}" == "true" ]]; then if [[ "${WILL_TEST}" == "true" ]]; then INSTALL_BENCHMARK=benchmark else exit 1 fi fi sudo ./tools/scripts/install_deps.sh build extras plugins ${INSTALL_BENCHMARK} -- ${{ matrix.apt_pkgs }} - name: Select latest installed GCC if: matrix.cc == 'gcc-latest' run: | LATEST_GCC_VER=$(compgen -c gcc- | grep -E '^gcc-[0-9]+$' | sort -V | tail -n 1 | cut -d- -f2) echo "CC=gcc-${LATEST_GCC_VER}" >> $GITHUB_ENV echo "CXX=g++-${LATEST_GCC_VER}" >> $GITHUB_ENV echo "Selected GCC version: ${LATEST_GCC_VER}" - name: Set CC/CXX if: matrix.cc != 'gcc-latest' run: | echo "CC=${{ matrix.cc || 'clang' }}" >> $GITHUB_ENV echo "CXX=${{ matrix.cxx || 'clang++' }}" >> $GITHUB_ENV - 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: 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@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ env.LLVM_ROOT }} key: llvm - name: Checkout the LLVM source if: matrix.name == 'msan' && env.WILL_BUILD == 'true' uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: false repository: llvm/llvm-project ref: llvmorg-18.1.3 # NB: 15.0.0 does not build ¯\_(ツ)_/¯ path: llvm_root persist-credentials: false - name: Git environment id: git-env run: | echo "parent=$(git rev-parse ${{ github.sha }}^)" >> $GITHUB_OUTPUT shell: bash - name: ccache uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 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 }}" SKIP_TEST=1 TARGETS=all CMAKE_CXX_FLAGS='${{ matrix.cxxflags }}' CMAKE_FLAGS='-DHWY_BROKEN_TARGETS=0' \ ./ci.sh ${MODE} \ -DJPEGXL_FORCE_SYSTEM_BROTLI=${{ matrix.force_system_brotli || 'ON'}} \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DBUILD_TESTING=${BUILD_TESTS} \ -DJPEGXL_ENABLE_HWY_AVX3_DL=false \ ${{ matrix.cmake_args }} - 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' && !matrix.skip_stats 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 # GCC does not know how to refer to shared libraries properly export CC=clang export CXX=clang++ 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: | disabled_tests="${{ join(matrix.disable_tests, '|') }}" if [[ -z "${disabled_tests}" ]]; then disabled_tests="bash_test"; else disabled_tests="bash_test|${disabled_tests}"; fi ./ci.sh test -E "${disabled_tests}" # 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: Install gcovr if: env.WILL_COV == 'true' run: pip install gcovr - 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@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 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.5s libjxl-libjxl-5a7da83/.github/workflows/build_test_bazel.yml000066400000000000000000000034641527504724200243500ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 1 persist-credentials: false - name: Patch run: | cd third_party/highway # TODO(eustas): stick to deps.sh commit git fetch origin 457c891775a7397bdb0376bb1031e6e027af1c48 git checkout 457c891775a7397bdb0376bb1031e6e027af1c48 git apply ${{ github.workspace }}/.github/workflows/highway.patch - name: Build run: USE_BAZEL_VERSION=8.5.1 bazelisk 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: USE_BAZEL_VERSION=8.5.1 bazelisk test -c opt --test_output=errors ...:all libjxl-libjxl-5a7da83/.github/workflows/build_test_cross.yml000066400000000000000000000220251527504724200243760ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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' }} env: SDE_ID: "924984" SDE_VERSION: "10.13.1-2026-07-28" SDE_SHA256: "94e97d623fec54385686e1e7ba65ebc9941748c05ee451423948334892bf2b50" jobs: compile: name: Cross-compiling ${{ matrix.identifier }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: ${{ matrix.os || 'ubuntu-latest' }} strategy: fail-fast: false matrix: identifier: [armhf, i386, x64] include: - arch: armhf os: ubuntu-24.04-arm identifier: armhf test_in_pr: true build_target: arm-linux-gnueabihf - arch: i386 identifier: i386 test_in_pr: true build_target: i686-linux-gnu cflags: -m32 - arch: i686 identifier: x64 test_in_pr: true cmake_args: >- -DJPEGXL_ENABLE_HWY_AVX3=true -DJPEGXL_ENABLE_HWY_AVX3_DL=true -DJPEGXL_ENABLE_HWY_AVX3_SPR=true -DJPEGXL_ENABLE_HWY_AVX3_ZEN4=true env: 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@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 1 persist-credentials: false - name: Install build deps run: | sudo ARCH=${{ matrix.arch }} BUILD_TARGET=${{ matrix.build_target }} \ ./tools/scripts/install_deps.sh ${{ matrix.arch != 'i686' && '--cross' || ''}} build extras - name: Build run: | CC=clang CXX=clang++ \ CFLAGS='${{ matrix.cflags }}' CXXFLAGS='${{ matrix.cflags }}' \ BUILD_DIR=build BUILD_TARGET=${{ matrix.build_target }} \ SKIP_TEST=1 TARGETS='all_tests cjxl djxl hwy_list_targets libjxl.so libjxl_dec.so' \ ./ci.sh release \ -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DJPEGXL_ENABLE_JNI=OFF \ ${{ matrix.cmake_args }} - name: Build stats run: | tools/scripts/build_stats.py --save build/stats.json \ ${{ matrix.build_target && format('--binutils {0}-', 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|json|log|o)' -delete find ./build -type f -executable > executable.lst - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 if: env.WILL_RUN_TESTS == 'true' with: name: cross_binary-${{ matrix.identifier }} path: | build/ executable.lst 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: ${{ matrix.os || 'ubuntu-latest' }} strategy: fail-fast: false matrix: shard_number: [1, 2, 3, 4, 5, 6, 7, 8] identifier: [armhf, i386, x64] include: - arch: armhf os: ubuntu-24.04-arm identifier: armhf test_in_pr: true last_shard: 2 - arch: i386 identifier: i386 test_in_pr: true last_shard: 2 - arch: i686 identifier: x64 test_in_pr: true last_shard: 8 env: UPLOAD_TEST_COST: false # Set to 1 for calculating test cost. LAST_SHARD: ${{ 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@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 1 persist-credentials: false - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 if: (matrix.shard_number < env.LAST_SHARD) && (env.WILL_RUN_TESTS == 'true') with: name: cross_binary-${{ matrix.identifier }} - name: Install build deps if: (matrix.shard_number < env.LAST_SHARD) && (env.WILL_RUN_TESTS == 'true') run: | sudo ARCH=${{ matrix.arch }} \ ./tools/scripts/install_deps.sh ${{ matrix.arch != 'i686' && '--cross' || ''}} extras_runtime runtime - name: Test ranging run: | mkdir -p ./build/Testing/Temporary unzip ./tools/scripts/test_cost-${{ matrix.identifier }}.zip -d ./build/Testing/Temporary - 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: Cache SDE if: ${{ matrix.arch == 'i686' }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ github.workspace }}/sde key: sde-${{ env.SDE_ID }} - name: Download SDE ${{ env.SDE_VERSION }} if: ${{ matrix.arch == 'i686' }} run: | mkdir -p sde cd sde SDE_ARCHIVE=sde.tar.xz if [ ! -f "${SDE_ARCHIVE}" ]; then curl --fail -L --show-error -o "${SDE_ARCHIVE}" \ https://downloadmirror.intel.com/${{ env.SDE_ID }}/sde-external-${{ env.SDE_VERSION }}-lin.tar.xz fi printf '%s %s\n' "${SDE_SHA256}" "${SDE_ARCHIVE}" | sha256sum --check tar --strip-components=1 -xJf "${SDE_ARCHIVE}" cd .. - name: Test if: (matrix.shard_number < env.LAST_SHARD) && (env.WILL_RUN_TESTS == 'true') run: | EMULATOR= if [[ -n '${{ matrix.arch == 'i686' && 'true' || '' }}' ]]; then EMULATOR="sde/sde64 -spr --" fi chmod +x `cat executable.lst` BUILD_DIR=build \ ${EMULATOR} ./ci.sh test \ -I ${{ matrix.shard_number }},,${LAST_SHARD} \ -E '(bash_test|conformance_tooling_test|test_jxl_jni_wrapper)' - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 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 # Compile-only s390x leg that guards #4627. squeeze.cc's big-endian, pre-1.2-Highway # branch is built nowhere else (the other legs use Highway >= 1.2), so this pins to 1.0.7. compile-s390x-old-highway: name: Cross-compiling s390x with Highway 1.0.7 (compile-only) if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: ubuntu-24.04-arm # ports.ubuntu.com carries the s390x cross packages steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 1 persist-credentials: false - name: Pin Highway to 1.0.7 run: | git -C third_party/highway fetch --depth 1 origin tag 1.0.7 git -C third_party/highway checkout 1.0.7 - name: Install build deps run: | sudo ARCH=s390x BUILD_TARGET=s390x-linux-gnu \ ./tools/scripts/install_deps.sh --cross build - name: Build run: | CC=clang CXX=clang++ \ BUILD_DIR=build BUILD_TARGET=s390x-linux-gnu \ SKIP_TEST=1 TARGETS='libjxl.so libjxl_dec.so' \ ./ci.sh release \ -DBUILD_TESTING=OFF \ -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DJPEGXL_ENABLE_JNI=OFF \ -DJPEGXL_ENABLE_TOOLS=OFF \ -DJPEGXL_ENABLE_OPENEXR=OFF \ -DJPEGXL_ENABLE_BENCHMARK=OFF libjxl-libjxl-5a7da83/.github/workflows/build_test_emu.yml000066400000000000000000000127361527504724200240430ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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 in emulator (QEMU). name: Build/Test in emulator on: workflow_dispatch: schedule: - cron: '14 3 * * *' # Daily on 03:14 UTC permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: compile: name: Build under ${{ matrix.platform }} emulator if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: 'ubuntu-latest' strategy: fail-fast: false matrix: include: - identifier: s390x platform: linux/s390x env: CCACHE_DIR: ${{ github.workspace }}/.ccache steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 2 persist-credentials: false - name: Git environment id: git-env run: | echo "parent=$(git rev-parse ${{ github.sha }}^)" >> $GITHUB_OUTPUT shell: bash - name: ccache uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 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: ccache config run: | mkdir -p ${CCACHE_DIR} echo "max_size = 200M" > ${CCACHE_DIR}/ccache.conf - name: Setup qemu run: | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - name: Build run: | docker run --rm --platform ${{ matrix.platform }} \ -v ${{ github.workspace }}:/workspace \ -w /workspace \ debian:trixie \ bash -c "./tools/scripts/install_deps.sh base build -- zlib1g-dev && \ echo 'Number of cores:' `nproc` && \ export CC=clang && \ export CCACHE_DIR=/workspace && \ export CMAKE_FLAGS='-DHWY_BROKEN_TARGETS=0' && \ export CXX=clang++ && \ export SKIP_TEST=1 && \ export TARGETS='all_tests cjxl djxl hwy_list_targets libjxl.so libjxl_dec.so' && \ ./ci.sh release \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DJPEGXL_BUNDLE_LIBPNG=ON \ -DJPEGXL_ENABLE_BENCHMARK=OFF \ -DJPEGXL_ENABLE_OPENEXR=OFF \ -DJPEGXL_ENABLE_PLUGINS=OFF \ -DJPEGXL_ENABLE_VIEWERS=OFF && \ find ./build -regextype egrep -type f -regex '.*\.(a|h|jar|json|log|o)' -delete && \ find ./build -type f -executable > executable.lst && \ echo 'Finished' \ " - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: emu_binary-${{ matrix.identifier }} path: | build/ executable.lst 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' strategy: fail-fast: false matrix: shard_number: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] include: - identifier: s390x platform: linux/s390x disable_tests: - bash_test - conformance_tooling_test - example - example64 - JxlTest.RoundtripLargeEmptyModular - test_jpegli_jni_wrapper - test_jxl_jni_wrapper steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 1 persist-credentials: false - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: emu_binary-${{ matrix.identifier }} - name: Setup qemu run: | docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - name: Test run: | chmod +x `cat executable.lst` docker run --rm --platform ${{ matrix.platform }} \ -v ${{ github.workspace }}:/workspace \ -w /workspace \ debian:trixie \ bash -c "./tools/scripts/install_deps.sh base runtime && \ export BUILD_DIR=build && \ ./ci.sh test \ -I ${{ matrix.shard_number }},,10 \ -E '${{ join(matrix.disable_tests, '|') }}' \ " libjxl-libjxl-5a7da83/.github/workflows/build_test_md.yml000066400000000000000000000034551527504724200236530ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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: fail-fast: false 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@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 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: fail-fast: false matrix: include: - msystem: clang64 # - msystem: clang32 runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - run: 'echo "markdown only changes: no build required"' libjxl-libjxl-5a7da83/.github/workflows/build_test_msys2.yml000066400000000000000000000071001527504724200243170ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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: ${{ matrix.os || 'windows-latest' }} continue-on-error: ${{ matrix.faulty || false }} strategy: fail-fast: false matrix: include: - msystem: mingw64 - msystem: clang64 # "Legacy" toolchains are being "phased-out": # https://www.msys2.org/news/#2023-12-13-starting-to-drop-some-32-bit-packages # clang32/mingw32 are already missing required "gtest" - msystem: clangarm64 os: windows-11-arm defaults: run: shell: msys2 {0} steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 1 persist-credentials: false - 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 ${{ matrix.msystem != 'mingw32' && '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: | disabled_tests="${{ join(matrix.disable_tests, '|') }}" if [[ -z "${disabled_tests}" ]]; then disabled_tests="bash_test"; else disabled_tests="bash_test|${disabled_tests}"; fi ctest --test-dir build --parallel 2 --output-on-failure -E "${disabled_tests}" - name: Fast benchmark ${{ matrix.msystem }} if: github.event_name != 'merge_group' && !(matrix.disable_benchmark || false) run: | systeminfo | grep -A 1 Processor powershell -c "Get-CimInstance -ClassName Win32_Processor | Select-Object 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-libjxl-5a7da83/.github/workflows/build_test_wasm.yml000066400000000000000000000076671527504724200242330ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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 # As of 28.08.2025 ubuntu-latest is 24.04; it is shipped with node 22.18 NODE_VERSION: 22 strategy: fail-fast: false matrix: include: - variant: scalar - variant: simd-128 - variant: simd-256 steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 1 persist-credentials: false - name: Install build deps run: | sudo ./tools/scripts/install_deps.sh build - name: Git environment id: git-env run: | echo "parent=$(git rev-parse ${{ github.sha }}^)" >> $GITHUB_OUTPUT shell: bash - name: ccache uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 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@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.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@4528d102f7230f0e7b276855c01ea1159be0e984 # v16 # 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 SKIP_TEST=1 TARGETS=all \ ./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 - 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 -E 'bash_test' || \ ./ci.sh test -E 'bash_test' --rerun-failed libjxl-libjxl-5a7da83/.github/workflows/codeql.yml000066400000000000000000000057631527504724200223100ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 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: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 2 persist-credentials: false - name: Install build deps run: | sudo ./tools/scripts/install_deps.sh build extras plugins - name: Build run: | CC=clang CXX=clang++ \ SKIP_TEST=1 TARGETS=all \ ./ci.sh opt \ -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DBUILD_TESTING=OFF - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 with: category: "/language:${{matrix.language}}" libjxl-libjxl-5a7da83/.github/workflows/conformance.yml000066400000000000000000000247731527504724200233350ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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: 4bf053529c7cefd2951be453475bb3dccc7e7be8 LIBJXL_VERSION: "0.13.0" LIBJXL_ABI_VERSION: "0.13" SDE_ID: "924984" SDE_VERSION: "10.13.1-2026-07-28" SDE_SHA256: "94e97d623fec54385686e1e7ba65ebc9941748c05ee451423948334892bf2b50" 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@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout the conformance source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: libjxl/conformance ref: ${{ env.CONFORMANCE_REPO_HASH }} path: conformance persist-credentials: false - name: Cache uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 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: CfB ${{ matrix.name }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: ${{ matrix.os || 'ubuntu-latest' }} strategy: fail-fast: false matrix: include: - name: AVX3 cflags: -DHWY_DISABLED_TARGETS=HWY_AVX3-1 cmake_args: -DJPEGXL_ENABLE_HWY_AVX3=ON - 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 - name: SVE2_128 os: ubuntu-24.04-arm cflags: -DHWY_DISABLED_TARGETS=HWY_SVE2_128-1 cc: gcc-latest cxx: g++-latest - name: SVE2 os: ubuntu-24.04-arm cflags: -DHWY_DISABLED_TARGETS=HWY_SVE2-1 cc: gcc-latest cxx: g++-latest - name: SVE os: ubuntu-24.04-arm cflags: -DHWY_DISABLED_TARGETS=HWY_SVE-1 cc: gcc-latest cxx: g++-latest - name: NEON_WITHOUT_AES os: ubuntu-24.04-arm cflags: -DHWY_DISABLED_TARGETS=HWY_NEON_WITHOUT_AES-1 env: CCACHE_DIR: ${{ github.workspace }}/.ccache steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout the jxl source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 2 persist-credentials: false - name: Install build deps run: | sudo ./tools/scripts/install_deps.sh build - name: Select latest installed GCC if: matrix.cc == 'gcc-latest' run: | LATEST_GCC_VER=$(compgen -c gcc- | grep -E '^gcc-[0-9]+$' | sort -V | tail -n 1 | cut -d- -f2) echo "CC=gcc-${LATEST_GCC_VER}" >> $GITHUB_ENV echo "CXX=g++-${LATEST_GCC_VER}" >> $GITHUB_ENV echo "Selected GCC version: ${LATEST_GCC_VER}" - name: Set CC/CXX if: matrix.cc != 'gcc-latest' run: | echo "CC=${{ matrix.cc || 'clang' }}" >> $GITHUB_ENV echo "CXX=${{ matrix.cxx || 'clang++' }}" >> $GITHUB_ENV - name: Git environment id: git-env run: | echo "parent=$(git rev-parse ${{ github.sha }}^)" >> $GITHUB_OUTPUT shell: bash - name: ccache uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 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 }} -DHWY_BROKEN_TARGETS=0" \ SKIP_TEST=1 TARGETS="hwy_list_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 \ ${{ matrix.cmake_args }} # 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 - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 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 test: name: CfT ${{ matrix.name }} / ${{ matrix.target }} if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} needs: [warmup, build] runs-on: ${{ (startsWith(matrix.target, 'SVE') || startsWith(matrix.target, 'NEON')) && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} strategy: fail-fast: false matrix: name: [level5, level10] target: [AVX3, AVX2, SSE4, SSSE3, EMU128, SCALAR, SCALAR_ASAN, SVE2_128, SVE2, SVE, NEON_WITHOUT_AES] steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout install_deps.sh uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: sparse-checkout: tools/scripts/install_deps.sh sparse-checkout-cone-mode: false fetch-depth: 1 persist-credentials: false - name: Install deps run: | sudo ./tools/scripts/install_deps.sh conformance - name: Cache SDE if: ${{ matrix.target == 'AVX3' }} uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ github.workspace }}/sde key: sde-${{ env.SDE_ID }} - name: Download SDE ${{ env.SDE_VERSION }} if: ${{ matrix.target == 'AVX3' }} run: | mkdir -p sde cd sde SDE_ARCHIVE=sde.tar.xz if [ ! -f "${SDE_ARCHIVE}" ]; then curl --fail -L --show-error -o "${SDE_ARCHIVE}" \ https://downloadmirror.intel.com/${{ env.SDE_ID }}/sde-external-${{ env.SDE_VERSION }}-lin.tar.xz fi printf '%s %s\n' "${SDE_SHA256}" "${SDE_ARCHIVE}" | sha256sum --check tar --strip-components=1 -xJf "${SDE_ARCHIVE}" cd .. - name: Checkout the conformance source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: libjxl/conformance ref: ${{ env.CONFORMANCE_REPO_HASH }} path: conformance persist-credentials: false - name: Cache uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 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@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: conformance_binary-${{ matrix.target }} - name: Run conformance tests run: | HERE=`pwd` export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${HERE} TARGET=${{ matrix.target }} 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 }} EMULATOR= if [[ $TARGET == AVX3 ]]; then EMULATOR="sde/sde64 -spr --" elif [[ $TARGET == SVE_256 ]]; then EMULATOR="qemu-aarch64 -cpu max,sve-default-vector-length=32" elif [[ $TARGET == SVE* ]]; then EMULATOR="qemu-aarch64 -cpu max,sve-default-vector-length=16" fi ${EMULATOR} ${HERE}/djxl -V | tee djxl_version.txt if [[ $TARGET == *_ASAN ]]; then grep -q ' ASAN ' djxl_version.txt || exit 1 TARGET=${TARGET%_ASAN} fi # Old HWY versions does not report selected target SELECTED=_ grep -E "(\[|,)_.*_(,|\])" djxl_version.txt || SELECTED= grep -q ${SELECTED}${TARGET}${SELECTED} djxl_version.txt || exit 1 python conformance.py \ --decoder="${EMULATOR} ${HERE}/djxl" \ --corpus=${HERE}/conformance/testcases/main_${{ matrix.name }}.txt libjxl-libjxl-5a7da83/.github/workflows/dependency-review.yml000066400000000000000000000026211527504724200244440ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: 'Checkout Repository' uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: 'Dependency Review' uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 libjxl-libjxl-5a7da83/.github/workflows/fuzz.yml000066400000000000000000000042101527504724200220210ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 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 persist-credentials: false - name: Build Fuzzers id: build uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@3e6a7fd7bcd631647ab9beed1fe0897498e6af39 # 22.09.2025 with: oss-fuzz-project-name: 'libjxl' language: c++ - name: Run Fuzzers uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@3e6a7fd7bcd631647ab9beed1fe0897498e6af39 # 22.09.2025 with: oss-fuzz-project-name: 'libjxl' language: c++ fuzz-seconds: 600 - name: Upload Crash uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 if: failure() && steps.build.outcome == 'success' with: name: artifacts path: ./out/artifacts libjxl-libjxl-5a7da83/.github/workflows/gitlab_mirror.yml000066400000000000000000000025551527504724200236710ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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 mirroring the github libjxl repo to the wg1 gitlab repo name: Mirror to GitLab on: push: branches: - main - v*.*.x env: BRANCH_NAME: ${{ github.head_ref || github.ref_name }} permissions: contents: read jobs: mirror: if: github.repository_owner == 'libjxl' runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 # Disable shallow clone persist-credentials: false - 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-libjxl-5a7da83/.github/workflows/highway.patch000066400000000000000000000020431527504724200227630ustar00rootroot00000000000000diff --git a/BUILD b/BUILD index c825050b..6e6bc604 100644 --- a/BUILD +++ b/BUILD @@ -171,6 +171,7 @@ cc_library( "hwy/detect_compiler_arch.h", # private "hwy/print.h", ], + includes = ["."], compatible_with = [], copts = COPTS, defines = DEFINES, diff --git a/MODULE.bazel b/MODULE.bazel index bd798803..5c767b53 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,6 +1,11 @@ -module(name = "highway", version = "1.2.0") +module( + name = "highway", + version = "1.2.0", + compatibility_level = 1, +) -bazel_dep(name = "bazel_skylib", version = "1.3.0") -bazel_dep(name = "googletest", version = "1.12.1") -bazel_dep(name = "rules_cc", version = "0.0.4") -bazel_dep(name = "rules_license", version = "0.0.4") +bazel_dep(name = "bazel_skylib", version = "1.6.1") +bazel_dep(name = "googletest", version = "1.15.2", repo_name = "com_google_googletest") +bazel_dep(name = "rules_cc", version = "0.0.9") +bazel_dep(name = "rules_license", version = "0.0.7") +bazel_dep(name = "platforms", version = "0.0.10") libjxl-libjxl-5a7da83/.github/workflows/pages.yml000066400000000000000000000051231527504724200221260ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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: pull_request: types: [opened, reopened, labeled, unlabeled, synchronize] # 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: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: # Single deploy job since we're just deploying deploy: environment: name: ${{ github.event_name != 'pull_request' && 'github-pages' || '' }} url: ${{ github.event_name != 'pull_request' && steps.deployment.outputs.page_url || '' }} runs-on: ubuntu-latest steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 2 persist-credentials: false - name: Install build deps run: | sudo ./tools/scripts/install_deps.sh docs - name: Sphinx dependencies run: | pip install -r doc/sphinx/requirements.txt - name: Build run: | cmake -B build doc cmake --build build -- rtd-html - name: Setup Pages if: ${{ github.event_name != 'pull_request' }} uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 - name: Upload artifact if: ${{ github.event_name != 'pull_request' }} uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 with: # Upload entire repository path: './build/rtd' - name: Deploy to GitHub Pages if: ${{ github.event_name != 'pull_request' }} id: deployment uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1 libjxl-libjxl-5a7da83/.github/workflows/pull_request.yml000066400000000000000000000045221527504724200235550ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - 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@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Install tools run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" brew install buildifier typos-cli zizmor - 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@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: bash_test run: | ./bash_test.sh libjxl-libjxl-5a7da83/.github/workflows/release.yaml000066400000000000000000000350721527504724200226160ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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 / upload 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: read concurrency: group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: ubuntu_static: name: linux ${{ matrix.arch }} static if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - arch: x86_64 os: ubuntu-latest - arch: aarch64 os: ubuntu-26.04-arm env: RELEASE_ARCH: ${{ matrix.arch }} steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 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-pixbuf-xlib-2.0-dev \ libgif-dev \ libgtest-dev \ libgtk2.0-dev \ libjpeg-dev \ libopenexr-dev \ libpng-dev \ libwebp-dev \ ninja-build \ pkg-config \ tarlz \ xdg-utils \ # echo "CC=clang" >> $GITHUB_ENV echo "CXX=clang++" >> $GITHUB_ENV - name: Verify native architecture run: test "$(uname -m)" = "${RELEASE_ARCH}" - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 1 persist-credentials: false - name: Build run: | SKIP_TEST=1 \ ./ci.sh release \ -DJPEGXL_DEP_LICENSE_DIR=/usr/share/doc \ -DJPEGXL_STATIC=ON \ -DBUILD_TESTING=OFF \ -DJPEGXL_ENABLE_VIEWERS=OFF \ -DJPEGXL_ENABLE_PLUGINS=OFF \ -DJPEGXL_ENABLE_OPENEXR=OFF \ -DJPEGXL_ENABLE_DEVTOOLS=ON \ - name: Package release tarball run: | ROOT=`pwd` cd build tarlz -9cvf "${ROOT}/jxl-linux-${RELEASE_ARCH}-static.tar.lz" \ LICENSE* tools/{cjxl,djxl,benchmark_xl,jxlinfo,butteraugli_main,ssimulacra2} - name: Upload release tarball uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: jxl-linux-${{ matrix.arch }}-static path: jxl-linux-${{ matrix.arch }}-static.tar.lz archive: false # Build .deb packages Ubuntu/Debian release_ubuntu_pkg: name: .deb / ${{ 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 - debian:bookworm - debian:trixie - debian:sid container: image: ${{ matrix.os }} steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Install build deps run: | rm -f /var/lib/man-db/auto-update apt update DEBIAN_FRONTEND=noninteractive apt install -y \ build-essential \ devscripts \ python3 \ sed \ # - name: Calculate artifact suffix shell: python3 {0} run: | import os artifact_suffix="${{ matrix.os}}".replace(':', '-') open(os.getenv("GITHUB_ENV"), "a").write(f"ARTIFACT_SUFFIX={artifact_suffix}\n") - 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 1 persist-credentials: false - 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 . EXCLUDE_DEBIAN_PACKAGES="${{ matrix.exclude_debian_packages || '' }}" ./ci.sh debian_build jpeg-xl - name: Stats run: | ./ci.sh debian_stats - name: Package release tarball run: | ROOT=`pwd` cd build/debs/ tar -cvf ${ROOT}/jxl-debs-amd64-${ARTIFACT_SUFFIX}.tar *jxl*.* - name: Upload release tarball uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: jxl-debs-amd64-${{ env.ARTIFACT_SUFFIX }} path: jxl-debs-amd64-${{ env.ARTIFACT_SUFFIX }}.tar archive: false windows_build: name: vcpkg / ${{ matrix.triplet }} ${{ matrix.build_type || 'Release'}} if: ${{ !contains(github.event.pull_request.labels.*.name, 'CI:none') }} runs-on: ${{ matrix.runner || 'windows-latest' }} strategy: fail-fast: false matrix: include: - triplet: x86-windows-static arch: '-A Win32' - triplet: x64-windows-static arch: '-A x64' run_benchmark: true - triplet: x64-windows-static arch: '-A x64' build_type: Debug upload: 'OFF' - triplet: x86-windows arch: '-A Win32' static: 'OFF' - triplet: x64-windows arch: '-A x64' static: 'OFF' # ARM64 support - triplet: arm64-windows-static arch: '-A ARM64' runner: windows-11-arm # ARM64 support - triplet: arm64-windows arch: '-A ARM64' runner: windows-11-arm static: 'OFF' env: VCPKG_VERSION: '2025.10.17' VCPKG_ROOT: vcpkg VCPKG_DISABLE_METRICS: 1 BUILD_TYPE: ${{ matrix.build_type || 'Release' }} PREFIX: ${{matrix.triplet}} steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 2 persist-credentials: false - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 if: github.event_name != 'release' id: cache-vcpkg with: path: vcpkg key: release-${{ runner.os }}-vcpkg-${{ env.VCPKG_VERSION }}-${{ matrix.triplet }} # TODO(eustas): remove when https://github.com/zizmorcore/zizmor/issues/2071 is fixed lookup-only: ${{ startsWith(github.ref, 'refs/tags/') }} - name: Download vcpkg if: github.event_name == 'release' || 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: github.event_name == 'release' || 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 }} -T ClangCL \ -DJPEGXL_STATIC=${{ matrix.static || 'ON'}} \ -DJPEGXL_ENABLE_TOOLS='ON' \ -DBUILD_TESTING=OFF \ -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ -DCMAKE_INSTALL_PREFIX=`pwd`/${PREFIX} \ -DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake \ -DJPEGXL_ENABLE_OPENEXR=OFF \ -DJPEGXL_ENABLE_PLUGINS=OFF \ -DJPEGXL_ENABLE_TCMALLOC=OFF \ -DJPEGXL_ENABLE_VIEWERS=OFF \ -DJPEGXL_ENABLE_DEVTOOLS='ON' \ -DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} \ -DX_VCPKG_APPLOCAL_DEPS_INSTALL=${{ matrix.static == 'OFF' && 'ON' || 'OFF' }} \ # - name: Build shell: 'bash' run: | set -x cmake --build build --config ${BUILD_TYPE} - name: Install shell: 'bash' run: | set -x cmake --build build --config ${BUILD_TYPE} --target install mkdir ${PREFIX}/licenses for pkg in giflib libjpeg-turbo libpng libwebp zlib; do cp vcpkg/installed/${{matrix.triplet}}/share/${pkg}/copyright \ ${PREFIX}/licenses/LICENSE.${pkg} done cp third_party/sjpeg/COPYING ${PREFIX}/licenses/LICENSE.sjpeg cp third_party/skcms/LICENSE ${PREFIX}/licenses/LICENSE.skcms cp third_party/highway/LICENSE ${PREFIX}/licenses/LICENSE.highway cp third_party/brotli/LICENSE ${PREFIX}/licenses/LICENSE.brotli cp LICENSE ${PREFIX}/licenses/LICENSE.libjxl - name: Fast benchmark ${{ matrix.triplet }} shell: 'bash' if: matrix.run_benchmark && true run: | systeminfo | grep -A 1 Processor powershell -c "Get-CimInstance -ClassName Win32_Processor | Select-Object 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: Package release shell: 'bash' if: matrix.upload != 'OFF' run: | 7z -mx=9 -mfb=273 a jxl-${{matrix.triplet}}.7z ${PREFIX}/bin ${PREFIX}/include ${PREFIX}/lib ${PREFIX}/licenses 7z -mm=Deflate64 -mfb=257 -mpass=15 -mtp=2 a jxl-${{matrix.triplet}}.zip ${PREFIX}/bin ${PREFIX}/include ${PREFIX}/lib ${PREFIX}/licenses - name: Upload ZIP release if: matrix.upload != 'OFF' uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: jxl-${{matrix.triplet}} path: | jxl-${{matrix.triplet}}.zip archive: false - name: Upload 7Z release if: matrix.upload != 'OFF' uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: jxl-${{matrix.triplet}}-7z path: | jxl-${{matrix.triplet}}.7z archive: false publish_release_assets: name: Publish release assets needs: [ubuntu_static, release_ubuntu_pkg, windows_build] if: github.event_name == 'release' runs-on: [ubuntu-latest] permissions: contents: write id-token: write attestations: write steps: - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 1 persist-credentials: false - name: Download all artifacts uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: path: release_assets skip-decompress: true merge-multiple: true - name: Attest release assets uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: subject-path: release_assets/* - name: Publish assets env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} run: | gh release upload ${GITHUB_EVENT_RELEASE_TAG_NAME} ./release_assets/* # Keep in sync with `publish_release_assets` workflow. dry_publish_release_assets: name: Publish release assets (dry run) needs: [ubuntu_static, release_ubuntu_pkg, windows_build] if: github.event_name != 'release' runs-on: [ubuntu-latest] steps: - name: Checkout the source uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: submodules: true fetch-depth: 1 persist-credentials: false - name: Download all artifacts uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: path: release_assets skip-decompress: true merge-multiple: true libjxl-libjxl-5a7da83/.github/workflows/scorecard.yml000066400000000000000000000056341527504724200230030ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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' # Daily on 02:37 UTC 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@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: "Checkout code" uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: "Run analysis" uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.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@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 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@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1 with: sarif_file: results.sarif libjxl-libjxl-5a7da83/.github/workflows/test_new_highway.yml000066400000000000000000000044041527504724200244000ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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: fail-fast: false 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' - branch: 'test_highway_1.3.0' tag: '1.3.0' steps: - name: Harden Runner uses: step-security/harden-runner@46b06cea07f9e64621fdf066ba77ac6915163d43 # v2.22.3 with: egress-policy: audit - name: 'Cloning libjxl' uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 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 -C third_party/highway rev-parse HEAD` 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 env: # NB: token expires on 15 August every year. TOKEN: ${{ secrets.PUSH_NEW_HWY_TOKEN }} run: | git push https://oauth2:${TOKEN%$'\n'}@github.com/libjxl/libjxl.git HEAD:refs/heads/${{ matrix.branch }} --force libjxl-libjxl-5a7da83/.gitignore000066400000000000000000000006631527504724200167030ustar00rootroot00000000000000# 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__ # Editor configuration *.code-workspace # macOS Finder state files .DS_Store libjxl-libjxl-5a7da83/.gitmodules000066400000000000000000000020251527504724200170620ustar00rootroot00000000000000[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://github.com/google/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/pnggroup/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-libjxl-5a7da83/.pre-commit-config.yaml000066400000000000000000000012511527504724200211660ustar00rootroot00000000000000repos: - 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-libjxl-5a7da83/.readthedocs.yaml000066400000000000000000000007741527504724200201450ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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-26.04 tools: python: "3" apt_packages: - doxygen - graphviz python: install: - requirements: doc/sphinx/requirements.txt libjxl-libjxl-5a7da83/AUTHORS000066400000000000000000000100741527504724200157600ustar00rootroot00000000000000# 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 Amiralgaby Anantha Narayanan R Andrew Kaster Andrius Lukas Narbutas Aous Naman Aras PranckeviÄ�ius Artem Selishchev Aryan Pingle Biswapriyo Nath Cacodemon345 CanadianBaconBoi CrushedAsian255 <155333241+CrushedAsian255@users.noreply.github.com> Damiano Albani Damon Townsend Daniel Novomeský David Burnett dependabot[bot] Diego Pino Dirk Lemstra Dmitry Baryshev Don Olmstead Dong Xu Emre Akyüz estrogently <41487185+estrogently@users.noreply.github.com> Even Rouault Fred Brennan Galaxy4594 <164440799+Galaxy4594@users.noreply.github.com> Gerhard Huber gi-man Gilles Devillers (GilDev) Giuseppe Francione Heiko Becker Ivan Kokorev Ivan Siutsou (Melirius) Jim Robinson John Platts Jonathan Brown (Jonnyawsom3) Joshua Root Kai Hollberg Kerry Su Khem Raj Kleis Auke Wolthuizen L. E. Segovia ledoge Leo Izen Linus VandeVondele LongYinan Lovell Fuller Lucas Chollet Maarten DB Marcin Konicki Martin Strunz Máté Makai Mathieu Malaterre Mikk Leini Misaki Kasumi Moonchild Straver Nicholas Hayes <0xC0000054@users.noreply.github.com> Nigel Tao oupson Petr Diblík Pieter Wuille Pepper Gray roland-rollo Samuel Leong Samuele Benassai Sandro sandstrom Sergey Fedorov Stephan T. Lavavej StepSecurity Bot Stoppedpuma <58333920+Stoppedpuma@users.noreply.github.com> Sylvestre Ledru Thomas Bonfort Timo Rothenpieler tmkk Vincent Torri Wonwoo Choi xiota Yonatan Nebenzhal yosh Ziemowit Zabawa æº�文雨 <41315874+fumiama@users.noreply.github.com> libjxl-libjxl-5a7da83/BUILD.bazel000066400000000000000000000013421527504724200165640ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/BUILDING.md000066400000000000000000000064001527504724200164250ustar00rootroot00000000000000# 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. ### ARM64 GCC 16.1 build workaround Building libjxl 0.12 on ARM64 (aarch64) with GCC 16.1 and Highway 1.4 can trigger an internal compiler error in `N_SVE2_128::DCFromLowestFrequencies`. If you encounter this error, add `-DJPEGXL_ENABLE_HWY_SVE2_128=false` to the CMake configuration command above: ```bash cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF \ -DJPEGXL_ENABLE_HWY_SVE2_128=false .. ``` This disables the Highway SVE2_128 target while leaving other enabled targets available. See [issue #4907](https://github.com/libjxl/libjxl/issues/4907) for the reported compiler failure and confirmation of this workaround. ## Installing ```bash sudo cmake --install . ``` ### Update library cache If running a executable give you "error while loading shared libraries: libjxl_threads.so.0.12", update the library cache: ```bash sudo ldconfig ``` ## 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-libjxl-5a7da83/BUILDING_Haiku.md000066400000000000000000000014211527504724200175440ustar00rootroot00000000000000## 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-libjxl-5a7da83/BUILDING_OSX.md000066400000000000000000000022441527504724200171600ustar00rootroot00000000000000## 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-libjxl-5a7da83/CHANGELOG.md000066400000000000000000001031041527504724200165160ustar00rootroot00000000000000# 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.13.0] - 2026-11-11 ### Security - bumped brotli to v1.2.1 (#5062) to mitigate CVE-2026-111497 - sized low-memory render pipeline ring buffers by simulating the actual stage schedule, fixing use-of-uninitialized-memory bugs with vertically subsampled stages (#5022) - reset JPEG reconstruction state, fixing potential use-after-free writes across decoder resets (#5029) - security and hardening fixes across the core decoder/encoder, JPEG reconstruction, splines, and codec plugins (EXR, APNG, PGX, PNM); this includes fixes for integer overflows in JPEG coefficient buffer allocations and spline area budgets, out-of-bounds read/write guards in chunked frame adapters and truncated EXR scanline streams, bounds-checked `Span::subspan` slicing, and null pointer guards (#4936, #4938, #5009, #5022, #5023, #5026, #5029, #5030, #5076, #5091, #5096, #5100) - decoder: more consistent image_out cleanup; reported-by: Alejandro Pacheco (@alepacheco) and Eunji Ham - harden patch rect calculations; reported-by: Robert McConnell and freezagent - make another decoding path aware of overflows (see #4808); reported-by: Robert McConnell - decoder: guard against using wrong out buffer; reported-by: Lakshit Verma - ensure CmsStage when converting CMYK has access to actual black channel; reported-by: Marinko Ružić - fix using wrong subsampling when reconstructing gray JPEGs; reported-by: synawk dsx, ptrstr and Lakshit Verma ### Added - encoder API: added `JXL_ENC_FRAME_SETTING_STRIP_ALPHA` frame setting and `--strip_alpha` CLI flag to control stripping of alpha channels on single-frame images with a single extra channel (#4981, #4984) - encoder API: added `JXL_ENC_FRAME_SETTING_JPEG_RECON_LFS` frame setting and `--jpeg_reconstruction_lfs` CLI flag to control LF (adaptive DC) smoothing during lossless JPEG recompression; allows non-444 transcoded JPEGs with adaptive DC/LF smoothing to decode (#4932, #5031) - release & CI: added native Linux AArch64 static release artifacts (#5039), Windows ARM64 CI build and release support (#4997), and GitHub artifact attestation for published release assets (#4982) ### Changed - rewrote LZ77 implementation for lossless image compression (#4928) - compression density & encoder improvements: - enabled LF smoothing by default for 4:4:4 lossless JPEG transcoding (#4932) - re-enabled squeezed delta palette (#4930) - signaled the default 39-entry block context map, saving 22-23 bytes per frame when using the default block context map (#5001) - improved histogram clustering with forced prefix codes (force_huffman) by accounting for merges that turn a zero-cost single-symbol histogram into a multi-symbol histogram (#5002) - modular encoder: skipped `EstimateWPCost` weighted-predictor mode search when a custom fixed tree is provided (#5003) - performance & memory: - optimized `EstimateWPCost` (#5093, #5120) - allocated `allowed_counts` on the heap during initialization in to reduce stack usage (#4918) - tools: `jxl_from_tree` now automatically uses 16-bit buffers when bit depth is 12 or less (#4976) ### Fixed - disabled adaptive DC smoothing during lossless JPEG reconstruction (#4931) - cleared all patch dictionary state, preventing patches from being blended using a previous frame's blend modes (#4989) - forgot extra channel output buffers in `JxlDecoderSkipCurrentFrame()` (#5026) - reset `prog_detail` on `JxlDecoderReset()` and rewind (#5121) - copied ANS lookup data from the containing entry in `InitAliasDistribution` and added compile-time size verification (#4952, #5069) - guarded PerformBlending against empty segments (#4992, #5068) - fixed `Forward`/`Meta` and `Inverse` squeeze transform agreement on hshift/vshift updates (#5116) - fixed luminance calculation and multiplicative factor in photon noise synthesis (`enc_photon_noise`) (#5053) - scaled `center_x` and `center_y` coordinates by `dc_scale` for LF frames when encoding with center-first group ordering (#4969) - handled legacy non-conforming Display P3 v4 ICC profiles (#4965) - fixed possible stream inconsistency in streaming encoding mode (#5115) - fixed formal data races in `enc_cache.cc` and modular `FillSigma` initialization (#5102, #5109) - validated packed frame extra-channel counts before encoding (#5075) - PNM/PAM decoder: rejected sample values exceeding `MaxVal` (#5064) - PNG/APNG decoder: zeroed rows left undecoded by truncated PNGs (#5083) - Gain Map API: avoided calling memcpy with a nullptr source (#5089) - NumPy encoder: properly escaped frame names per RFC 8259 in `--metadata_out` JSON output (#5088) - extras: derived file extensions from the final path component only in `CodecFromPath`. (#5081) - build & platform fixes: - fixed build with Highway 1.3+ (#4906) - dropped `-lm` from `libjxl_cms.pc` and `libjxl_threads.pc` pkg-config files on MSVC (#5061) - fixed MSVC intrinsics include order so `_sub_overflow_i32` is visible on x86/x64 (#5071) - enabled `JXL_UNUSED` macro under clang-cl (#5122) - checked whether `Qt` has feature xcb before enabling xcb in tools/ CMake build (#5019) - fixed mingw32 build (#4940) and sequentialized CMake test discovery to avoid build races on macOS/multi-core machines (#4939) ## [0.12.0] - 2026-07-01 ### Added - A new buffering flag is now available in the CLI, alongside support for streaming input with buffered output. (#4634) - Introduced the new `jxltran` tool, which supports extracting codestreams from `jxlp` boxes and packing raw codestreams into `jxlc` boxes. (#4161, #4165, #4168, #4196) - Added the `--reconstruct_jpeg` flag to `djxl` to losslessly reconstruct a JPEG and fail if impossible (mutually exclusive with `--pixels_to_jpeg` and `--jpeg_quality`). (#4498) - Added `JXL_DEC_UNSUPPORTED` (`kUnsupported`) status code to the core API to better indicate when requested features or operations are not supported by the decoder. (#4390) - tools: added support for `ProPhoto` (#4824) and `Adobe98` / `AdobeRGB` (#4199) color space names in CLI arguments (`cjxl` / `djxl`). - decoder API: support for out-of-order `jxlp` boxes (ftyp minor version 1). (#4741) - encoder API: `JXL_ENC_FRAME_SETTING_OUTPUT_MODE` frame setting to control how the codestream is written to the output. Mode 0 (default) buffers the output internally and produces a normally ordered, progressively decodable codestream. Mode 1 uses seek-based streaming (reduces peak memory for large images, requires a seekable output stream). Mode 2 uses out-of-order `jxlp` boxes (reduces peak memory without requiring seeking, but requires a decoder that supports ftyp minor version 1). (#4745) ### Changed - Major overhaul for faster decoding and progressive lossless. (#4201, #4641, #4811) - Progressive lossless images are around 30-40% smaller and are now multithreaded increasing encoding performance by 2-5x. - Lossless images with faster decoding are now 30-80% smaller and their decoding speeds properly scale as the faster decoding level increases from 1-4. - Disabled global palette for progressive images, fixing glitchy progressive loading for indexed/low color images and restoring transparency support (Note: this causes a density/speed penalty for very low bit-depth images). - Numerous speed/memory usage improvements. - Improved encoding speeds by SIMDifying `EstimateCost` (+5% performance) and speeding up uint-coding trials. (#4322, #4330) - Accelerated modular encoding and decoding via SIMD optimizations for forward RCT transforms and upsampling (up to 4x faster). (#4332, #4384) - Massive overhaul to histogram encoding and decoding, providing significant performance optimizations for modular mode. (#4123, #4132, #4136, #4185) - Tweaked JPEG recompression logic to improve density and efficiency. (#4202) - Fixed performance regressions in fast lossless modes and optimized text-like patch detection. (#4341, #4448) - Refined x86 `XCR0` CPU checks to prevent issues on specific hardware. (#4449) - Migrated Windows release builds to use `clang-cl`, which improves performance across the board. (#4529) - Improved use of SIMD in tree construction hot loop. (#4720) - Improved spline caching. (#4857) - Postponed JPEG coeffs allocation until first SOS. (#4863) - The default buffering level in the CLI has been changed to `2`, greatly improving encoding performance for images under 2048x2048. Additionally, output is buffered by default to allow basic progressive loading. (#4635, #4637, #4642) - Better Density/Speed tradeoff for lossless effort levels. (#4236) - Improved visual quality of gradients by using channel-offset blue noise dithering instead of bayer when decoding to lower bitdepths. (#4305, #4559) - Lossy modular encoding quality/density improvements. (#3575) - `local_tone_map` tool: Now produces SDR output in the `DisplayP3` color space rather than `Rec2020`. Also added an optional third command-line argument to dump the HDR image as a raw `rgba1010102` file for use with the `libultrahdr` example app. (#4210) - Significant improvements to EXR input/output handling for cjxl. Now supports reading float32, multilayer, per-channel bitdepth, and writing greyscale EXR images. (#4312, #4460) - The decoder API for `JxlDecoderSetImageOutBuffer` and `JxlDecoderSetPreviewOutBuffer` has been clarified; their timeframes are now non-intersecting, preventing accidental buffer overwrites. (#4671) - Layered JXL files are no longer coalesced when re-encoding with cjxl, and can now be decoded to separate PNG/PAM files with djxl by using `--no_coalescing`. (#4299) - The progressive flag `-p` in cjxl will now encode a more progressive image. (#4258, #4699) - When lossy encoding, resampling 2 is now enabled at distance 10, and is up to 10x faster below effort 10 by using a faster downsampling method. (#4147) - Faster PNG compression. (#3819) - decoder API: documented that at most one preview image exists in a codestream. (#4671) - encoder API: corrected documentation for box size header in `JxlEncoderAddBox` (0 means box extends to end of file, 1 means 64-bit size follows). (#4081) - Performance & memory: tracked memory for temporary box/codestream storage (#4852) and reduced allocation overhead in `ValidateTree` (#4851) and low memory rendering pipeline. (#4495, #4496) - Quality: improved HDR behavior at effort 8+. (#3885) - tools: `jxlinfo` rewritten in C++ with human-friendly output formatting. (#4300) - tools: image viewers now perform CMS color transforms in parallel across threads. (#4326) ### Deprecated - encoder API: `JXL_ENC_FRAME_SETTING_BUFFERING` mode 3 is deprecated; output buffering should now be controlled via `JXL_ENC_FRAME_SETTING_OUTPUT_MODE`. (#4745) ### Removed - The `jpegli` codebase has been removed as it is now maintained as a separate project at [google/jpegli](https://github.com/google/jpegli). (#4657) - Dropped GIMP plugin (`libjxl-gimp-plugin`). (#4875) ### Fixed - Allows -E to be used in jpeg-transcoding again. (#4729) - Fixed an issue where Lossy Delta Palette encoding failed on images larger than 2048x2048. (#4201) - Fixed Delta Palette with Weighted Predictor decoding. (#4837) - `JxlEncoderAddChunkedFrame` incorrectly called `JxlEncoderCloseInput` instead of `JxlEncoderCloseFrames`, resulting in corrupted files when trying to add more boxes. (#4466) - Float16 values roundtrip more accurately. (#4461) - JXL input to cjxl no longer gets double orientation applied and also now keeps frame names. (#4374, #4561) - `JxlBasicInfo.alpha_premultiplied` was not correctly forwarded. (#4357) - CMYK JXL files would not decode to PNG correctly. (#4301) - Encoder would hang with specific parameters on images containing more than 256 groups. (#4302) - Decoding would fail with LZ77 runs that crossed entropy-coded streams within the same section. (#4298) - Images could be corrupted when encoding effort 1 lossless. (#4027, #4291) - Extremely high quality lossy would not conform to Level 5 of the spec. (#4238) - Density regression with Predictor Zero since v0.11. (#4225) - Progressive VarDCT encoding would create non-progressive files. (#4223) - Extremely tall/wide images failed to encode using modular. (#3937, #4308) - Empty DHT markers no longer cause JPEG transcoding to fail. (#2704) - Fixed photon noise encoding for progressive images. (#4866) - Correctly handled mistakenly set "interleaved" alpha. (#4862) - Fixed grayscale over-read in `EncodeWithSJpeg`. (#4871) - Fixed `djxl` `SelectFormat` failure when converting grayscale images to PPM. (#4691) - Fixed segmentation fault when attempting to open non-existing input files in CLI tools. (#4846) - Fixed GIF always being decoded as RGBA. (#4849) - Added a warning when an unsupported GIF disposal mode is encountered. (#3918) - Fixed PNG color space information reading. (#4598) - Fixed MSVC ARM64 build and restricted `_sub_overflow_i32` to x86/x64. (#4877) - Corrected specialized decoding path for fast lossless. (#4836) - Rejected duplicate out-of-order `jxlp` indices. (#4814) - Used `png_bit_depth` to correctly determine pixel layout. (#4854) - Fixed memory leak in `GetContext()` in `jxl_cms.cc`. (#4663) - Fixed stdin evaluation if file `-` exists. (#4668) - Fixed JXL primary color representation detection. (#4618) ### Security - Extensive security and hardening fixes across the core decoder/encoder, box buffers, and plugins (EXR, GIF, APNG/PNG, PAM/PNM/PGX). This includes fixes for integer overflows/underflows, out-of-bounds reads/writes, buffer overflows, and null pointer guards. (#4629, #4631, #4646, #4683, #4685, #4687, #4688, #4722, #4746, #4758, #4764, #4766, #4773, #4775, #4777, #4778, #4788, #4791, #4792, #4795, #4797, #4799, #4808, #4839, #4840, #4850, #4855, #4870, #4874) ## [0.11.2] - 2026-02-10 ### Fixed - Fix tile dimension in low memory rendering pipeline. (#4495 - [CVE-2025-12474](https://www.cve.org/cverecord?id=CVE-2025-12474)) - Fix number of channels for gray-to-gray color transform. (#4579 - [CVE-2026-1837](https://www.cve.org/cverecord?id=CVE-2026-1837)) - `djxl`: Reject decoding JXL files if "packed" representation size overflows `size_t`. (#4589 - Thanks to Mateusz Jurczyk of Google Project Zero for identifying this issue.) ## [0.11.1] - 2024-11-26 ### 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)) ### Changed / clarified - Encoder API: Document that `JxlEncoderFrameSettingsCreate` could return `NULL`. (#4121) ## [0.11.0] - 2024-09-13 ### Added - Gain Map API: `JxlGainMapBundle` struct and API functions to read and write gain map bundles`JxlGainMapWriteBundle` and `JxlGainMapReadBundle`. (#3552) - Handling of compressed ICC profiles: `JxlICCProfileEncode` and `JxlICCProfileDecode`. (#3628) - 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. (#3657) ### 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. [Unreleased]: https://github.com/libjxl/libjxl/compare/v0.12.0...HEAD [0.12.0]: https://github.com/libjxl/libjxl/compare/v0.11.2...v0.12.0 [0.11.2]: https://github.com/libjxl/libjxl/compare/v0.11.1...v0.11.2 [0.11.1]: https://github.com/libjxl/libjxl/compare/v0.11.0...v0.11.1 [0.11.0]: https://github.com/libjxl/libjxl/compare/v0.10.2...v0.11.0 [0.10.2]: https://github.com/libjxl/libjxl/compare/v0.10.1...v0.10.2 [0.10.1]: https://github.com/libjxl/libjxl/compare/v0.10.0...v0.10.1 [0.10.0]: https://github.com/libjxl/libjxl/compare/v0.9.2...v0.10.0 [0.9.2]: https://github.com/libjxl/libjxl/compare/v0.9.1...v0.9.2 [0.9.1]: https://github.com/libjxl/libjxl/compare/v0.9.0...v0.9.1 [0.9.0]: https://github.com/libjxl/libjxl/compare/v0.8.2...v0.9.0 [0.8.2]: https://github.com/libjxl/libjxl/compare/v0.8.1...v0.8.2 [0.8.1]: https://github.com/libjxl/libjxl/compare/v0.8.0...v0.8.1 [0.8.0]: https://github.com/libjxl/libjxl/compare/v0.7...v0.8.0 [0.7]: https://github.com/libjxl/libjxl/compare/v0.6.1...v0.7 [0.6.1]: https://github.com/libjxl/libjxl/compare/v0.6...v0.6.1 [0.6]: https://github.com/libjxl/libjxl/compare/v0.5...v0.6 [0.5]: https://github.com/libjxl/libjxl/compare/v0.3.7...v0.5 [0.3.7]: https://github.com/libjxl/libjxl/compare/v0.3.6...v0.3.7 [0.3.6]: https://github.com/libjxl/libjxl/compare/v0.3.5...v0.3.6 [0.3.5]: https://github.com/libjxl/libjxl/compare/v0.3.4...v0.3.5 [0.3.4]: https://github.com/libjxl/libjxl/compare/v0.3.3...v0.3.4 [0.3.3]: https://github.com/libjxl/libjxl/compare/v0.3.2...v0.3.3 [0.3.2]: https://github.com/libjxl/libjxl/compare/v0.3.1...v0.3.2 [0.3.1]: https://github.com/libjxl/libjxl/compare/v0.3...v0.3.1 [0.3]: https://github.com/libjxl/libjxl/compare/v0.2...v0.3 [0.2]: https://github.com/libjxl/libjxl/compare/v0.1.1...v0.2 [0.1.1]: https://github.com/libjxl/libjxl/compare/v0.1...v0.1.1 [0.1]: https://github.com/libjxl/libjxl/releases/tag/v0.1 libjxl-libjxl-5a7da83/CMakeLists.txt000066400000000000000000000500421527504724200174470ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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) message(STATUS "CMake version: ${CMAKE_VERSION}") message(STATUS "Compiler IDs\ C:${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION},\ C++:${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}") message(STATUS "Linker IDs\ C:${CMAKE_C_COMPILER_LINKER_ID} ${CMAKE_C_COMPILER_LINKER_VERSION},\ C++:${CMAKE_CXX_COMPILER_LINKER_ID} ${CMAKE_CXX_COMPILER_LINKER_VERSION}") # 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) check_cxx_compiler_flag("-mavx2" CXX_MAVX2_SUPPORTED) check_cxx_compiler_flag("-mf16c" CXX_MF16C_SUPPORTED) check_cxx_compiler_flag("-mavx512f" CXX_MAVX512F_SUPPORTED) check_cxx_compiler_flag("-mavx512dq" CXX_MAVX512DQ_SUPPORTED) check_cxx_compiler_flag("-mavx512cd" CXX_MAVX512CD_SUPPORTED) check_cxx_compiler_flag("-mavx512bw" CXX_MAVX512BW_SUPPORTED) check_cxx_compiler_flag("-mavx512vl" CXX_MAVX512VL_SUPPORTED) check_cxx_source_compiles(" #include __attribute__((target(\"+sve\"))) svuint32_t add(svuint32_t a, svuint32_t b) { svuint32_t c = svadd_u32_z(svptrue_b32(), a, b); return c; } int main() { return 0; } " CXX_SVE_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() 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() set(JPEGXL_HWY_TARGETS AVX2 AVX3 AVX3_DL AVX3_SPR AVX3_ZEN4 EMU128 NEON NEON_BF16 NEON_WITHOUT_AES PPC10 PPC8 PPC9 RVV SCALAR SSE2 SSE4 SSSE3 SVE SVE_256 SVE2 SVE2_128 WASM WASM_EMU256 Z14 Z15) set(JPEGXL_HWY_TARGETS_OFF_BY_DEFAULT AVX3 AVX3_SPR AVX3_ZEN4 RVV SSSE3 SVE_256) if (NOT CXX_SVE_SUPPORTED) list(APPEND JPEGXL_HWY_TARGETS_OFF_BY_DEFAULT SVE SVE2 SVE_256 SVE2_128) 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_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_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_WASM_THREADS true CACHE BOOL "Builds WASM modules with threads support.") set(JPEGXL_ENABLE_LTO false CACHE BOOL "Set flags to enable LTO.") # HWY codegen levers foreach(tgt IN LISTS JPEGXL_HWY_TARGETS) set(JPEGXL_ENABLE_HWY_${tgt}_DEFAULT true) if (${tgt} IN_LIST JPEGXL_HWY_TARGETS_OFF_BY_DEFAULT) set(JPEGXL_ENABLE_HWY_${tgt}_DEFAULT false) endif() set(JPEGXL_ENABLE_HWY_${tgt} ${JPEGXL_ENABLE_HWY_${tgt}_DEFAULT} CACHE BOOL "Build with ${tgt} support (faster on CPUs that support it, but larger binary size).") endforeach() # 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() set(JXL_HWY_INCLUDE_DIRS "$,hwy::hwy,hwy>,INTERFACE_INCLUDE_DIRECTORIES>>") if (JXL_HWY_DISABLED_TARGETS_FORCED) message(WARNING "HWY_DISABLED_TARGETS is overridden; JPEGXL_ENABLE_hwy_target will be ignored") else() set(HWY_DISABLED_TARGETS_LIST "") foreach(tgt IN LISTS JPEGXL_HWY_TARGETS) message(STATUS "HWY ${tgt} enabled: ${JPEGXL_ENABLE_HWY_${tgt}}") if (NOT JPEGXL_ENABLE_HWY_${tgt}) list(APPEND HWY_DISABLED_TARGETS_LIST HWY_${tgt}) endif() endforeach() if (NOT JPEGXL_ENABLE_HWY_AVX3) add_definitions(-DFJXL_ENABLE_AVX512=0) endif() if (HWY_DISABLED_TARGETS_LIST) list(JOIN HWY_DISABLED_TARGETS_LIST "|" HWY_DISABLED_TARGETS) add_compile_options($<$>:-DHWY_DISABLED_TARGETS=\(${HWY_DISABLED_TARGETS}\)>) endif() 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 # For debug builds we intentionally link with shared library to ensure that we don’t accidentally # redistribute such binaries anywhere. set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:DebugDLL>" CACHE STRING "") # Clang developers say that in case to use "static" we have to build stdlib # ourselves; for real use case we don't care about stdlib, as it is "granted", # so just linking all other libraries is fine. if (NOT MSVC AND NOT APPLE) 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) message(STATUS "CXX_STANDARD is ${CMAKE_CXX_STANDARD}") # 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. # On Android, libc (bionic) has pthreads built-in. if (NOT APPLE AND NOT ANDROID) # 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 (JPEGXL_ENABLE_LTO) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -flto") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -flto") 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" ) # Machine flags. add_compile_options(-funwind-tables) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_SYSTEM_PROCESSOR MATCHES "riscv") 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 -fprofile-update=atomic ) 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) if (NOT EXISTS "${JPEGXL_TEST_DATA_PATH}/README.md") message(FATAL_ERROR "testdata directory '${JPEGXL_TEST_DATA_PATH}' is not populated") endif() # Script to run tests over the source code in bash. find_program (BASH_PROGRAM bash) if(BASH_PROGRAM) add_test( NAME bash_test COMMAND ${BASH_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/bash_test.sh) endif() endif() # BUILD_TESTING # Documentation generated by Doxygen if(JPEGXL_ENABLE_DOXYGEN) add_subdirectory(doc) 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( -e)?$" 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() # uninstall target if(NOT TARGET uninstall) configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" IMMEDIATE @ONLY) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) endif() libjxl-libjxl-5a7da83/CODE_OF_CONDUCT.md000066400000000000000000000106111527504724200175040ustar00rootroot00000000000000# 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-libjxl-5a7da83/CONTRIBUTING.md000066400000000000000000000150401527504724200171370ustar00rootroot00000000000000# Contributing to libjxl ## Contributing with AI We don't prohibit the use of AI tools to assist with your contributions, but we expect you to follow a couple of strict guidelines: * A human must be present: No autonomous AI bots. A human must evaluate, understand, and actively submit changes. * Take full responsibility: You are considered the author of your Pull Request. You take full responsibility for the functionality, quality, and security of all code you submit, regardless of how it was generated. Since you are considered the author of the submitted code, you are expected to abide by [Google's CLA](#contributor-license-agreements). ## 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-libjxl-5a7da83/CONTRIBUTORS000066400000000000000000000012401527504724200165630ustar00rootroot00000000000000# 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-libjxl-5a7da83/LICENSE000066400000000000000000000027441527504724200157220ustar00rootroot00000000000000Copyright (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-libjxl-5a7da83/MODULE.bazel000066400000000000000000000016141527504724200167140ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project Authors. All rights reserved. # # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. """JPEG XL reference implementation""" module( name = "libjxl", repo_name = "libjxl", ) bazel_dep(name = "bazel_skylib", version = "1.8.2") bazel_dep(name = "brotli", version = "1.1.0") bazel_dep(name = "giflib", version = "5.2.1.bcr.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.50.bcr.1") bazel_dep(name = "libwebp", version = "1.6.0") bazel_dep(name = "openexr", version = "3.2.1") bazel_dep(name = "skcms", version = "20250916.0") # Requires patching `MODULE.bazel` and `BUILD` files bazel_dep(name = "highway", version = "1.2.0") local_path_override( module_name = "highway", path = "third_party/highway", ) libjxl-libjxl-5a7da83/MODULE.bazel.lock000066400000000000000000000624771527504724200176610ustar00rootroot00000000000000{ "lockFileVersion": 24, "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/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16", "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/source.json": "750d5e29326fb59cbe61116a7b803c8a1d0a7090a9c8ed89888d188e3c473fc7", "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442", "https://bcr.bazel.build/modules/apple_support/1.23.1/source.json": "d888b44312eb0ad2c21a91d026753f330caa48a25c9b2102fae75eb2b0dcfdd2", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", "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.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", "https://bcr.bazel.build/modules/brotli/1.1.0/MODULE.bazel": "3b5b90488995183419c4b5c9b063a164f6c0bc4d0d6b40550a612a5e860cc0fe", "https://bcr.bazel.build/modules/brotli/1.1.0/source.json": "098a4fd315527166e8dfe1fd1537c96a737a83764be38fc43f4da231d600f3d0", "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.bcr.1/MODULE.bazel": "1838e0db95006160fe94151b6f263de8885f871fa517f9152c238fa1991e084c", "https://bcr.bazel.build/modules/giflib/5.2.1.bcr.1/source.json": "e10494681282359cfa93730f55c608867f2be45686016c5241e3eaf9d7ca9906", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", "https://bcr.bazel.build/modules/googletest/1.15.2/source.json": "dbdda654dcb3a0d7a8bc5d0ac5fc7e150b58c2a986025ae5bc634bb2cb61f470", "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/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", "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/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", "https://bcr.bazel.build/modules/libpng/1.6.50.bcr.1/MODULE.bazel": "5a5bef9f8b723d04b194b80071b927653a4064379bb6673c829fd3c14828fba0", "https://bcr.bazel.build/modules/libpng/1.6.50.bcr.1/source.json": "90245ae3e111b84da48061935c3f6ebc658e3c87e5bd8c6e2f19cd7eec43203a", "https://bcr.bazel.build/modules/libwebp/1.6.0/MODULE.bazel": "b541e9026102a85fa3b5ec890e20f186e1664a06ab0f876bbfb3165f81c9b5df", "https://bcr.bazel.build/modules/libwebp/1.6.0/source.json": "ff540383b07271ba7d1a3ebe332dd0910a92dc13cd16e525305205526559ee6f", "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.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", "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/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680", "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa", "https://bcr.bazel.build/modules/re2/2024-07-02/source.json": "547d0111a9d4f362db32196fef805abbf3676e8d6afbe44d395d87816c1130ca", "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", "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.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", "https://bcr.bazel.build/modules/rules_cc/0.2.16/MODULE.bazel": "9242fa89f950c6ef7702801ab53922e99c69b02310c39fb6e62b2bd30df2a1d4", "https://bcr.bazel.build/modules/rules_cc/0.2.16/source.json": "d03d5cde49376d87e14ec14b666c56075e5e3926930327fd5d0484a1ff2ac1cc", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", "https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", "https://bcr.bazel.build/modules/rules_java/8.14.0/source.json": "8a88c4ca9e8759da53cddc88123880565c520503321e2566b4e33d0287a3d4bc", "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", "https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197", "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", "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/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", "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/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", "https://bcr.bazel.build/modules/rules_python/0.40.0/source.json": "939d4bd2e3110f27bfb360292986bb79fd8dcefb874358ccd6cdaa7bda029320", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.2.0/source.json": "7f27af3c28037d9701487c4744b5448d26537cc66cdef0d8df7ae85411f8de95", "https://bcr.bazel.build/modules/skcms/20250916.0/MODULE.bazel": "4dd6405c5c5220e0d1ea218094a2f1541acceaf85e066747ec94878c9553a07f", "https://bcr.bazel.build/modules/skcms/20250916.0/source.json": "f979e9985009d3d8913dd7b211e6ae4ec0152fdd70ef1d5dc4538a5abaa93df8", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", "https://bcr.bazel.build/modules/stardoc/0.7.1/source.json": "b6500ffcd7b48cd72c29bb67bcac781e12701cc0d6d55d266a652583cfcdab01", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.6/MODULE.bazel": "e937cf0a3772f93ad91f3c7af4f330b76a878bbfee06527ca1a9673b790eb896", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.6/source.json": "5f397158198f338129c865a4c3ae21bc5626a9664b3c3b40fa3b3c2ec1ff83bf", "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" }, "selectedYankedVersions": {}, "moduleExtensions": { "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { "bzlTransitiveDigest": "rL/34P1aFDq2GqVC2zCFgQ8nTuOC6ziogocpvG50Qz8=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, "generatedRepoSpecs": { "com_github_jetbrains_kotlin_git": { "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", "attributes": { "urls": [ "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" ], "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" } }, "com_github_jetbrains_kotlin": { "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", "attributes": { "git_repository_name": "com_github_jetbrains_kotlin_git", "compiler_version": "1.9.23" } }, "com_github_google_ksp": { "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", "attributes": { "urls": [ "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" ], "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", "strip_version": "1.9.23-1.0.20" } }, "com_github_pinterest_ktlint": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", "attributes": { "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", "urls": [ "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" ], "executable": true } }, "rules_android": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", "strip_prefix": "rules_android-0.1.1", "urls": [ "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" ] } } }, "recordedRepoMappingEntries": [ [ "rules_kotlin+", "bazel_tools", "bazel_tools" ] ] } } }, "facts": {} } libjxl-libjxl-5a7da83/PATENTS000066400000000000000000000024651527504724200157560ustar00rootroot00000000000000Additional 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-libjxl-5a7da83/README.md000066400000000000000000000156651527504724200162020ustar00rootroot00000000000000# 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) [![Discord](https://img.shields.io/discord/794206087879852103?label=Discord&logo=discord)](https://discord.gg/DqkQgDRTFu) JXL logo > [!IMPORTANT] > **Security Update:** It is highly recommended to update to v0.12 ASAP due to numerous security fixes. 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-libjxl-5a7da83/SECURITY.md000066400000000000000000000062401527504724200165010ustar00rootroot00000000000000# 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-libjxl-5a7da83/bash_test.sh000077500000000000000000000224521527504724200172260ustar00rootroot00000000000000#!/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 # Check that jxl and hwy includes are system-styled. if grep -i -H -n -E '#include\s*"(hwy|jxl)' "$f" >&2; then echo "Use system includes for hwy/ and jxl/ 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 return ${ret} } test_no_include_gtest() { local ret=0 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 return ${ret} } test_include_printf_macros() { local ret=0 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 return ${ret} } test_no_priudxs_in_headers() { local ret=0 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-libjxl-5a7da83/ci.sh000077500000000000000000001472571527504724200156600ustar00rootroot00000000000000#!/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}") ### Colors TEXT_BOLD_PURPLE="\033[1;35m" TEXT_RESET="\033[0m" ### Environment parameters: # TODO(eustas): tighten; only several items need more than 48KiB TEST_STACK_LIMIT="${TEST_STACK_LIMIT:-128}" 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}" CORPUS_DIR="${CORPUS_DIR:-}" 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}" EXCLUDE_DEBIAN_PACKAGES="${EXCLUDE_DEBIAN_PACKAGES:-}" # 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. # using "--verify" to avoid fatal git error when there is no upstream branch for the local branch MR_ANCESTOR_SHA=$(git -C "${MYDIR}" rev-parse -q --verify HEAD@{upstream} 2>/dev/null || true) fi if [[ -z "${MR_ANCESTOR_SHA}" ]]; then echo "Warning: current branch has no upstream tracking branch configured" \ "(git rev-parse HEAD@{upstream} failed). Falling back to comparing" \ "against the parent of HEAD (HEAD^), so only the last commit will be" \ "checked. Run 'git branch --set-upstream-to=/' to" \ "compare against a real base branch instead.">&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 [[ "${EMIT_STACK_SIZES:-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. export ASAN_OPTIONS=detect_leaks=0 cmake --build "${BUILD_DIR}" -- $TARGETS ctest -N -Q --test-dir "${BUILD_DIR}" # 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" --gcov-ignore-parse-errors suspicious_hits.warn_once_per_file # 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.8" ["19"]="19.1.7" ["20"]="20.1.8" ["21"]="21.1.5" ) 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 corpus_dir="${CORPUS_DIR}" if [[ -z "${CORPUS_DIR}" ]]; then corpus_dir=$(mktemp -d) CLEANUP_FILES+=("${corpus_dir}") else mkdir -p "${corpus_dir}" fi tar -xf "${small_corpus_tar}" -C "${corpus_dir}" run_benchmark "${corpus_dir}" 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 corpus_dir="${CORPUS_DIR}" if [[ -z "${CORPUS_DIR}" ]]; then corpus_dir=$(mktemp -d) CLEANUP_FILES+=("${corpus_dir}") else mkdir -p "${corpus_dir}" fi tar -xvf "${nikon_corpus_tar}" -C "${corpus_dir}" 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" \ "${corpus_dir}/${filename}" "${mode}" "${corpus_dir}/${png_filename}" images+=( "${png_filename}" ) done < <(cd "${corpus_dir}"; ${FIND_BIN} . -name '*.ppm' -type f) sem --id "${sem_id}" --wait # We need about 10 GiB per thread on these images. run_benchmark "${corpus_dir}" 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:-18 17 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 else echo -e "${TEXT_BOLD_PURPLE}SKIPPED:${TEXT_RESET} buildifier (not installed)" 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 -e "${TEXT_BOLD_PURPLE}SKIPPED:${TEXT_RESET} typos not installed; try: cargo install typos-cli" fi # It is ok, if zizmor is not installed. if which zizmor >/dev/null; then zizmor "${MYDIR}/.github/workflows/" else echo -e "${TEXT_BOLD_PURPLE}SKIPPED:${TEXT_RESET} zizmor (not installed)" 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 if [[ -n "${EXCLUDE_DEBIAN_PACKAGES}" ]]; then # TODO(eustas): support comma-separated list rm -f "${builddir}"/debian/${EXCLUDE_DEBIAN_PACKAGES}.install sed -i "/Package: ${EXCLUDE_DEBIAN_PACKAGES}/,/\n/d" "${builddir}"/debian/control fi ( 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.". - EMIT_STACK_SIZES=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-libjxl-5a7da83/cmake/000077500000000000000000000000001527504724200157665ustar00rootroot00000000000000libjxl-libjxl-5a7da83/cmake/FindAtomics.cmake000066400000000000000000000032431527504724200211720ustar00rootroot00000000000000# 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-libjxl-5a7da83/cmake/FindBrotli.cmake000066400000000000000000000051621527504724200210300ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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} ) find_library(${BRPREFIX}_DEBUG_LIBRARY NAMES ${${BRPREFIX}_DEBUG_NAMES} ${brlib}d 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}) if (${BRPREFIX}_DEBUG_LIBRARY) target_link_libraries(${brlib} INTERFACE debug ${${BRPREFIX}_DEBUG_LIBRARY}) target_link_libraries(${brlib} INTERFACE optimized ${${BRPREFIX}_LIBRARY}) else() target_link_libraries(${brlib} INTERFACE ${${BRPREFIX}_LIBRARY}) endif() 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-libjxl-5a7da83/cmake/FindHWY.cmake000066400000000000000000000056111527504724200202430ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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} ) find_library(HWY_DEBUG_LIBRARY NAMES ${HWY_DEBUG_NAMES} hwyd 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}) if (HWY_DEBUG_LIBRARY) target_link_libraries(hwy INTERFACE debug ${HWY_DEBUG_LIBRARY}) target_link_libraries(hwy INTERFACE optimized ${HWY_LIBRARY}) else() target_link_libraries(hwy INTERFACE ${HWY_LIBRARY}) endif() 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-libjxl-5a7da83/cmake/FindLCMS2.cmake000066400000000000000000000045601527504724200204160ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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} ) find_library(LCMS2_DEBUG_LIBRARY NAMES ${LCMS2_DEBUG_NAMES} lcms2d liblcms2d lcms-2d liblcms-2d 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}) if (LCMS2_DEBUG_LIBRARY) target_link_libraries(lcms2 INTERFACE debug ${LCMS2_DEBUG_LIBRARY}) target_link_libraries(lcms2 INTERFACE optimized ${LCMS2_LIBRARY}) else() target_link_libraries(lcms2 INTERFACE ${LCMS2_LIBRARY}) endif() 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-libjxl-5a7da83/cmake_uninstall.cmake.in000066400000000000000000000027151527504724200214730ustar00rootroot00000000000000# see https://gitlab.kitware.com/cmake/community/-/wikis/FAQ#can-i-do-make-uninstall-with-cmake if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") endif() # Read the install manifest file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) string(REGEX REPLACE "\n" ";" files "${files}") # Uninstall files foreach(file ${files}) message(STATUS "Uninstalling $ENV{DESTDIR}${file}") if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") execute_process( COMMAND "@CMAKE_COMMAND@" -E remove "$ENV{DESTDIR}${file}" OUTPUT_VARIABLE rm_out RESULT_VARIABLE rm_retval ) if(NOT "${rm_retval}" STREQUAL 0) message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") endif() else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") message(STATUS "File $ENV{DESTDIR}${file} does not exist.") endif() endforeach() # Remove empty directories foreach(file ${files}) get_filename_component(dir "$ENV{DESTDIR}${file}" DIRECTORY) while(NOT "${dir}" STREQUAL "/" AND EXISTS "${dir}") file(GLOB dir_content "${dir}/*") if(dir_content) break() # Directory is not empty, stop removing endif() message(STATUS "Removing empty directory ${dir}") file(REMOVE_RECURSE "${dir}") get_filename_component(dir "${dir}" DIRECTORY) # Move up one directory endwhile() endforeach() libjxl-libjxl-5a7da83/debian/000077500000000000000000000000001527504724200161305ustar00rootroot00000000000000libjxl-libjxl-5a7da83/debian/changelog000066400000000000000000000064471527504724200200150ustar00rootroot00000000000000jpeg-xl (0.13.0) UNRELEASED; urgency=medium * Bump JPEG XL version to 0.13.0. -- JPEG XL Maintainers Thu, 02 Jul 2026 08:32:47 +0000 jpeg-xl (0.12.0) unstable; urgency=medium * Bump JPEG XL version to 0.12.0. -- JPEG XL Maintainers Thu, 01 Jul 2026 11:16:31 +0200 jpeg-xl (0.11.0) unstable; urgency=medium * Bump JPEG XL version to 0.11.0. -- JPEG XL Maintainers Fri, 13 Sep 2024 09:19:23 +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-libjxl-5a7da83/debian/compat000066400000000000000000000000031527504724200173270ustar00rootroot0000000000000010 libjxl-libjxl-5a7da83/debian/control000066400000000000000000000047271527504724200175450ustar00rootroot00000000000000Source: 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 | libgdk-pixbuf-xlib-2.0-dev, libgif-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. libjxl-libjxl-5a7da83/debian/copyright000066400000000000000000000242451527504724200200720ustar00rootroot00000000000000Format: 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-libjxl-5a7da83/debian/jxl.install000066400000000000000000000000761527504724200203200ustar00rootroot00000000000000usr/bin/* usr/share/man/man1/cjxl.1 usr/share/man/man1/djxl.1 libjxl-libjxl-5a7da83/debian/libjxl-dev.install000066400000000000000000000000741527504724200215610ustar00rootroot00000000000000usr/include/jxl/*.h usr/lib/*/*.so usr/lib/*/pkgconfig/*.pc libjxl-libjxl-5a7da83/debian/libjxl-gdk-pixbuf.install000066400000000000000000000001601527504724200230370ustar00rootroot00000000000000usr/lib/*/gdk-pixbuf-*/*/loaders/* usr/share/mime/packages/image-jxl.xml usr/share/thumbnailers/jxl.thumbnailer libjxl-libjxl-5a7da83/debian/libjxl.install000066400000000000000000000000271527504724200210030ustar00rootroot00000000000000usr/lib/*/libjxl*.so.* libjxl-libjxl-5a7da83/debian/rules000077500000000000000000000007151527504724200172130ustar00rootroot00000000000000#!/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. dh_auto_configure -- \ -DJPEGXL_VERSION=$(DEB_VERSION) \ -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DJPEGXL_FORCE_SYSTEM_GTEST=ON \ -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \ -DJPEGXL_FORCE_SYSTEM_HWY=ON \ -DJPEGXL_ENABLE_PLUGINS=ON libjxl-libjxl-5a7da83/debian/source/000077500000000000000000000000001527504724200174305ustar00rootroot00000000000000libjxl-libjxl-5a7da83/debian/source/format000066400000000000000000000000141527504724200206360ustar00rootroot000000000000003.0 (quilt) libjxl-libjxl-5a7da83/deps.sh000077500000000000000000000056571527504724200162150ustar00rootroot00000000000000#!/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="db24a98aed38a16de78cc05ca15cdd8dc2a0d92d" # v1.2.1 THIRD_PARTY_GOOGLETEST="6910c9d9165801d8827d628cb72eb7ea9dd538c5" # v1.16.0 THIRD_PARTY_HIGHWAY="457c891775a7397bdb0376bb1031e6e027af1c48" # v1.2.0 THIRD_PARTY_SKCMS="96d9171c94b937a1b5f0293de7309ac16311b722" # 2025_09_16 THIRD_PARTY_SJPEG="dd4d9857c58a25713711a46d37385ad23ca2177e" # Sun Nov 8 22:48:20 2026 +0100 THIRD_PARTY_ZLIB="51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf" # v1.3.1 THIRD_PARTY_LIBPNG="872555f4ba910252783af1507f9e7fe1653be252" # v1.6.47 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=1 url="https://github.com/${project}/tarball/${sha}" 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). ## Building WASM with Docker There is a docker devcontainer which can be used to build the WASM libraries available in the .devcontainer package. That can be run in an editor supporting devcontainers, or can be run manually with: ```bash docker build . -f .devcontainer/Dockerfile -t libjxl docker run --mount type=bind,src=.,dst=/workspaces/libjxl -i libjxl source $OPT/emsdk/emsdk_env.sh BUILD_TARGET=wasm32 ENABLE_WASM_SIMD=1 emconfigure ./ci.sh release ``` libjxl-libjxl-5a7da83/doc/color_management.md000066400000000000000000000055021527504724200213110ustar00rootroot00000000000000# 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-libjxl-5a7da83/doc/debugging_workflows.md000066400000000000000000000046551527504724200220570ustar00rootroot00000000000000### 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. NB: this action is not in allow-list of libjxl repository; use it in a fork. ### 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:trixie 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 # -DCMAKE_CXX_FLAGS=-DJXL_HIGH_PRECISION=0 ``` libjxl-libjxl-5a7da83/doc/developing_in_debian.md000066400000000000000000000041311527504724200221200ustar00rootroot00000000000000# 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 clang cmake doxygen graphviz ninja-build libpng-dev ``` 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 extra-cmake-modules g++ libbenchmark-dev libbenchmark-tools \ libgif-dev libgoogle-perftools-dev libgtest-dev libjpeg-dev libopenexr-dev \ libwebp-dev qt6-base-dev xdg-utils ``` 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. ## Debian Build with Docker There is a Dockerfile contained in the `.devcontainer` which can be used for building on windows to create a docker image. It is used as a devcontainer in your editor, or from the command line via: ```bash docker build . -f .devcontainer/Dockerfile -t libjxl docker run --mount type=bind,src=.,dst=/workspaces/libjxl -i libjxl ./ci.sh release ``` See also the building WASM for how to use this image to build WASM builds. libjxl-libjxl-5a7da83/doc/developing_in_github.md000066400000000000000000000327271527504724200221740ustar00rootroot00000000000000# 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-libjxl-5a7da83/doc/developing_in_windows_msys.md000066400000000000000000000154741527504724200234570ustar00rootroot00000000000000# 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:** 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. libjxl-libjxl-5a7da83/doc/developing_in_windows_vcpkg.md000066400000000000000000000054411527504724200235670ustar00rootroot00000000000000# 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-libjxl-5a7da83/doc/developing_with_crossroad.md000066400000000000000000000065041527504724200232500ustar00rootroot00000000000000# 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 `gtest` and `brotli` are 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 . ``` libjxl-libjxl-5a7da83/doc/encode_effort.md000066400000000000000000000070131527504724200206000ustar00rootroot00000000000000# 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 `10`, but there is also `e11` if 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 | only 8x8, basically XYB jpeg with ANS | | e2 | global channel palette, fixed MA tree (context based on Gradient-error), ANS, otherwise same as e1 | same as e1 | | e3 | same as e2 but fixed Weighted predictor and fixed MA tree with context based on WP-error | e2 + better ANS | | e4 | try both ClampedGradient and Weighted predictor, learned MA tree, global palette | coefficient reordering | | e5 | e4 + patches, local palette / local channel palette, different local RCTs | e4 + simple variable blocks heuristics, adaptive quantization, 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 | e8 + more Butteraugli iterations | | e10 | e9 + global MA tree, try all predictors, and disables chunked encoding | e9 + more thorough adaptive quantization, disables chunked encoding and uses iterative downsampling | | e11 | e10 + previous-channel MA tree properties, different group dimensions, and try multiple e10 configurations | N/A | For the entropy coding (context clustering, lz77 search, hybriduint configuration): slower/more exhaustive search as effort goes up. Chunked encoding is also disabled under these circumstances: * When the image is smaller than 2048x2048. * Lossless Jpeg transcoding. * VarDCT at distances ≥10. * Effort 7 VarDCT at distances ≥3.0. * Efforts 8 & 9 VarDCT at distances >0.5. * Lossy Modular. * When using any of these flags: * `--patches=1` * `--progressive_dc >0` * `-p` * `-d 0` and `-R 1` * `--noise=1` * `--resampling >1` * `--disable_perceptual_optimizations` libjxl-libjxl-5a7da83/doc/format_overview.md000066400000000000000000000324711527504724200212220ustar00rootroot00000000000000# 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-libjxl-5a7da83/doc/fuzzing.md000066400000000000000000000210641527504724200174740ustar00rootroot00000000000000# 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-libjxl-5a7da83/doc/jxl.svg000066400000000000000000000023151527504724200167720ustar00rootroot00000000000000libjxl-libjxl-5a7da83/doc/man/000077500000000000000000000000001527504724200162265ustar00rootroot00000000000000libjxl-libjxl-5a7da83/doc/man/cjxl.txt000066400000000000000000000054731527504724200177400ustar00rootroot00000000000000cjxl(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-libjxl-5a7da83/doc/man/djxl.txt000066400000000000000000000023741527504724200177360ustar00rootroot00000000000000djxl(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-libjxl-5a7da83/doc/release.md000066400000000000000000000371241527504724200174240ustar00rootroot00000000000000# 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:trixie /bin/bash apt update apt install -y clang cmake git libbrotli-dev libopenexr-dev libpng-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-libjxl-5a7da83/doc/software_support.md000066400000000000000000000147301527504724200214300ustar00rootroot00000000000000# 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) - Zen: supported since version v1.0.0-a.39 [release notes](https://zen-browser.app/release-notes/#1.0.0-a.39) - Floorp: supported since at least 2024 [discussion](https://github.com/Floorp-Projects/Floorp/discussions/1289) 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) - [KDE's kimageformats](https://invent.kde.org/frameworks/kimageformats) - 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](https://gitlab.com/SuperSaltyGamer/jpegxlformat); plugin for Adobe Photoshop 2020 and above on Windows, supports 8-bit and 16-bit color depth, embedded ICC color profiles, transparency. - [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/) - [tev](https://github.com/Tom94/tev) - [Oculante](https://github.com/woelper/oculante) - 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) - [Czkawka](https://github.com/qarmin/czkawka); Only basic support at this time ## 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 ## 3D Software - [Okino PolyTrans|CAD+DCC 3D Conversion System](https://www.okino.com/conv/conv.htm) - [Okino NuGraf|CAD+DCC 3D Visualization and Conversion System](https://www.okino.com) libjxl-libjxl-5a7da83/doc/sphinx/000077500000000000000000000000001527504724200167645ustar00rootroot00000000000000libjxl-libjxl-5a7da83/doc/sphinx/api.rst000066400000000000000000000004741527504724200202740ustar00rootroot00000000000000libjxl 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-libjxl-5a7da83/doc/sphinx/api_color.rst000066400000000000000000000001761527504724200214710ustar00rootroot00000000000000Color encoding and conversion ============================= .. doxygengroup:: libjxl_color :members: :private-members: libjxl-libjxl-5a7da83/doc/sphinx/api_common.rst000066400000000000000000000001531527504724200216360ustar00rootroot00000000000000Common API concepts =================== .. doxygengroup:: libjxl_common :members: :private-members: libjxl-libjxl-5a7da83/doc/sphinx/api_cpp.rst000066400000000000000000000001301527504724200211230ustar00rootroot00000000000000C++ helpers =========== .. doxygengroup:: libjxl_cpp :members: :private-members: libjxl-libjxl-5a7da83/doc/sphinx/api_decoder.rst000066400000000000000000000002021527504724200217460ustar00rootroot00000000000000Decoder API - ``jxl/decode.h`` ============================== .. doxygengroup:: libjxl_decoder :members: :private-members: libjxl-libjxl-5a7da83/doc/sphinx/api_encoder.rst000066400000000000000000000002021527504724200217600ustar00rootroot00000000000000Encoder API - ``jxl/encode.h`` ============================== .. doxygengroup:: libjxl_encoder :members: :private-members: libjxl-libjxl-5a7da83/doc/sphinx/api_metadata.rst000066400000000000000000000001671527504724200221330ustar00rootroot00000000000000Image and frame metadata ======================== .. doxygengroup:: libjxl_metadata :members: :private-members: libjxl-libjxl-5a7da83/doc/sphinx/api_threads.rst000066400000000000000000000002021527504724200217730ustar00rootroot00000000000000Multi-threaded Encoder/Decoder ============================== .. doxygengroup:: libjxl_threads :members: :private-members: libjxl-libjxl-5a7da83/doc/sphinx/conf.py000066400000000000000000000062231527504724200202660ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/doc/sphinx/index.rst000066400000000000000000000004201527504724200206210ustar00rootroot00000000000000.. libjxl sphinx documentation entrypoint JPEG XL image format reference implementation ============================================= .. toctree:: :maxdepth: 3 :caption: Contents: api Indices and tables ================== * :ref:`genindex` * :ref:`search` libjxl-libjxl-5a7da83/doc/sphinx/requirements.txt000066400000000000000000000000401527504724200222420ustar00rootroot00000000000000breathe sphinx sphinx-rtd-theme libjxl-libjxl-5a7da83/doc/vuln_playbook.md000066400000000000000000000257111527504724200206670ustar00rootroot00000000000000# 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-libjxl-5a7da83/doc/xl_overview.md000066400000000000000000000153011527504724200203460ustar00rootroot00000000000000# 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-libjxl-5a7da83/examples/000077500000000000000000000000001527504724200165245ustar00rootroot00000000000000libjxl-libjxl-5a7da83/examples/CMakeLists.txt000066400000000000000000000016731527504724200212730ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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.16) 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-libjxl-5a7da83/examples/decode_exif_metadata.cc000066400000000000000000000122631527504724200231350ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 bool DecodeJpegXlExif(const uint8_t* jxl, size_t size, std::vector* exif) { JxlDecoderPtr 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 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-libjxl-5a7da83/examples/decode_oneshot.cc000066400000000000000000000205261527504724200220220ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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. JxlResizableParallelRunnerPtr runner = JxlResizableParallelRunnerMake(nullptr); JxlDecoderPtr 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-libjxl-5a7da83/examples/decode_progressive.cc000066400000000000000000000173731527504724200227210ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #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. JxlResizableParallelRunnerPtr runner = JxlResizableParallelRunnerMake(nullptr); JxlDecoderPtr 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-libjxl-5a7da83/examples/encode_oneshot.cc000066400000000000000000000210471527504724200220330ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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; size_t num_pixels = static_cast(*ysize) * *xsize; if (data.size() != num_pixels * 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(num_pixels * 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(num_pixels * 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) { JxlEncoderPtr enc = JxlEncoderMake(/*memory_manager=*/nullptr); JxlThreadParallelRunnerPtr 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 (!frame_settings) { fprintf(stderr, "JxlEncoderFrameSettingsCreate failed\n"); return false; } 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-libjxl-5a7da83/examples/examples.cmake000066400000000000000000000013311527504724200213420ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/flake.lock000066400000000000000000000027311527504724200166450ustar00rootroot00000000000000{ "nodes": { "flake-utils": { "inputs": { "systems": "systems" }, "locked": { "lastModified": 1731533236, "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { "owner": "numtide", "repo": "flake-utils", "type": "github" } }, "nixpkgs": { "locked": { "lastModified": 1735471104, "narHash": "sha256-0q9NGQySwDQc7RhAV2ukfnu7Gxa5/ybJ2ANT8DQrQrs=", "owner": "NixOS", "repo": "nixpkgs", "rev": "88195a94f390381c6afcdaa933c2f6ff93959cb4", "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-libjxl-5a7da83/flake.nix000066400000000000000000000015351527504724200165140ustar00rootroot00000000000000{ 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 ninja ]; shellHook = '' export CC=clang export CXX=clang++ ''; }; } ); } libjxl-libjxl-5a7da83/lib/000077500000000000000000000000001527504724200154545ustar00rootroot00000000000000libjxl-libjxl-5a7da83/lib/BUILD000066400000000000000000000152051527504724200162410ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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_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_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, ) 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 = "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 = [":includes"], ) CODEC_FILES = libjxl_codec_apng_sources + libjxl_codec_exr_sources + libjxl_codec_gif_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")] # 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", ], deps = [ ":jpegxl_private", ":jpegxl_threads", ":jxl_version", ] + libjxl_deps_exr + libjxl_deps_gif + libjxl_deps_jpeg + libjxl_deps_png, ) TESTLIB_FILES = libjxl_testlib_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 = [ ":jpegxl_extras", ":jpegxl_private", ] + libjxl_deps_runfiles, ) TESTS = [path.partition(".")[0] for path in libjxl_tests] [ cc_test( name = test, timeout = libjxl_test_timeouts.get(test, "moderate"), srcs = [ test + ".cc", "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-libjxl-5a7da83/lib/CMakeLists.txt000066400000000000000000000144451527504724200202240ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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 13) set(JPEGXL_PATCH_VERSION 0) set(JPEGXL_LIBRARY_VERSION "${JPEGXL_MAJOR_VERSION}.${JPEGXL_MINOR_VERSION}.${JPEGXL_PATCH_VERSION}") # This is the library API/ABI compatibility version. Changing this value makes # the shared library incompatible with previous version. A program linked # against this shared library SOVERSION will not run with an older SOVERSION. # It is important to update this value when making incompatible API/ABI changes # so that programs that depend on libjxl can update their dependencies. Semantic # versioning allows 0.y.z to have incompatible changes in minor versions. set(JPEGXL_SO_MINOR_VERSION 13) 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 ) if (NOT JPEGXL_ENABLE_COVERAGE AND NOT SANITIZER MATCHES ".san") # NB: use `-Rpass-analysis=stack-frame-layout -g` for investigation. # TODO(eustas): tighten if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") list(APPEND JPEGXL_INTERNAL_FLAGS -Wframe-larger-than=5632) elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 14.0) list(APPEND JPEGXL_INTERNAL_FLAGS -Wframe-larger-than=5632) endif() endif() # 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 -Wshadow -Wstring-conversion -Wtautological-overlap-compare -Wthread-safety-analysis -Wundefined-func-template -Wunreachable-code -Wunused-comparison ) if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 8.0) # for SKCMS on PPC list(APPEND HWY_FLAGS -Wno-deprecated-altivec-src-compat) endif() 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 # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109740 if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") list(APPEND JPEGXL_INTERNAL_FLAGS -Wno-overloaded-virtual) endif() 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() # MSVCRT bundles math functions so no explicit libm dependency is required. if(MSVC) set(PKGCONFIG_MATH_LIB "") else() set(PKGCONFIG_MATH_LIB "-lm") 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) # 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}") # Tests for the jxl library. include(jxl_tests.cmake) if(BUILD_TESTING AND JPEGXL_ENABLE_BENCHMARK) # Google benchmark for the jxl library include(jxl_benchmark.cmake) endif() libjxl-libjxl-5a7da83/lib/extras/000077500000000000000000000000001527504724200167625ustar00rootroot00000000000000libjxl-libjxl-5a7da83/lib/extras/LICENSE.apngdis000066400000000000000000000017711527504724200214210ustar00rootroot00000000000000APNG 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-libjxl-5a7da83/lib/extras/README.md000066400000000000000000000003021527504724200202340ustar00rootroot00000000000000## 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-libjxl-5a7da83/lib/extras/alpha_blend.cc000066400000000000000000000042211527504724200215210ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #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; } const bool is_gray_alpha = (format.num_channels == 2); --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 (is_gray_alpha) { 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); } } } JXL_ASSIGN_OR_RETURN(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-libjxl-5a7da83/lib/extras/alpha_blend.h000066400000000000000000000007651527504724200213740ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/extras/codec.cc000066400000000000000000000072621527504724200203550ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include #include "lib/extras/codec_in_out.h" #include "lib/extras/dec/color_hints.h" #include "lib/extras/dec/decode.h" #include "lib/extras/enc/apng.h" #include "lib/extras/enc/encode.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/data_parallel.h" #include "lib/jxl/base/span.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, io->memory_manager)) { 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-libjxl-5a7da83/lib/extras/codec.h000066400000000000000000000035331527504724200202140ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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/codec_in_out.h" #include "lib/extras/dec/color_hints.h" #include "lib/extras/dec/decode.h" #include "lib/extras/packed_image.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" 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-libjxl-5a7da83/lib/extras/codec_in_out.h000066400000000000000000000061411527504724200215670ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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_IN_OUT_H_ #define LIB_EXTRAS_CODEC_IN_OUT_H_ // Holds inputs/outputs for decoding/encoding images. #include #include #include #include #include "lib/jxl/base/status.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/headers.h" #include "lib/jxl/image.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/image_metadata.h" #include "lib/jxl/luminance.h" namespace jxl { // Holds a preview, a main image or one or more frames, plus the inputs/outputs // to/from decoding/encoding. class CodecInOut { public: explicit CodecInOut(JxlMemoryManager* memory_manager) : memory_manager(memory_manager), preview_frame(memory_manager, &metadata.m) { frames.reserve(1); frames.emplace_back(memory_manager, &metadata.m); } // Move-only. CodecInOut(CodecInOut&&) = default; CodecInOut& operator=(CodecInOut&&) = default; size_t LastStillFrame() const { JXL_DASSERT(!frames.empty()); size_t last = 0; for (size_t i = 0; i < frames.size(); i++) { last = i; if (frames[i].duration > 0) break; } return last; } ImageBundle& Main() { return frames[LastStillFrame()]; } const ImageBundle& Main() const { return frames[LastStillFrame()]; } // If c_current.IsGray(), all planes must be identical. Status SetFromImage(Image3F&& color, const ColorEncoding& c_current) { JXL_RETURN_IF_ERROR(Main().SetFromImage(std::move(color), c_current)); SetIntensityTarget(&this->metadata.m); JXL_RETURN_IF_ERROR(SetSize(Main().xsize(), Main().ysize())); return true; } Status SetSize(size_t xsize, size_t ysize) { JXL_RETURN_IF_ERROR(metadata.size.Set(xsize, ysize)); return true; } Status CheckMetadata() const { JXL_ENSURE(metadata.m.bit_depth.bits_per_sample != 0); JXL_ENSURE(!metadata.m.color_encoding.ICC().empty()); if (preview_frame.xsize() != 0) { JXL_RETURN_IF_ERROR(preview_frame.VerifyMetadata()); } JXL_ENSURE(preview_frame.metadata() == &metadata.m); for (const ImageBundle& ib : frames) { JXL_RETURN_IF_ERROR(ib.VerifyMetadata()); JXL_ENSURE(ib.metadata() == &metadata.m); } return true; } size_t xsize() const { return metadata.size.xsize(); } size_t ysize() const { return metadata.size.ysize(); } Status ShrinkTo(size_t xsize, size_t ysize) { // preview is unaffected. for (ImageBundle& ib : frames) { JXL_RETURN_IF_ERROR(ib.ShrinkTo(xsize, ysize)); } JXL_RETURN_IF_ERROR(SetSize(xsize, ysize)); return true; } // -- DECODER OUTPUT, ENCODER INPUT: // Metadata stored into / retrieved from bitstreams. JxlMemoryManager* memory_manager; CodecMetadata metadata; // applies to preview and all frames // If metadata.have_preview: ImageBundle preview_frame; std::vector frames; // size=1 if !metadata.have_animation }; } // namespace jxl #endif // LIB_EXTRAS_CODEC_IN_OUT_H_ libjxl-libjxl-5a7da83/lib/extras/codec_test.cc000066400000000000000000000727161527504724200214220ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/extras/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 { TEST(CodecTest, EncodersRejectSurplusFrameExtraChannel) { PackedPixelFile ppf; ppf.info.xsize = 1; ppf.info.ysize = 1; ppf.info.num_color_channels = 1; ppf.info.bits_per_sample = 8; const JxlPixelFormat gray{1, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0}; JXL_TEST_ASSIGN_OR_DIE(PackedFrame frame, PackedFrame::Create(1, 1, gray)); JXL_TEST_ASSIGN_OR_DIE(PackedImage ec, PackedImage::Create(1, 1, gray)); memset(frame.color.pixels(), 0x7f, frame.color.pixels_size); memset(ec.pixels(), 0x3f, ec.pixels_size); frame.extra_channels.emplace_back(std::move(ec)); ppf.frames.emplace_back(std::move(frame)); for (const char* extension : {".pgm", ".png"}) { auto encoder = Encoder::FromExtension(extension); if (!encoder) continue; EncodedImage encoded; EXPECT_FALSE(encoder->Encode(ppf, &encoded, nullptr)) << extension; } } TEST(CodecTest, PackedPixelFileValidExtraChannelCounts) { PackedPixelFile ppf; ppf.info.xsize = 1; ppf.info.ysize = 1; ppf.info.num_color_channels = 1; ppf.info.bits_per_sample = 8; PackedExtraChannel metadata{}; metadata.ec_info.bits_per_sample = 8; ppf.extra_channels_info.push_back(metadata); const JxlPixelFormat gray{1, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0}; for (int i = 0; i < 2; ++i) { JXL_TEST_ASSIGN_OR_DIE(PackedFrame frame, PackedFrame::Create(1, 1, gray)); JXL_TEST_ASSIGN_OR_DIE(PackedImage ec, PackedImage::Create(1, 1, gray)); memset(frame.color.pixels(), 0x7f, frame.color.pixels_size); memset(ec.pixels(), 0x3f, ec.pixels_size); frame.extra_channels.emplace_back(std::move(ec)); ppf.frames.emplace_back(std::move(frame)); } ASSERT_TRUE(ppf.Validate()); for (const char* extension : {".pgm", ".png"}) { auto encoder = Encoder::FromExtension(extension); if (!encoder) continue; EncodedImage encoded; ASSERT_TRUE(encoder->Encode(ppf, &encoded, nullptr)) << extension; ASSERT_EQ(encoded.bitstreams.size(), 2u); ASSERT_EQ(encoded.extra_channel_bitstreams.size(), 1u); ASSERT_EQ(encoded.extra_channel_bitstreams[0].size(), 2u); for (const auto& bytes : encoded.extra_channel_bitstreams[0]) { PackedPixelFile decoded; ASSERT_TRUE(DecodeBytes(Bytes(bytes), ColorHints(), &decoded)); ASSERT_EQ(decoded.frames.size(), 1u); EXPECT_EQ(*static_cast(decoded.frames[0].color.pixels()), 0x3f); } } ppf.frames[1].extra_channels.clear(); EXPECT_FALSE(ppf.Validate()); for (const char* extension : {".pgm", ".png"}) { auto encoder = Encoder::FromExtension(extension); if (!encoder) continue; EncodedImage encoded; EXPECT_FALSE(encoder->Encode(ppf, &encoded, nullptr)) << extension; } } 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()); if (!CanDecode(params.codec)) { fprintf(stderr, "Skipping test because of missing decoding support.\n"); return; } auto encoder = Encoder::FromExtension(extension); if (!encoder) { fprintf(stderr, "Skipping test because of missing encoding support.\n"); return; } PackedPixelFile ppf_in; CreateTestImage(params, &ppf_in); EncodedImage encoded; ASSERT_TRUE(encoder->Encode(ppf_in, &encoded, pool)); ASSERT_EQ(encoded.bitstreams.size(), 1u); 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(), 1u); 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), 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(), 1u); 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; const std::string max_unsigned = std::to_string(std::numeric_limits::max()); // Failing to parse invalid strings results in a crash if `JXL_CRASH_ON_ERROR` // is defined and hence the tests fail. Therefore we only run these tests if // `JXL_CRASH_ON_ERROR` is not defined. #if (!JXL_CRASH_ON_ERROR) ASSERT_FALSE(PnmParseUnsigned(MakeSpan(""), &u)); ASSERT_FALSE(PnmParseUnsigned(MakeSpan("+"), &u)); ASSERT_FALSE(PnmParseUnsigned(MakeSpan("-"), &u)); ASSERT_FALSE(PnmParseUnsigned(MakeSpan("A"), &u)); const std::string overflowing_unsigned = max_unsigned + "0"; ASSERT_FALSE(PnmParseUnsigned(MakeSpan(overflowing_unsigned.c_str()), &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(PnmParseUnsigned(MakeSpan(max_unsigned.c_str()), &u)); ASSERT_TRUE(u == std::numeric_limits::max()); 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, PNMSampleAboveMaxVal) { const auto decode = [](const std::string& header, const std::vector& samples) -> bool { std::vector pnm(header.begin(), header.end()); pnm.insert(pnm.end(), samples.begin(), samples.end()); PackedPixelFile ppf; return static_cast(DecodeBytes(Bytes(pnm), ColorHints(), &ppf)); }; const std::string kPam12 = "P7\nWIDTH 1\nHEIGHT 1\nDEPTH 4\nMAXVAL 4095\n" "TUPLTYPE RGB_ALPHA\nENDHDR\n"; // Samples up to MaxVal are valid. EXPECT_TRUE(decode("P5\n2 1\n15\n", {0, 15})); EXPECT_TRUE(decode("P5\n2 1\n255\n", {0, 255})); EXPECT_TRUE(decode("P6\n1 1\n4095\n", {0x0F, 0xFF, 0, 0, 0x0F, 0xFF})); EXPECT_TRUE(decode("P5\n1 1\n65535\n", {0xFF, 0xFF})); EXPECT_TRUE(decode(kPam12, {0, 0, 0, 0, 0, 0, 0x0F, 0xFF})); #if (!JXL_CRASH_ON_ERROR) // Samples above MaxVal are rejected. EXPECT_FALSE(decode("P5\n2 1\n15\n", {0, 16})); EXPECT_FALSE(decode("P6\n1 1\n4095\n", {0x0F, 0xFF, 0x10, 0x00, 0, 0})); EXPECT_FALSE(decode("P6\n1 1\n4095\n", {0, 0, 0, 0, 0xFF, 0xFF})); EXPECT_FALSE(decode(kPam12, {0, 0, 0, 0, 0, 0, 0x10, 0x00})); // Check every sample position of images larger than 8 bytes. const std::vector max4(19, 15); std::vector max12; for (size_t i = 0; i < 19; ++i) max12.insert(max12.end(), {0x0F, 0xFF}); EXPECT_TRUE(decode("P5\n19 1\n15\n", max4)); EXPECT_TRUE(decode("P5\n19 1\n4095\n", max12)); for (size_t i = 0; i < 19; ++i) { std::vector samples4 = max4; samples4[i] = 16; EXPECT_FALSE(decode("P5\n19 1\n15\n", samples4)) << i; std::vector samples12 = max12; samples12[2 * i] = 0x10; samples12[2 * i + 1] = 0x00; EXPECT_FALSE(decode("P5\n19 1\n4095\n", samples12)) << i; } #endif } 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, FormatNegotiationGrayscalePromotion) { const std::vector accepted_formats = { {/*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}, }; JxlBasicInfo info; JxlEncoderInitBasicInfo(&info); info.bits_per_sample = 8; info.num_color_channels = 1; JxlPixelFormat format; ASSERT_TRUE(SelectFormat(accepted_formats, info, &format)); EXPECT_EQ(format.num_channels, 3u); EXPECT_EQ(format.data_type, JXL_TYPE_UINT8); } TEST(CodecTest, FormatNegotiationGrayAlphaPromotion) { const std::vector accepted_formats = { {/*num_channels=*/3, /*data_type=*/JXL_TYPE_UINT8, /*endianness=*/JXL_NATIVE_ENDIAN, /*align=*/0}, }; JxlBasicInfo info; JxlEncoderInitBasicInfo(&info); info.bits_per_sample = 8; info.num_color_channels = 1; info.alpha_bits = 8; JxlPixelFormat format; ASSERT_TRUE(SelectFormat(accepted_formats, info, &format)); EXPECT_EQ(format.num_channels, 3u); EXPECT_EQ(format.data_type, JXL_TYPE_UINT8); } #if (!JXL_CRASH_ON_ERROR) TEST(CodecTest, FormatNegotiationGrayscaleNoMatch) { const std::vector accepted_formats = { {/*num_channels=*/5, /*data_type=*/JXL_TYPE_UINT8, /*endianness=*/JXL_NATIVE_ENDIAN, /*align=*/0}, }; JxlBasicInfo info; JxlEncoderInitBasicInfo(&info); info.bits_per_sample = 8; info.num_color_channels = 1; JxlPixelFormat format; EXPECT_FALSE(SelectFormat(accepted_formats, info, &format)); } #endif 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(), 1u); 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(), 1u); VerifySameImage(ppf.frames[0].color, ppf.info.bits_per_sample, decoded_ppf.frames[0].color, decoded_ppf.info.bits_per_sample); } TEST(CodecTest, ExtensionFromPath) { // The extension is derived from the final path component only: dots in // directory names must not leak into the result, the returned extension // never contains a path separator, and a trailing dot that carries no // extension is reported as none. std::string extension; size_t bits_per_sample = 0; extension.clear(); ASSERT_EQ(CodecFromPath("a.png/../../b", &bits_per_sample, &extension), Codec::kUnknown); EXPECT_EQ(extension, ""); // A valid codec extension in the final component is still detected even // with a dotted directory part ahead of it. extension.clear(); ASSERT_EQ(CodecFromPath("a.jpg/../../b.jpg", &bits_per_sample, &extension), Codec::kJPG); EXPECT_EQ(extension, ".jpg"); extension.clear(); ASSERT_EQ(CodecFromPath("dir.name/file", &bits_per_sample, &extension), Codec::kUnknown); EXPECT_EQ(extension, ""); extension.clear(); ASSERT_EQ(CodecFromPath("out.png.", &bits_per_sample, &extension), Codec::kUnknown); EXPECT_EQ(extension, ""); // Backslash separators are stripped as well. extension.clear(); ASSERT_EQ(CodecFromPath("C:\\x\\y.png", &bits_per_sample, &extension), Codec::kPNG); EXPECT_EQ(extension, ".png"); extension.clear(); ASSERT_EQ(CodecFromPath("/dev/stdout", &bits_per_sample, &extension), Codec::kUnknown); EXPECT_EQ(extension, ""); // Bare extension strings (as passed by the benchmark tooling) keep // working, including the .pfm bits_per_sample side effect. extension.clear(); ASSERT_EQ(CodecFromPath(".pfm", &bits_per_sample, &extension), Codec::kPNM); EXPECT_EQ(extension, ".pfm"); EXPECT_EQ(bits_per_sample, 32u); } TEST(CodecTest, DecodeTruncatedPNGLeavesMissingRowsZero) { std::unique_ptr png_encoder = Encoder::FromExtension(".png"); if (!png_encoder || !CanDecode(Codec::kPNG)) { fprintf(stderr, "Skipping test because of missing codec support.\n"); return; } constexpr size_t kXSize = 64; constexpr size_t kYSize = 64; TestImageParams params = {Codec::kPNG, kXSize, kYSize, /*bits_per_sample=*/8, /*is_gray=*/false, /*add_alpha=*/false, /*big_endian=*/true, /*add_extra_channels=*/false}; PackedPixelFile ppf_in; CreateTestImage(params, &ppf_in); EncodedImage encoded; ASSERT_TRUE(png_encoder->Encode(ppf_in, &encoded, nullptr)); ASSERT_EQ(encoded.bitstreams.size(), 1u); std::vector png = encoded.bitstreams[0]; // Declare twice the height in IHDR (the chunk CRC is not checked), so the // pixel data ends after half of the rows. ASSERT_EQ(memcmp(&png[12], "IHDR", 4), 0); ASSERT_EQ(LoadBE32(&png[20]), kYSize); StoreBE32(2 * kYSize, &png[20]); // Fill freed heap blocks of the size of the pixel buffer, so that a buffer // that is not cleared holds stale bytes instead of fresh zero pages. for (size_t i = 0; i < 4; ++i) { std::vector dirty(kXSize * 2 * kYSize * 3, 0xA5); EXPECT_EQ(dirty.back(), 0xA5); } PackedPixelFile ppf_out; ASSERT_TRUE(DecodeBytes(Bytes(png), ColorHints(), &ppf_out)); ASSERT_EQ(ppf_out.frames.size(), 1u); const PackedImage& in = ppf_in.frames[0].color; const PackedImage& out = ppf_out.frames[0].color; ASSERT_EQ(out.xsize, kXSize); ASSERT_EQ(out.ysize, 2 * kYSize); ASSERT_EQ(out.pixels_size, 2 * in.pixels_size); // The rows in the file are decoded, the rows missing from it are zero. const auto* in_pixels = static_cast(in.pixels()); const auto* out_pixels = static_cast(out.pixels()); EXPECT_EQ(memcmp(in_pixels, out_pixels, in.pixels_size), 0); for (size_t i = in.pixels_size; i < out.pixels_size; ++i) { ASSERT_EQ(out_pixels[i], 0) << "byte " << i; } } // Frame names are arbitrary bytes from the codestream. The NumPy metadata // writer must produce valid JSON for them: a raw '"' in a name used to // terminate the JSON string early, which both broke parsing and allowed a // crafted name to inject additional fields into the frame dictionary. TEST(CodecTest, NumPyMetadataEscapesFrameName) { std::unique_ptr npy_encoder = Encoder::FromExtension(".npy"); ASSERT_TRUE(npy_encoder); const JxlPixelFormat format{/*num_channels=*/1, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0}; PackedPixelFile ppf; ppf.info.xsize = 2; ppf.info.ysize = 2; ppf.info.num_color_channels = 1; ppf.info.bits_per_sample = 32; ppf.info.exponent_bits_per_sample = 8; JXL_TEST_ASSIGN_OR_DIE(PackedFrame frame, PackedFrame::Create(2, 2, format)); memset(frame.color.pixels(), 0, frame.color.pixels_size); frame.name = std::string("q\"b\\n\ne\x01", 8); ppf.frames.emplace_back(std::move(frame)); EncodedImage encoded; ASSERT_TRUE(npy_encoder->Encode(ppf, &encoded, nullptr)); const std::string metadata(encoded.metadata.begin(), encoded.metadata.end()); EXPECT_NE(metadata.find("\"name\": \"q\\\"b\\\\n\\ne\\u0001\""), std::string::npos) << metadata; // A quote inside the name must never end the JSON string. EXPECT_EQ(metadata.find("\"q\"b"), std::string::npos) << metadata; } } // namespace } // namespace extras } // namespace jxl libjxl-libjxl-5a7da83/lib/extras/common.cc000066400000000000000000000043541527504724200205670ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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; for (bool drop_alpha : {false, true}) { if (drop_alpha && basic_info.alpha_bits == 0) continue; for (bool promote_gray : {false, true}) { if (promote_gray && basic_info.num_color_channels != 1) continue; if (current_bit_depth != 0) continue; size_t num_channels = basic_info.num_color_channels; if (promote_gray) num_channels = 3; if (!drop_alpha) num_channels += (basic_info.alpha_bits != 0 ? 1 : 0); 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) { return JXL_FAILURE("no appropriate format found"); } 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-libjxl-5a7da83/lib/extras/common.h000066400000000000000000000012421527504724200204220ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/extras/compressed_icc.cc000066400000000000000000000050171527504724200222560ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include #include #include #include #include #include #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.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/enc_icc_codec.h" #include "lib/jxl/icc_codec.h" #include "lib/jxl/memory_manager_internal.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)); if (*compressed_icc == nullptr) { return JXL_FALSE; } 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)); if (*icc == nullptr) { return JXL_FALSE; } memcpy(*icc, decompressed.data(), *icc_size); return JXL_TRUE; } libjxl-libjxl-5a7da83/lib/extras/compressed_icc_test.cc000066400000000000000000000026441527504724200233200ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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/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-libjxl-5a7da83/lib/extras/dec/000077500000000000000000000000001527504724200175155ustar00rootroot00000000000000libjxl-libjxl-5a7da83/lib/extras/dec/apng.cc000066400000000000000000001356151527504724200207640ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // 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 "lib/extras/dec/apng.h" // IWYU pragma: no_include #include "lib/extras/dec/color_hints.h" #include "lib/extras/packed_image.h" #include "lib/extras/size_constraints.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #if !JPEGXL_ENABLE_APNG namespace jxl { namespace extras { bool CanDecodeAPNG() { return false; } Status DecodeImageAPNG(const Span bytes, const ColorHints& color_hints, PackedPixelFile* ppf, const SizeConstraints* constraints) { return false; } } // namespace extras } // namespace jxl #else // JPEGXL_ENABLE_APNG #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/compiler_specific.h" #include "lib/jxl/base/printf_macros.h" // IWYU pragma: keep #include "lib/jxl/base/rect.h" #include "lib/jxl/base/sanitizers.h" #include "png.h" /* original (unpatched) libpng is ok */ namespace jxl { namespace extras { 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 'cHRM' 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 { // Zero-initialized: rows that libpng never delivers (truncated or corrupt // data) are copied to the output as well. std::vector pixels_storage; size_t pixels_size = 0; std::vector rows; std::atomic has_error{0}; 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_storage.resize(new_size); pixels_size = new_size; } rows.resize(num_rows); for (size_t y = 0; y < num_rows; y++) { rows[y] = pixels_storage.data() + 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 = 1; 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, followed by sRGB, followed finally by gAMA and cHRM. enum class ColorInfoType { NONE = 0, GAMA = 1, CHRM = 2, GAMA_AND_CHRM = 3, SRGB = 4, ICCP = 5, CICP = 6 }; } // 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_ENSURE(ysize <= ctx.frameRaw.rows.size()); 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"); } JXL_ASSIGN_OR_RETURN(Bytes type, chunk.subspan(4, 4)); id = LoadLE32(type.data()); // Cut 'size' and 'type' at front and 'CRC' at the end. JXL_ASSIGN_OR_RETURN(Bytes payload, chunk.subspan(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.IsInside(image_rect)) { // 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); uint8_t bit_depth = png_get_bit_depth(ctx.png_ptr, ctx.info_ptr); SetColorData(ppf, png_get_color_type(ctx.png_ptr, ctx.info_ptr), bit_depth, 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); JxlDataType data_type = bit_depth > 8 ? JXL_TYPE_UINT16 : JXL_TYPE_UINT8; format = { /*num_channels=*/num_channels, /*data_type=*/data_type, /*endianness=*/JXL_BIG_ENDIAN, /*align=*/0, }; size_t bytes_per_sample = (format.data_type == JXL_TYPE_UINT16) ? 2 : 1; bytes_per_pixel = bytes_per_sample * num_channels; size_t row_bytes; size_t total_bytes; if (!SafeMul(image_rect.xsize(), bytes_per_pixel, row_bytes) || !SafeMul(row_bytes, image_rect.ysize(), total_bytes)) { return JXL_FAILURE("Image too big."); } (void)total_bytes; // Calculated only for check. // 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' JXL_ASSIGN_OR_RETURN(Bytes chunk_tail, chunk.subspan(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) { return JXL_FAILURE("Repeated cICP chunk"); } 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) { return JXL_FAILURE("Repeated iCCP chunk"); } if (color_info_type > ColorInfoType::ICCP) { 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; continue; } case MakeTag('s', 'R', 'G', 'B'): if (color_info_type == ColorInfoType::SRGB) { return JXL_FAILURE("Repeated sRGB chunk"); } if (color_info_type > ColorInfoType::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::SRGB; continue; case MakeTag('g', 'A', 'M', 'A'): if (color_info_type == ColorInfoType::GAMA || color_info_type == ColorInfoType::GAMA_AND_CHRM) { return JXL_FAILURE("Repeated gAMA chunk"); } if (color_info_type > ColorInfoType::GAMA_AND_CHRM) { JXL_DEBUG_V(2, "Excessive colorspace definition; gAMA chunk ignored"); continue; } JXL_RETURN_IF_ERROR(DecodeGamaChunk(payload, &ppf->color_encoding)); color_info_type = color_info_type == ColorInfoType::CHRM ? ColorInfoType::GAMA_AND_CHRM : ColorInfoType::GAMA; continue; case MakeTag('c', 'H', 'R', 'M'): if (color_info_type == ColorInfoType::CHRM || color_info_type == ColorInfoType::GAMA_AND_CHRM) { return JXL_FAILURE("Repeated cHRM chunk"); } if (color_info_type > ColorInfoType::GAMA_AND_CHRM) { JXL_DEBUG_V(2, "Excessive colorspace definition; cHRM chunk ignored"); continue; } JXL_RETURN_IF_ERROR(DecodeChrmChunk(payload, &ppf->color_encoding)); color_info_type = color_info_type == ColorInfoType::GAMA ? ColorInfoType::GAMA_AND_CHRM : ColorInfoType::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_sample = PackedImage::BitsPerChannel(new_data.format.data_type) / 8; size_t pixel_stride = bytes_per_sample * new_data.format.num_channels; memcpy( static_cast(new_data.pixels()) + new_data.stride * (y + y0 - py0) + pixel_stride * (x0 - px0), static_cast(pixels.pixels()) + pixels.stride * y, xsize * pixel_stride); } 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; } } // namespace extras } // namespace jxl #endif // JPEGXL_ENABLE_APNG libjxl-libjxl-5a7da83/lib/extras/dec/apng.h000066400000000000000000000014271527504724200206170ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { struct SizeConstraints; namespace extras { class ColorHints; class PackedPixelFile; 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-libjxl-5a7da83/lib/extras/dec/color_description.cc000066400000000000000000000211601527504724200235450ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include #include #include #include "lib/jxl/base/common.h" #include "lib/jxl/base/status.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 start_ = input_->size(); } else { *next = input_->substr(start_, end - start_); start_ = end + 1; } if (next->empty()) return JXL_FAILURE("Missing token"); 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; c->white_point = JXL_WHITE_POINT_CUSTOM; if (str == "D50") { c->white_point_xy[0] = 0.345669; c->white_point_xy[1] = 0.358496; return true; } Tokenizer xy_tokenizer(&str, ';'); 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; // Adobe98 primaries if (str == "Ado") { c->primaries_red_xy[0] = 0.6400; c->primaries_red_xy[1] = 0.3300; c->primaries_green_xy[0] = 0.2100; c->primaries_green_xy[1] = 0.7100; c->primaries_blue_xy[0] = 0.1500; c->primaries_blue_xy[1] = 0.0600; c->primaries = JXL_PRIMARIES_CUSTOM; return true; } // ProPhoto primaries if (str == "Pro") { c->primaries_red_xy[0] = 0.734699; c->primaries_red_xy[1] = 0.265301; c->primaries_green_xy[0] = 0.159597; c->primaries_green_xy[1] = 0.840403; c->primaries_blue_xy[0] = 0.036598; c->primaries_blue_xy[1] = 0.000105; c->primaries = JXL_PRIMARIES_CUSTOM; 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 == "Ado") { c->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA; c->gamma = 256.0 / 563.0; return true; } // This is how it is done in the Adobe ProPhoto space; // others use a transfer function with a short linear segment // (but that cannot be represented as a jxl color encoding) if (str == "Pro") { c->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA; c->gamma = 1.0 / 1.8; 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") { return ParseDescription("RGB_D65_SRG_Rel_SRG", c); } else if (description == "DisplayP3") { return ParseDescription("RGB_D65_DCI_Rel_SRG", c); } else if (description == "Adobe98") { return ParseDescription("RGB_D65_Ado_Rel_Ado", c); } else if (description == "ProPhoto") { return ParseDescription("RGB_D50_Pro_Rel_Pro", c); } else if (description == "Rec2100PQ") { return ParseDescription("RGB_D65_202_Rel_PeQ", c); } else if (description == "Rec2100HLG") { return ParseDescription("RGB_D65_202_Rel_HLG", c); } 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-libjxl-5a7da83/lib/extras/dec/color_description.h000066400000000000000000000012251527504724200234070ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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/compiler_specific.h" #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-libjxl-5a7da83/lib/extras/dec/color_description_test.cc000066400000000000000000000024071527504724200246070ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #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-libjxl-5a7da83/lib/extras/dec/color_hints.cc000066400000000000000000000057521527504724200223600ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include #include "lib/extras/dec/color_description.h" #include "lib/extras/packed_image.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-libjxl-5a7da83/lib/extras/dec/color_hints.h000066400000000000000000000044201527504724200222110ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 "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-libjxl-5a7da83/lib/extras/dec/decode.cc000066400000000000000000000174731527504724200212630ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include #include #include #include #include #include #include #include "lib/extras/dec/apng.h" #include "lib/extras/dec/color_hints.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" #include "lib/extras/packed_image.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.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". Only the final path component can carry an // extension: strip any directory part (both '/' and '\' separators) // first, so that dots inside directory names (e.g. "a.png/../../b") // cannot leak into the returned extension, and a trailing dot that // carries no extension (e.g. "out.png.") is reported as none. const size_t sep = path.find_last_of("/\\"); const std::string base = (sep == std::string::npos) ? path : path.substr(sep + 1); const size_t pos = base.find_last_of('.'); if (pos == std::string::npos || pos == base.size() - 1) { return ""; } return base.substr(pos); } } // 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, JxlMemoryManager* memory_manager, bool coalescing) { 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; Codec codec = DetectCodec(bytes); bool ok = false; switch (codec) { case Codec::kEXR: ok = DecodeImageEXR(bytes, color_hints, ppf, constraints); break; case Codec::kGIF: ok = DecodeImageGIF(bytes, color_hints, ppf, constraints); break; case Codec::kJPG: ok = DecodeImageJPG(bytes, color_hints, ppf, constraints); break; case Codec::kJXL: { JXLDecompressParams dparams = {}; dparams.memory_manager = memory_manager; dparams.coalescing = coalescing; 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; ok = DecodeImageJXL(bytes.data(), bytes.size(), dparams, &decoded_bytes, ppf, nullptr, constraints) && ApplyColorHints(color_hints, true, ppf->info.num_color_channels == 1, ppf); break; } case Codec::kPGX: ok = DecodeImagePGX(bytes, color_hints, ppf, constraints); break; case Codec::kPNG: ok = DecodeImageAPNG(bytes, color_hints, ppf, constraints); break; case Codec::kPNM: ok = DecodeImagePNM(bytes, color_hints, ppf, constraints); break; case Codec::kUnknown: return JXL_FAILURE("Unrecognized codec"); } if (!ok) { return JXL_FAILURE("Codecs failed to decode"); } if (orig_codec) *orig_codec = codec; return true; } template bool CheckSignatures(const Span& bytes, const std::array, N>& signatures) { static_assert(L <= kMinBytes, "Signature too long"); if (bytes.size() < L) return false; for (auto signature : signatures) { if (memcmp(bytes.data(), signature.data(), signature.size()) == 0) { return true; } } return false; } Codec DetectCodec(const Span& bytes) { constexpr std::array, 1> kExrSignatures = {{ {'v', '/', '1', 0x01}, }}; constexpr std::array, 2> kGifSignatures = {{ {'G', 'I', 'F', '8', '7', 'a'}, {'G', 'I', 'F', '8', '9', 'a'}, }}; constexpr std::array, 4> kPgxSignatures = { {{'P', 'G', ' ', 'L', 'M', ' ', '+'}, {'P', 'G', ' ', 'L', 'M', ' ', '-'}, {'P', 'G', ' ', 'M', 'L', ' ', '+'}, {'P', 'G', ' ', 'M', 'L', ' ', '-'}}}; constexpr std::array, 1> kPngSignatures = { {{137, 'P', 'N', 'G', '\r', '\n', 26, '\n'}}}; static const std::array, 9> kPnmSignatures = { {{'P', '1'}, {'P', '2'}, {'P', '3'}, {'P', '4'}, {'P', '5'}, {'P', '6'}, {'P', '7'}, {'P', 'F'}, {'P', 'f'}}}; static const std::array, 5> kJpgSignatures = {{ {0xFF, 0xD8, 0xFF, 0xDB}, {0xFF, 0xD8, 0xFF, 0xE0}, {0xFF, 0xD8, 0xFF, 0xE1}, {0xFF, 0xD8, 0xFF, 0xE2}, {0xFF, 0xD8, 0xFF, 0xEE}, }}; static const std::array, 1> kJxlBoxSignatures = {{ {0x00, 0x00, 0x00, 0x0C, 'J', 'X', 'L', ' ', 0x0D}, }}; static const std::array, 1> kJxlSignatures = {{ {0xFF, 0x0A}, }}; if (CheckSignatures(bytes, kExrSignatures)) return Codec::kEXR; if (CheckSignatures(bytes, kGifSignatures)) return Codec::kGIF; if (CheckSignatures(bytes, kJpgSignatures)) return Codec::kJPG; if (CheckSignatures(bytes, kJxlBoxSignatures)) return Codec::kJXL; if (CheckSignatures(bytes, kJxlSignatures)) return Codec::kJXL; if (CheckSignatures(bytes, kPgxSignatures)) return Codec::kPGX; if (CheckSignatures(bytes, kPngSignatures)) return Codec::kPNG; if (CheckSignatures(bytes, kPnmSignatures)) return Codec::kPNM; return Codec::kUnknown; } } // namespace extras } // namespace jxl libjxl-libjxl-5a7da83/lib/extras/dec/decode.h000066400000000000000000000033121527504724200211100ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include "lib/extras/dec/color_hints.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/compiler_specific.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 }; Codec DetectCodec(const Span& bytes); 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, JxlMemoryManager* memory_manager = nullptr, bool coalescing = true); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_DEC_DECODE_H_ libjxl-libjxl-5a7da83/lib/extras/dec/exr.cc000066400000000000000000000505721527504724200206330ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 // JXL_CHANNEL_OPTIONAL #include #include "lib/extras/dec/color_hints.h" #include "lib/extras/packed_image.h" #include "lib/extras/size_constraints.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 #include #include #include #include #include #include #include #include #include #include #include #include "lib/jxl/base/common.h" #include "lib/jxl/base/compiler_specific.h" #ifdef __EXCEPTIONS #include #define JXL_EXR_THROW_LENGTH_ERROR(M) throw Iex::InputExc(M); #else // __EXCEPTIONS #define JXL_EXR_THROW_LENGTH_ERROR(M) 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()); 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) {} // Do not advertise memory mapping. When an OpenEXR::IStream reports itself // as memory-mapped, OpenEXR reads a scanline's pixel data straight from the // pointer returned by readMemoryMapped(), using the size derived from the // image header rather than the size of the on-disk chunk. A file whose chunk // declares fewer bytes than a full scanline then makes OpenEXR read past the // end of our input buffer (a heap out-of-bounds read reachable from cjxl on // an untrusted .exr file). The copying read() path below hands OpenEXR a // buffer that it sizes itself, so an over-read stays within OpenEXR's own // allocation and is caught by its chunk-size validation instead. The data is // already in memory, so this costs one extra copy per scanline and no I/O. bool isMemoryMapped() const override { return false; } char* readMemoryMapped(const int n) override { if (pos_ + n < pos_) { JXL_EXR_THROW_LENGTH_ERROR("Overflow"); } if (pos_ + n > bytes_.size()) { JXL_EXR_THROW_LENGTH_ERROR("Read past end of file"); } char* const result = const_cast(reinterpret_cast(bytes_.data() + pos_)); pos_ += n; return result; } bool read(char c[/*n*/], int n) override { // That is not stated in documentation, but the OpenEXR code expects that // when requested amount is not accessible and exception is thrown, all // the accessible data is read. if (pos_ + n < pos_) { JXL_EXR_THROW_LENGTH_ERROR("Overflow"); } if (pos_ + n > bytes_.size()) { int can_read = static_cast(bytes_.size() - pos_); std::copy_n(readMemoryMapped(can_read), can_read, c); JXL_EXR_THROW_LENGTH_ERROR("Read past end of file"); } else { 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("Seeks past end of file"); } pos_ = pos; } private: const Span bytes_; size_t pos_ = 0; }; } // namespace bool CanDecodeEXR() { return true; } static std::string FindColorLayerPrefix(const OpenEXR::ChannelList& channels) { // check if just R,G,B channels exist; use those if present if (channels.findChannel("R") != nullptr && channels.findChannel("G") != nullptr && channels.findChannel("B") != nullptr) { return ""; } // check channels for presence of R,G,B with common prefix ("layer name"), // use the first one that is present for (OpenEXR::ChannelList::ConstIterator it = channels.begin(); it != channels.end(); ++it) { std::string name = it.name(); size_t dotIndex = name.rfind('.'); if (dotIndex == std::string::npos) continue; std::string suffix = name.substr(dotIndex + 1); if (suffix != "R" && suffix != "G" && suffix != "B") continue; std::string prefix = name.substr(0, dotIndex + 1); if (channels.findChannel((prefix + "R").c_str()) != nullptr && channels.findChannel((prefix + "G").c_str()) != nullptr && channels.findChannel((prefix + "B").c_str()) != nullptr) { return prefix; } } return ""; } 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::InputFile& input = *input_ptr; #else OpenEXR::InputFile input(is); #endif const OpenEXR::Header& header = input.header(); const OpenEXR::ChannelList& channels = header.channels(); // we don't support subsampled or UINT channels yet // TODO: support common cases of subsampling (2x, 4x) for (OpenEXR::ChannelList::ConstIterator it = channels.begin(); it != channels.end(); ++it) { const OpenEXR::Channel& ch = it.channel(); if (ch.type == OpenEXR::UINT) { return JXL_FAILURE("OpenEXR files with UINT channels are not supported"); } if (ch.xSampling != 1 || ch.ySampling != 1) { return JXL_FAILURE( "OpenEXR files sub-sampled channels are not supported"); } } const std::string color_prefix = FindColorLayerPrefix(channels); const std::string ch_name_r = color_prefix + "R"; const OpenEXR::Channel* ch_r = channels.findChannel(ch_name_r.c_str()); const std::string ch_name_g = color_prefix + "G"; const OpenEXR::Channel* ch_g = channels.findChannel(ch_name_g.c_str()); const std::string ch_name_b = color_prefix + "B"; const OpenEXR::Channel* ch_b = channels.findChannel(ch_name_b.c_str()); const std::string ch_name_a = color_prefix + "A"; const OpenEXR::Channel* ch_a = channels.findChannel(ch_name_a.c_str()); // If we don't have RGB (same type) channels, we'll treat the first // channel as grayscale. bool has_rgb = (ch_r != nullptr) && (ch_g != nullptr) && (ch_b != nullptr); bool is_gray = !has_rgb; const OpenEXR::Channel* ch_gray = is_gray ? &channels.begin().channel() : nullptr; const std::string ch_name_gray = is_gray ? channels.begin().name() : ""; const auto color_type = (is_gray ? ch_gray : ch_r)->type; if (has_rgb) { if (ch_g->type != color_type || ch_b->type != color_type) { return JXL_FAILURE( "OpenEXR color channels with different types are not supported yet"); } } bool has_alpha = (ch_a != nullptr) && (ch_a != ch_gray); if (has_alpha) { if (ch_a->type != color_type) { return JXL_FAILURE( "OpenEXR color channels with different types are not supported yet"); } } const float intensity_target = OpenEXR::hasWhiteLuminance(header) ? OpenEXR::whiteLuminance(header) : 0; const Imath::Box2i display_window = header.displayWindow(); const Imath::Box2i data_window = header.dataWindow(); // display_window / data_window bounds come straight from the EXR file header // and are not otherwise constrained: an arbitrary range of ints is legal in // the file format. Reject coordinates outside +/-2^30 to keep the pointer // arithmetic below sane. JXL level 10 already caps image sizes at 2^30, so // this is not a real-world limitation. The inclusive window sizes are still // computed in 64 bits, because the span of two in-range coordinates can // reach 2^31, which does not fit in `int`. constexpr int kEXRCoordBound = 1 << 30; auto OutOfRange = [](int v) { return v < -kEXRCoordBound || v > kEXRCoordBound; }; if (OutOfRange(display_window.min.x) || OutOfRange(display_window.max.x) || OutOfRange(display_window.min.y) || OutOfRange(display_window.max.y) || OutOfRange(data_window.min.x) || OutOfRange(data_window.max.x) || OutOfRange(data_window.min.y) || OutOfRange(data_window.max.y)) { return JXL_FAILURE("EXR: window coordinates out of range"); } // TODO(eustas): empty data_window could be valid use case. if (display_window.isEmpty() || data_window.isEmpty()) { return JXL_FAILURE("EXR: empty window"); } // Size is computed as max - min, but both bounds are inclusive. Compute in // 64 bits: `Box2i::size()` subtracts two `int` coordinates, which overflows // when the (in-range) span reaches 2^31. const int64_t image_width = static_cast(display_window.max.x) - display_window.min.x + 1; const int64_t image_height = static_cast(display_window.max.y) - display_window.min.y + 1; if (!VerifyDimensions(constraints, image_width, image_height)) { return JXL_FAILURE("image too big"); } // Apply the same constraints to data_window, since its dimensions drive // input buffer allocations and per-row pointer arithmetic below. const int64_t data_width = static_cast(data_window.max.x) - data_window.min.x + 1; const int64_t data_height = static_cast(data_window.max.y) - data_window.min.y + 1; if (!VerifyDimensions(constraints, data_width, data_height)) { return JXL_FAILURE("EXR: data_window too big"); } // https://www.openexr.com/documentation/ReadingAndWritingImageFiles.pdf // recommends reading the whole file at once. size_t num_pixels; // Width must correspond to data scanlines in file. // TODO(eustas): if projection of data_window on X axis is inside of // projection of display_window, then we can avoid extra memcpy. if (!SafeMul(image_height, data_width, num_pixels)) { return JXL_FAILURE("EXR: image too big"); } // Intersect data and display window. const int x1 = std::max(data_window.min.x, display_window.min.x); const int x2 = std::min(data_window.max.x, display_window.max.x); const int y1 = std::max(data_window.min.y, display_window.min.y); const int y2 = std::min(data_window.max.y, display_window.max.y); const int x_span = x2 - x1 + 1; const int y_span = y2 - y1 + 1; const auto get_pixel_type = [](OpenEXR::PixelType t) -> JxlDataType { return (t == OpenEXR::HALF) ? JXL_TYPE_FLOAT16 : JXL_TYPE_FLOAT; }; const auto get_pixel_stride = [](OpenEXR::PixelType t) -> size_t { return (t == OpenEXR::HALF) ? 2 : 4; }; const auto get_bpp = [](OpenEXR::PixelType t) -> size_t { return (t == OpenEXR::HALF) ? 16 : 32; }; const auto get_exponent_bits = [](OpenEXR::PixelType t) -> size_t { return (t == OpenEXR::HALF) ? 5 : 8; }; uint32_t num_color_channels = is_gray ? 1 : 3; ppf->info.xsize = image_width; ppf->info.ysize = image_height; ppf->info.num_color_channels = num_color_channels; const JxlPixelFormat format{ /*num_channels=*/num_color_channels + (has_alpha ? 1u : 0u), /*data_type=*/get_pixel_type(color_type), /*endianness=*/JXL_NATIVE_ENDIAN, /*align=*/0, }; ppf->frames.clear(); // Allocates the frame buffer. { JXL_ASSIGN_OR_RETURN( PackedFrame frame, PackedFrame::Create(image_width, image_height, format)); ppf->frames.emplace_back(std::move(frame)); } auto& frame = ppf->frames.back(); // Allocate extra channel images. std::set ec_set; std::vector > ec_data; for (OpenEXR::ChannelList::ConstIterator it = channels.begin(); it != channels.end(); ++it) { const std::string name = it.name(); // Skip {RGB|Gray}(A) if (is_gray && (name == ch_name_gray)) continue; if (has_alpha && (name == ch_name_a)) continue; if (has_rgb) { if ((name == ch_name_r) || (name == ch_name_g) || (name == ch_name_b)) { continue; } } const OpenEXR::Channel* ch = &it.channel(); OpenEXR::PixelType t = ch->type; ec_set.insert(ch); size_t pixel_stride = get_pixel_stride(t); size_t volume; if (!SafeMul(pixel_stride, num_pixels, volume)) { return JXL_FAILURE("EXR: image too big"); } std::vector storage(volume); ec_data.emplace_back(std::move(storage)); const JxlPixelFormat ec_format{/*num_channels=*/1, /*data_type=*/get_pixel_type(t), /*endianness=*/JXL_NATIVE_ENDIAN, /*align=*/0}; JXL_ASSIGN_OR_RETURN( PackedImage ec, PackedImage::Create(image_width, image_height, ec_format)); frame.extra_channels.emplace_back(std::move(ec)); JXL_DASSERT(frame.extra_channels.back().pixel_stride() == pixel_stride); PackedExtraChannel pec = {}; pec.ec_info.bits_per_sample = get_bpp(t); pec.ec_info.exponent_bits_per_sample = get_exponent_bits(t); // TODO: detect channel types (depth etc.) based on naming convention pec.ec_info.type = JXL_CHANNEL_OPTIONAL; pec.name = name; ppf->extra_channels_info.emplace_back(std::move(pec)); } ppf->info.num_extra_channels = (has_alpha ? 1 : 0) + frame.extra_channels.size(); const size_t color_channel_bytes = get_pixel_stride(color_type); const size_t color_pixel_bytes = color_channel_bytes * format.num_channels; size_t color_data_size; if (!SafeMul(color_pixel_bytes, num_pixels, color_data_size)) { return JXL_FAILURE("EXR: image too big"); } // Interleaved RGB{A} / Gray{A} std::vector color_data(color_data_size); // If intersection is empty, then image is just zeroes. if (x_span > 0 && y_span > 0) { // Setup framebuffer: color/grayscale OpenEXR::FrameBuffer fb; size_t x_stride = color_pixel_bytes; size_t y_stride = x_stride * static_cast(data_width); char* virtual_image_origin = color_data.data(); // Offset to match output to allocation start; when EXR puts pixel at // (data_window.min.x, y1) it goes to 0-th element. virtual_image_origin -= static_cast(y1) * static_cast(y_stride); virtual_image_origin -= static_cast(data_window.min.x) * static_cast(x_stride); if (has_rgb) { fb.insert(ch_name_r.c_str(), OpenEXR::Slice(color_type, virtual_image_origin + color_channel_bytes * 0, x_stride, y_stride)); fb.insert(ch_name_g.c_str(), OpenEXR::Slice(color_type, virtual_image_origin + color_channel_bytes * 1, x_stride, y_stride)); fb.insert(ch_name_b.c_str(), OpenEXR::Slice(color_type, virtual_image_origin + color_channel_bytes * 2, x_stride, y_stride)); } else { fb.insert(ch_name_gray.c_str(), OpenEXR::Slice(color_type, virtual_image_origin + color_channel_bytes * 0, x_stride, y_stride)); } // Setup framebuffer: alpha if (has_alpha) { fb.insert(ch_name_a.c_str(), OpenEXR::Slice(color_type, virtual_image_origin + color_channel_bytes * (has_rgb ? 3 : 1), x_stride, y_stride)); } // Setup framebuffer: extra channels size_t ec_data_idx = 0; for (OpenEXR::ChannelList::ConstIterator it = channels.begin(); it != channels.end(); ++it) { const OpenEXR::Channel* ch = &it.channel(); if (ec_set.find(ch) == ec_set.end()) { continue; } auto& ec = ec_data[ec_data_idx++]; size_t ec_x_stride = get_pixel_stride(ch->type); size_t ec_y_stride = ec_x_stride * static_cast(data_width); char* ec_virtual_image_origin = ec.data(); // Offset to match output to allocation start; when EXR puts pixel at // (data_window.min.x, y1) it goes to 0-th element. ec_virtual_image_origin -= static_cast(y1) * static_cast(ec_y_stride); ec_virtual_image_origin -= static_cast(data_window.min.x) * static_cast(ec_x_stride); fb.insert(it.name(), OpenEXR::Slice(ch->type, ec_virtual_image_origin, ec_x_stride, ec_y_stride)); } // Read EXR data input.setFrameBuffer(fb); input.readPixels(y1, y2); const int x_data = x1 - data_window.min.x; const int x_out = x1 - display_window.min.x; JXL_DASSERT(x_out >= 0); JXL_DASSERT(x_out + x_span <= image_width); // Copy read data into the result image. // TODO(eustas): should we deal with unpopulated pixels? for (int y = y1; y <= y2; ++y) { // Scanline index const int y_data = y - y1; const int y_out = y - display_window.min.y; JXL_DASSERT(y_out >= 0); JXL_DASSERT(y_out < image_height); const char* const JXL_RESTRICT data_ptr = &color_data[x_data * color_pixel_bytes + y_data * y_stride]; uint8_t* pixels = static_cast(frame.color.pixels()); uint8_t* image_ptr = pixels + x_out * color_pixel_bytes + y_out * frame.color.stride; memcpy(image_ptr, data_ptr, x_span * color_pixel_bytes); for (size_t ec_idx = 0; ec_idx < frame.extra_channels.size(); ++ec_idx) { PackedImage& ec = frame.extra_channels[ec_idx]; auto& data = ec_data[ec_idx]; size_t ec_x_stride = ec.pixel_stride(); size_t ec_y_stride = ec_x_stride * static_cast(data_width); const char* const JXL_RESTRICT ec_data_ptr = &data[x_data * ec_x_stride + y_data * ec_y_stride]; uint8_t* ec_pixels = static_cast(ec.pixels()); uint8_t* ec_image_ptr = ec_pixels + x_out * ec_x_stride + y_out * ec.stride; memcpy(ec_image_ptr, ec_data_ptr, x_span * ec_x_stride); } } } ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR; ppf->color_encoding.color_space = has_rgb ? JXL_COLOR_SPACE_RGB : JXL_COLOR_SPACE_GRAY; ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB; ppf->color_encoding.white_point = JXL_WHITE_POINT_D65; if (OpenEXR::hasChromaticities(header)) { ppf->color_encoding.primaries = JXL_PRIMARIES_CUSTOM; ppf->color_encoding.white_point = JXL_WHITE_POINT_CUSTOM; const auto& chromaticities = OpenEXR::chromaticities(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 = get_bpp(color_type); ppf->info.exponent_bits_per_sample = get_exponent_bits(color_type); if (has_alpha) { ppf->info.alpha_bits = ppf->info.bits_per_sample; 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-libjxl-5a7da83/lib/extras/dec/exr.h000066400000000000000000000014541527504724200204700ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { struct SizeConstraints; namespace extras { class ColorHints; class PackedPixelFile; 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-libjxl-5a7da83/lib/extras/dec/gif.cc000066400000000000000000000374351527504724200206050ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include "lib/extras/dec/color_hints.h" #include "lib/extras/packed_image.h" #include "lib/extras/size_constraints.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #if !JPEGXL_ENABLE_GIF namespace jxl { namespace extras { bool CanDecodeGIF() { return false; } Status DecodeImageGIF(Span bytes, const ColorHints& color_hints, PackedPixelFile* ppf, const SizeConstraints* constraints) { return false; } } // namespace extras } // namespace jxl #else // JPEGXL_ENABLE_GIF #include #include #include #include #include #include #include #include #include "lib/jxl/base/rect.h" #include "lib/jxl/base/sanitizers.h" namespace jxl { namespace extras { 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 bool CanDecodeGIF() { return true; } Status DecodeImageGIF(Span bytes, const ColorHints& color_hints, PackedPixelFile* ppf, const SizeConstraints* constraints) { 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)) { if (!JXL_IS_DEBUG_BUILD) { fprintf(stderr, "GIF with dispose-to-0 is not supported for non-full or blended " "frames\n"); } 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; } } // namespace extras } // namespace jxl #endif // JPEGXL_ENABLE_GIF libjxl-libjxl-5a7da83/lib/extras/dec/gif.h000066400000000000000000000014501527504724200204330ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { struct SizeConstraints; namespace extras { class ColorHints; class PackedPixelFile; 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-libjxl-5a7da83/lib/extras/dec/jpg.cc000066400000000000000000000311161527504724200206060ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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" #include #include "lib/extras/dec/color_hints.h" #include "lib/extras/packed_image.h" #include "lib/extras/size_constraints.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #if !JPEGXL_ENABLE_JPEG namespace jxl { namespace extras { bool CanDecodeJPG() { return false; } Status DecodeImageJPG(const Span bytes, const ColorHints& color_hints, PackedPixelFile* ppf, const SizeConstraints* constraints, const JPGDecompressParams* dparams) { return false; } } // namespace extras } // namespace jxl #else // JPEGXL_ENABLE_JPEG #include #include #include #include #include #include #include #include #include #include "lib/extras/include_jpeglib.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/sanitizers.h" namespace jxl { namespace extras { 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 bool CanDecodeJPG() { return true; } Status DecodeImageJPG(const Span bytes, const ColorHints& color_hints, PackedPixelFile* ppf, const SizeConstraints* constraints, const JPGDecompressParams* dparams) { // 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, "Only 8/16 bit samples are supported"); 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(); } } // namespace extras } // namespace jxl #endif // JPEGXL_ENABLE_JPEG libjxl-libjxl-5a7da83/lib/extras/dec/jpg.h000066400000000000000000000022461527504724200204520ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { struct SizeConstraints; namespace extras { class ColorHints; class PackedPixelFile; 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-libjxl-5a7da83/lib/extras/dec/jxl.cc000066400000000000000000000627621527504724200206360ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include // PRIu32 #include #include #include #include #include #include #include #include #include "lib/extras/common.h" #include "lib/extras/dec/color_description.h" #include "lib/extras/exif.h" #include "lib/extras/packed_image.h" #include "lib/extras/size_constraints.h" #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/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, const SizeConstraints* constraints) { 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; bool set_colorspace = false; 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; } set_colorspace = true; 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 (JXL_DEC_SUCCESS != JxlDecoderSetCoalescing(dec, TO_JXL_BOOL(dparams.coalescing))) { fprintf(stderr, "JxlDecoderSetCoalescing 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); uint64_t total_pixel_count = 0; 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 (!VerifyDimensions(constraints, ppf->xsize(), ppf->ysize())) { fprintf(stderr, "Image too big\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); size_t format_color_channels = have_alpha ? format.num_channels - 1 : format.num_channels; if (format_color_channels > ppf->info.num_color_channels) { ppf->info.num_color_channels = format_color_channels; } 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; } if (eci.type == JXL_CHANNEL_BLACK && !set_colorspace && !dparams.color_space_for_cmyk.empty()) { if (!jxl::ParseDescription(dparams.color_space_for_cmyk, &color_encoding)) { fprintf(stderr, "Failed to parse color space %s.\n", dparams.color_space_for_cmyk.c_str()); return false; } set_colorspace = true; } 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 (set_colorspace) { JxlDecoderSetCms(dec, *JxlGetDefaultCms()); if (JXL_DEC_SUCCESS != JxlDecoderSetOutputColorProfile(dec, &color_encoding, nullptr, 0)) { fprintf(stderr, "Failed to set color space.\n"); return false; } ppf->color_encoding = color_encoding; } 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; } } ppf->primary_color_representation = PackedPixelFile::kColorEncodingIsPrimary; if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsEncodedProfile( dec, target, &ppf->color_encoding)) { ppf->color_encoding.color_space = JXL_COLOR_SPACE_UNKNOWN; ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary; } 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; } } } else if (status == JXL_DEC_FRAME) { if (!VerifyDimensions(constraints, ppf->xsize(), ppf->ysize())) { fprintf(stderr, "Image too big\n"); return false; } uint64_t file_px = static_cast(ppf->xsize()) * ppf->ysize(); JxlFrameHeader fh; if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec, &fh)) { fprintf(stderr, "JxlDecoderGetFrameHeader failed\n"); return false; } if (!VerifyDimensions(constraints, fh.layer_info.xsize, fh.layer_info.ysize)) { fprintf(stderr, "Image too big\n"); return false; } uint64_t frame_px = static_cast(fh.layer_info.xsize) * fh.layer_info.ysize; total_pixel_count += std::max(file_px, frame_px); if (constraints && (total_pixel_count > constraints->dec_max_pixels)) { return JXL_FAILURE("Image too big"); } JXL_ASSIGN_OR_QUIT(jxl::extras::PackedFrame frame, jxl::extras::PackedFrame::Create( fh.layer_info.xsize, fh.layer_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( frame.frame_info.layer_info.xsize, frame.frame_info.layer_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 ec_buffer_size; if (JXL_DEC_SUCCESS != JxlDecoderExtraChannelBufferSize(dec, &ec_format, &ec_buffer_size, eci.index)) { fprintf(stderr, "JxlDecoderExtraChannelBufferSize failed\n"); return false; } if (ec_buffer_size != ec.pixels_size) { fprintf(stderr, "Invalid extra channel buffer size" " %" PRIuS " %" PRIuS "\n", ec_buffer_size, ec.pixels_size); return false; } if (JXL_DEC_SUCCESS != JxlDecoderSetExtraChannelBuffer(dec, &ec_format, ec.pixels(), ec_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. // 16 = 4 + 12 = offset + min EXIF payload. if (ppf->metadata.exif.size() >= 16) { uint32_t offset = LoadBE32(ppf->metadata.exif.data()); if (offset <= ppf->metadata.exif.size() - 16) { 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); if (jpeg_bytes == nullptr && !dparams.keep_orientation) { // when decoding to pixels and orientation is undone during decode, // reset exif orientation to avoid double orientation ResetExifOrientation(ppf->metadata.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-libjxl-5a7da83/lib/extras/dec/jxl.h000066400000000000000000000051111527504724200204610ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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/size_constraints.h" namespace jxl { namespace extras { class PackedPixelFile; struct JXLDecompressParams { // If empty, little endian float formats will be accepted. std::vector accepted_formats; // Requested output color space description. std::string color_space; // Requested output color space description in case of CMYK images. std::string color_space_for_cmyk; // 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; // Coalescing or not bool coalescing = true; // 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, const SizeConstraints* constraints = nullptr); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_DEC_JXL_H_ libjxl-libjxl-5a7da83/lib/extras/dec/pgx.cc000066400000000000000000000155511527504724200206310ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include #include "lib/extras/dec/color_hints.h" #include "lib/extras/packed_image.h" #include "lib/extras/size_constraints.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.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) { if (static_cast(end_ - pos_) < 2) return false; 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') { const size_t digit = static_cast(*pos_ - '0'); if (*number > (std::numeric_limits::max() - digit) / 10) { return JXL_FAILURE("PGX: number too large"); } *number = *number * 10 + digit; ++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"); } const size_t bytes_per_pixel = header->bits_per_sample <= 8 ? 1 : 2; size_t numpixels; size_t bytes_needed; if (!SafeMul(header->xsize, header->ysize, numpixels) || !SafeMul(numpixels, bytes_per_pixel, bytes_needed)) { return JXL_FAILURE("PGX: image dimensions are too large"); } if (static_cast(end_ - pos_) < bytes_needed) { 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-libjxl-5a7da83/lib/extras/dec/pgx.h000066400000000000000000000013711527504724200204660ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { struct SizeConstraints; namespace extras { class ColorHints; class PackedPixelFile; // 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-libjxl-5a7da83/lib/extras/dec/pgx_test.cc000066400000000000000000000120221527504724200216560ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include #include #include "lib/extras/packed_image.h" #include "lib/extras/packed_image_convert.h" #include "lib/jxl/base/common.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) { JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); std::string pgx = "PG ML + 8 2 3\npixels"; PackedPixelFile ppf; ThreadPool* pool = nullptr; EXPECT_TRUE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(), &ppf)); auto io = jxl::make_unique(memory_manager); EXPECT_TRUE(ConvertPackedPixelFileToCodecInOut(ppf, pool, io.get())); 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; std::array, 3> expected = { {{'p', 'i'}, {'x', 'e'}, {'l', 's'}}}; for (size_t y = 0; y < 3; ++y) { for (size_t x = 0; x < 2; ++x) { EXPECT_NEAR(expected[y][x], io->Main().color()->Plane(0).Row(y)[x], eps); } } } TEST(CodecPGXTest, Test16bits) { JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); 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)); auto io = jxl::make_unique(memory_manager); EXPECT_TRUE(ConvertPackedPixelFileToCodecInOut(ppf, pool, io.get())); 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; std::array, 3> expected = { {{'p', 'i'}, {'x', 'e'}, {'l', 's'}}}; for (size_t y = 0; y < 3; ++y) { for (size_t x = 0; x < 2; ++x) { float expected_value = 256.0f * expected[y][x] + '_'; EXPECT_NEAR(expected_value, io->Main().color()->Plane(0).Row(y)[x] * 257, eps); } } } // Crafted PGX with > 19 digit dimension: without the ParseUnsigned overflow // guard, the size_t multiplication during ASCII parsing silently wraps. The // decoder must reject this before any size arithmetic runs. TEST(CodecPGXTest, RejectsDimensionsThatOverflowParseUnsigned) { std::string pgx = "PG ML + 8 99999999999999999999999 2\np"; PackedPixelFile ppf; EXPECT_FALSE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(), &ppf)); } // Crafted PGX whose individual dimensions fit in size_t but whose product // xsize*ysize wraps. Without SafeMul on the data-size check, the data-too- // small test can be bypassed and the decoder relies entirely on // VerifyDimensions to catch it. With SafeMul, the parser rejects up front. TEST(CodecPGXTest, RejectsDimensionProductOverflow) { // 2^33 * 2^33 = 2^66, wraps to 0 in size_t (on 64-bit). On 32-bit either // dimension alone already overflows ParseUnsigned and is rejected there. std::string pgx = "PG ML + 8 8589934592 8589934592\n"; PackedPixelFile ppf; EXPECT_FALSE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(), &ppf)); } // The two-byte "PG" signature was read before any range check, relying on a // minimum input size enforced in a different translation unit // (extras/dec/decode.cc). DecodeImagePGX does not enforce that itself, so a // caller reaching it directly - as these tests do - read past the end of the // buffer. ParseHeader must enforce its own precondition. // // The buffers are heap-allocated at exactly the tested length so sanitizer // builds observe any overread; a string literal would hide it inside the // terminating NUL. Length 1 is checked first because it is the case a // sanitizer reports precisely - for length 0 an empty vector may yield a null // data() pointer, whose behaviour is not guaranteed across standard libraries. TEST(CodecPGXTest, RejectsInputShorterThanSignature) { PackedPixelFile ppf; for (const size_t len : {static_cast(1), static_cast(0)}) { const std::vector bytes(len, static_cast('P')); EXPECT_FALSE( DecodeImagePGX(Bytes(bytes.data(), bytes.size()), ColorHints(), &ppf)); } } } // namespace } // namespace extras } // namespace jxl libjxl-libjxl-5a7da83/lib/extras/dec/pnm.cc000066400000000000000000000543741527504724200206330ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include #include #include #include "lib/extras/dec/color_hints.h" #include "lib/extras/mmap.h" #include "lib/extras/packed_image.h" #include "lib/extras/size_constraints.h" #include "lib/jxl/base/bits.h" #include "lib/jxl/base/c_callback_support.h" #include "lib/jxl/base/common.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) { if (static_cast(end_ - pos_) < 2) return false; if (pos_[0] != 'P') return false; const uint8_t type = pos_[1]; pos_ += 2; switch (type) { case '1': return JXL_FAILURE("ascii pbm not supported"); case '2': return JXL_FAILURE("ascii pgm not supported"); case '3': return JXL_FAILURE("ascii ppm not supported"); 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') { const size_t digit = *pos_ - '0'; if (!SafeMul(*number, static_cast(10), *number) || !SafeAdd(*number, digit, *number)) { return JXL_FAILURE("PNM: unsigned number too large"); } ++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_; }; // Netpbm requires every sample to be in [0, MaxVal]. MaxVal is 2^bits - 1, so // a sample is out of range iff it has a bit set at or above `bits_per_sample`. // Samples are big-endian, so for 16-bit storage only the first byte of each // sample can hold such bits. `data` holds `size` bytes of integer samples. Status VerifySampleRange(const uint8_t* data, size_t size, size_t bits_per_sample) { // Every value of the storage type is a valid sample. if (bits_per_sample == 8 || bits_per_sample == 16) return true; // OR the input 8 bytes at a time: byte k of `acc` is then the OR of all // bytes at offsets equal to k modulo 8. uint64_t acc = 0; size_t i = 0; for (; i + 8 <= size; i += 8) { uint64_t v; memcpy(&v, data + i, 8); acc |= v; } uint8_t acc_bytes[8]; memcpy(acc_bytes, &acc, 8); for (; i < size; ++i) acc_bytes[i % 8] |= data[i]; const size_t step = bits_per_sample > 8 ? 2 : 1; const size_t shift = bits_per_sample > 8 ? bits_per_sample - 8 : bits_per_sample; uint8_t bits = 0; for (size_t k = 0; k < 8; k += step) bits |= acc_bytes[k]; if (bits >> shift) return JXL_FAILURE("PNM: sample value exceeds MaxVal"); return true; } } // 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, const SizeConstraints* constraints) { 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"); } JXL_RETURN_IF_ERROR( VerifyDimensions(constraints, header.xsize, header.ysize)); 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; if (!SafeMul(dec.header_.xsize, bytes_per_pixel, row_size)) { return JXL_FAILURE("PNM image dimensions are too large"); } size_t required_size; if (!SafeMul(header.ysize, row_size, required_size) || !SafeAdd(required_size, dec.data_start_, required_size)) { return JXL_FAILURE("PNM image dimensions are too large"); } if (size < required_size) { return JXL_FAILURE("PNM file too small"); } JXL_RETURN_IF_ERROR(VerifySampleRange(dec.pnm_.data() + dec.data_start_, required_size - dec.data_start_, header.bits_per_sample)); 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; } } // No align - pixels are tightly packed. constexpr size_t kAlign = 0; size_t twidth = PackedImage::BitsPerChannel(data_type) / 8; const JxlPixelFormat format{ /*num_channels=*/num_interleaved_channels, /*data_type=*/data_type, /*endianness=*/header.big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN, kAlign, }; // EC format is same as color, but 1-channel. JxlPixelFormat ec_format = format; ec_format.num_channels = 1; // Compute required pixel-data size with overflow checks. Without these, // a crafted header (large xsize/ysize) wraps the multiplication and lets // the size check below pass while the actual memcpy below reads OOB. size_t total_channels = num_interleaved_channels + header.ec_types.size(); size_t required_pnm_size; if (!SafeMul(header.xsize, total_channels, required_pnm_size) || !SafeMul(required_pnm_size, twidth, required_pnm_size) || !SafeMul(required_pnm_size, header.ysize, required_pnm_size)) { return JXL_FAILURE("PNM image dimensions are too large"); } size_t pnm_remaining_size = bytes.data() + bytes.size() - pos; if (pnm_remaining_size < required_pnm_size) { return JXL_FAILURE("PNM file too small"); } if (!header.floating_point) { JXL_RETURN_IF_ERROR( VerifySampleRange(pos, required_pnm_size, header.bits_per_sample)); } 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(); uint8_t* out = reinterpret_cast(frame->color.pixels()); std::vector ec_out; 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)); ec_out.emplace_back( reinterpret_cast(frame->extra_channels.back().pixels())); JXL_DASSERT(frame->extra_channels.back().stride == header.xsize * twidth); } JXL_DASSERT(frame->color.stride == header.xsize * num_interleaved_channels * twidth); if (ec_out.empty()) { const bool flipped_y = (header.bits_per_sample == 32); // PFMs are flipped if (!flipped_y) { // When there are no EC and input is not flipped we can copy the whole // image at once. memcpy(out, pos, header.ysize * frame->color.stride); } else { // Otherwise copy row-by-row. for (size_t y = 0; y < header.ysize; ++y) { size_t y_out = header.ysize - 1 - y; const uint8_t* row_in = pos + y * frame->color.stride; uint8_t* row_out = out + y_out * frame->color.stride; memcpy(row_out, row_in, frame->color.stride); } } } else { // In case there are EC, we have to deinterleave data pixel-wise. JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(data_type)); size_t color_stride = twidth * num_interleaved_channels; 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 += color_stride; pos += color_stride; for (auto& p : ec_out) { memcpy(p, pos, twidth); pos += twidth; p += twidth; } } } } 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-libjxl-5a7da83/lib/extras/dec/pnm.h000066400000000000000000000033001527504724200204540ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include // TODO(janwas): workaround for incorrect Win64 codegen (cause unknown) #include #include #include "lib/extras/mmap.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" namespace jxl { struct SizeConstraints; namespace extras { class ColorHints; class PackedPixelFile; // 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, const SizeConstraints* constraints = nullptr); // 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-libjxl-5a7da83/lib/extras/dec/pnm_test.cc000066400000000000000000000041161527504724200216570ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 "lib/extras/dec/color_hints.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/span.h" #include "lib/jxl/testing.h" namespace jxl { namespace extras { namespace { Span MakeSpan(const char* str) { return Bytes(reinterpret_cast(str), strlen(str)); } // The two-byte "P" signature was read before any range check, relying on // a minimum input size enforced in a different translation unit // (extras/dec/decode.cc). DecodeImagePNM does not enforce that itself, so a // caller reaching it directly - as these tests do - read past the end of the // buffer. The out-of-bounds byte then selected the header variant in the switch // that follows. ParseHeader must enforce its own precondition. // // The buffers are heap-allocated at exactly the tested length so sanitizer // builds observe any overread; a string literal would hide it inside the // terminating NUL. Length 1 is checked first because it is the case a // sanitizer reports precisely - for length 0 an empty vector may yield a null // data() pointer, whose behaviour is not guaranteed across standard libraries. TEST(CodecPNMTest, RejectsInputShorterThanSignature) { PackedPixelFile ppf; for (const size_t len : {static_cast(1), static_cast(0)}) { const std::vector bytes(len, static_cast('P')); EXPECT_FALSE( DecodeImagePNM(Bytes(bytes.data(), bytes.size()), ColorHints(), &ppf)); } } // A complete signature with nothing after it must still be rejected, and must // not read beyond the two bytes it consumed. TEST(CodecPNMTest, RejectsSignatureOnly) { PackedPixelFile ppf; const std::string pnm = "P6"; EXPECT_FALSE(DecodeImagePNM(MakeSpan(pnm.c_str()), ColorHints(), &ppf)); } } // namespace } // namespace extras } // namespace jxl libjxl-libjxl-5a7da83/lib/extras/enc/000077500000000000000000000000001527504724200175275ustar00rootroot00000000000000libjxl-libjxl-5a7da83/lib/extras/enc/apng.cc000066400000000000000000000462411527504724200207720ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // 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 "lib/extras/enc/apng.h" // IWYU pragma: no_include #include #include "lib/extras/enc/encode.h" #if !JPEGXL_ENABLE_APNG namespace jxl { namespace extras { std::unique_ptr GetAPNGEncoder() { return nullptr; } } // namespace extras } // namespace jxl #else // JPEGXL_ENABLE_APNG #include #include #include #include #include #include #include #include #include #include "lib/extras/exif.h" #include "lib/extras/packed_image.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/data_parallel.h" #include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/status.h" #include "png.h" /* original (unpatched) libpng is ok */ namespace jxl { namespace extras { 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 { JXL_RETURN_IF_ERROR(ppf.Validate()); // Encode main image frames JXL_RETURN_IF_ERROR(VerifyBasicInfo(ppf.info)); encoded_image->icc.clear(); JXL_RETURN_IF_ERROR( EncodePackedPixelFileToAPNG(ppf, pool, &encoded_image->bitstreams)); // 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(); JXL_RETURN_IF_ERROR( EncodePackedPixelFileToAPNG(ppf, pool, &ec_bitstreams, true, i)); } return true; } private: Status EncodePackedPixelFileToAPNG( const PackedPixelFile& ppf, ThreadPool* pool, std::vector >* bitstreams, 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 == 10000) return; const uint32_t max_content_light_level = static_cast(10000.f * Clamp1(intensity_target, 0.f, 10000.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 >* bitstreams, 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); bitstreams->emplace_back(); auto bytes = &bitstreams->back(); size_t count = 0; size_t anim_chunks = 0; for (const auto& frame : ppf.frames) { if (!ppf.info.have_animation && count > 0) { // in the case of non-coalesced layers (composite still), write multiple // files bitstreams->emplace_back(); bytes = &bitstreams->back(); } 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::lround(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::lround(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::lround(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 || !ppf.info.have_animation) { if (!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 && ppf.info.have_animation) { 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 std::unique_ptr GetAPNGEncoder() { return jxl::make_unique(); } } // namespace extras } // namespace jxl #endif // JPEGXL_ENABLE_APNG libjxl-libjxl-5a7da83/lib/extras/enc/apng.h000066400000000000000000000007461527504724200206340ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/extras/enc/encode.cc000066400000000000000000000135201527504724200212740ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include #include #include #include #include #include #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" #include "lib/extras/packed_image.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/status.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)); // TODO(jon): frames do not necessarily have to match image size, // but probably this is still assumed in some encoders 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-libjxl-5a7da83/lib/extras/enc/encode.h000066400000000000000000000054151527504724200211420ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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; // Can this encoder write CMYK images? virtual bool AcceptsCmyk() const { return false; } // 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-libjxl-5a7da83/lib/extras/enc/exr.cc000066400000000000000000000203601527504724200206350ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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" #include // IWYU pragma: no_include "ImathConfig.h" // IWYU pragma: no_include "ImfChromaticities.h" // IWYU pragma: no_include "ImfHeader.h" // IWYU pragma: no_include "OpenEXRConfig.h" #include "lib/extras/enc/encode.h" #if !JPEGXL_ENABLE_EXR namespace jxl { namespace extras { std::unique_ptr GetEXREncoder() { return nullptr; } } // namespace extras } // namespace jxl #else // JPEGXL_ENABLE_EXR #include #include #include // IWYU pragma: keep #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/extras/packed_image.h" #include "lib/jxl/base/byte_order.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/status.h" namespace jxl { namespace extras { namespace { namespace OpenEXR = OPENEXR_IMF_NAMESPACE; namespace Imath = IMATH_NAMESPACE; // OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using // uint64_t as recommended causes build failures with previous OpenEXR versions // 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 = image.xsize; const size_t ysize = image.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) { return JXL_FAILURE("OpenEXR encoding: expected 3 color channels, got %u", static_cast(info.num_color_channels)); } if (c_enc.color_space != JXL_COLOR_SPACE_RGB && c_enc.color_space != JXL_COLOR_SPACE_GRAY) { return JXL_FAILURE( "OpenEXR encoding: expected RGB (%d) or grayscale (%d) colorspace, got " "%d", static_cast(JXL_COLOR_SPACE_RGB), static_cast(JXL_COLOR_SPACE_GRAY), static_cast(c_enc.color_space)); } if (c_enc.transfer_function != JXL_TRANSFER_FUNCTION_LINEAR) { return JXL_FAILURE( "OpenEXR encoding: expected linear transfer function (%d), got %d", static_cast(JXL_TRANSFER_FUNCTION_LINEAR), static_cast(c_enc.transfer_function)); } 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 = static_cast(image.pixels()); size_t in_stride = num_channels * 4 * xsize; OpenEXR::Header header(xsize, ysize); if (c_enc.color_space == JXL_COLOR_SPACE_RGB) { 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, c_enc.color_space == JXL_COLOR_SPACE_GRAY ? (has_alpha ? OpenEXR::WRITE_YA : OpenEXR::WRITE_Y) : (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 : {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 std::unique_ptr GetEXREncoder() { return jxl::make_unique(); } } // namespace extras } // namespace jxl #endif // JPEGXL_ENABLE_EXR libjxl-libjxl-5a7da83/lib/extras/enc/exr.h000066400000000000000000000007451527504724200205040ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/extras/enc/jpg.cc000066400000000000000000000555441527504724200206330ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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" #include #include "lib/extras/enc/encode.h" #if !JPEGXL_ENABLE_JPEG namespace jxl { namespace extras { std::unique_ptr GetJPEGEncoder() { return nullptr; } } // namespace extras } // namespace jxl #else // JPEGXL_ENABLE_JPEG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lib/extras/exif.h" #include "lib/extras/include_jpeglib.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/data_parallel.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 { 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 < image.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 < image.ysize; ++y) { const uint8_t* image_row = pixels + y * image.stride; for (size_t x = 0; x < image.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 < image.ysize; ++y) { const uint8_t* image_row = pixels + y * image.stride; for (size_t x = 0; x < image.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 Status EncodeWithSJpeg(const PackedImage& image, const JxlBasicInfo& info, const std::vector& icc, std::vector exif, const JpegParams& params, std::vector* bytes) { return JXL_FAILURE("JPEG XL was built without sjpeg support"); } #else // 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; }; Status EncodeWithSJpeg(const PackedImage& image, const JxlBasicInfo& info, const std::vector& icc, std::vector exif, const JpegParams& params, std::vector* bytes) { if (image.format.data_type != JXL_TYPE_UINT8) { return JXL_FAILURE("Unsupported pixel data type"); } if (info.alpha_bits > 0) { return JXL_FAILURE("alpha is not supported"); } 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(); } const size_t num_channels = image.format.num_channels; if (num_channels != 1 && num_channels != 3) { return JXL_FAILURE("sjpeg supports only grayscale and RGB input"); } const size_t stride = image.xsize * num_channels; const uint8_t* pixels = reinterpret_cast(image.pixels()); std::string output; if (num_channels == 1) { JXL_RETURN_IF_ERROR(sjpeg::EncodeGray(pixels, image.xsize, image.ysize, stride, param, &output)); } else { 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 // JPEGXL_ENABLE_SJPEG 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 std::unique_ptr GetJPEGEncoder() { return jxl::make_unique(); } } // namespace extras } // namespace jxl #endif // JPEGXL_ENABLE_JPEG libjxl-libjxl-5a7da83/lib/extras/enc/jpg.h000066400000000000000000000007571527504724200204710ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/extras/enc/jxl.cc000066400000000000000000000401731527504724200206400ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #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 (params.strip_alpha != -1) { if (JXL_ENC_SUCCESS != JxlEncoderFrameSettingsSetOption( settings, JXL_ENC_FRAME_SETTING_STRIP_ALPHA, params.strip_alpha)) { fprintf(stderr, "Setting strip_alpha failed.\n"); return false; } } if (frame_index < ppf.frames.size()) { const auto& frame_name = ppf.frames[frame_index].name; if (!frame_name.empty()) { if (JXL_ENC_SUCCESS != JxlEncoderSetFrameName(settings, frame_name.c_str())) { fprintf(stderr, "JxlEncoderSetFrameName() failed.\n"); 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; extra_channel_info.alpha_premultiplied = ppf.info.alpha_premultiplied; 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; } const auto& ec_name = ppf.extra_channels_info[i].name; if (!ec_name.empty()) { if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelName(enc, num_interleaved_alpha + i, ec_name.c_str(), ec_name.size())) { fprintf(stderr, "JxlEncoderSetExtraChannelName() 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, "JxlEncoderSetOutputProcessor failed\n"); return false; } auto* settings = JxlEncoderFrameSettingsCreate(enc, nullptr); if (!settings) { fprintf(stderr, "JxlEncoderFrameSettingsCreate failed\n"); return false; } 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, &pframe.extra_channels[i].format, 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-libjxl-5a7da83/lib/extras/enc/jxl.h000066400000000000000000000062361527504724200205040ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 namespace jxl { namespace extras { class PackedPixelFile; 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; // Alpha channel stripping: // -1 = encoder chooses (default: strip if empty for lossy, keep for lossless) // 0 = always keep alpha // 1 = always strip // 2 = strip if empty (opaque) int32_t strip_alpha = -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-libjxl-5a7da83/lib/extras/enc/npy.cc000066400000000000000000000256151527504724200206550ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include #include #include #include #include #include #include "lib/extras/enc/encode.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/status.h" namespace jxl { namespace extras { namespace { // JSON value writing // Returns `value` as a quoted JSON string literal. Frame names are arbitrary // bytes taken from the codestream, so '"', '\' and control characters have to // be escaped (RFC 8259, section 7); otherwise the metadata is not valid JSON // and a quote inside a name can inject arbitrary fields. std::string JSONStringLiteral(const std::string& value) { std::string out; out.reserve(value.size() + 2); out.push_back('"'); for (const char ch : value) { const uint8_t c = static_cast(ch); switch (c) { case '"': out += "\\\""; break; case '\\': out += "\\\\"; break; case '\b': out += "\\b"; break; case '\f': out += "\\f"; break; case '\n': out += "\\n"; break; case '\r': out += "\\r"; break; case '\t': out += "\\t"; break; default: if (c < 0x20) { char buf[8]; snprintf(buf, sizeof(buf), "\\u%04x", static_cast(c)); out += buf; } else { out.push_back(ch); } } } out.push_back('"'); return out; } 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_(JSONStringLiteral(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; } bool AcceptsCmyk() const override { return true; } }; } // namespace std::unique_ptr GetNumPyEncoder() { return jxl::make_unique(); } } // namespace extras } // namespace jxl libjxl-libjxl-5a7da83/lib/extras/enc/npy.h000066400000000000000000000010021527504724200204770ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/extras/enc/pgx.cc000066400000000000000000000114111527504724200206320ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include #include #include #include "lib/extras/enc/encode.h" #include "lib/extras/packed_image.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" namespace jxl { namespace extras { namespace { constexpr size_t kMaxHeaderSize = 200; Status EncodeHeader(const PackedImage& image, 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"); } unsigned int xsize = image.xsize; unsigned int ysize = image.ysize; if (xsize != image.xsize || ysize != image.ysize) { return JXL_FAILURE("PGX: image too large"); } // 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, xsize, 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; const PackedImage& color = frame.color; JXL_RETURN_IF_ERROR(EncodeHeader(color, info, header, &header_size)); 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 = static_cast(color.xsize) * color.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-libjxl-5a7da83/lib/extras/enc/pgx.h000066400000000000000000000007411527504724200205000ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 "lib/extras/enc/encode.h" namespace jxl { namespace extras { std::unique_ptr GetPGXEncoder(); } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_ENC_PGX_H_ libjxl-libjxl-5a7da83/lib/extras/enc/pnm.cc000066400000000000000000000312771527504724200206420ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include #include "lib/extras/enc/encode.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/data_parallel.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(ppf.Validate()); 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; } bool AcceptsCmyk() const override { return true; } 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; int n = 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]); if (n < 0 || static_cast(n) >= kMaxHeaderSize - pos) { return JXL_FAILURE("PNM header is too long"); } JXL_RETURN_IF_ERROR(pos < kMaxHeaderSize); pos += n; for (const auto& info : ec_info) { n = snprintf(header + pos, kMaxHeaderSize - pos, "TUPLTYPE %s\n", ExtraChannelTypeName(info.ec_info.type).c_str()); if (n < 0 || static_cast(n) >= kMaxHeaderSize - pos) { return JXL_FAILURE("PNM header is too long"); } pos += n; } n = snprintf(header + pos, kMaxHeaderSize - pos, "ENDHDR\n"); if (n < 0 || static_cast(n) >= kMaxHeaderSize - pos) { return JXL_FAILURE("PNM header is too long"); } pos += n; 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-libjxl-5a7da83/lib/extras/enc/pnm.h000066400000000000000000000013761527504724200205010ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/extras/exif.cc000066400000000000000000000027411527504724200202300ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #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-libjxl-5a7da83/lib/extras/exif.h000066400000000000000000000007261527504724200200730ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/extras/gain_map.cc000066400000000000000000000206741527504724200210550ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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/jxl/base/byte_order.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #include "lib/jxl/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) // Optional parts of the bundle may be absent (`nullptr` with size 0); // passing a null pointer to memcpy is undefined behavior even then. #define SAFE_COPY(src, n) \ do { \ if ((n) != 0) memcpy(output_buffer + cursor, src, n); \ } 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); SAFE_COPY(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); SAFE_COPY(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); SAFE_COPY(map_bundle->alt_icc, map_bundle->alt_icc_size); SAFE_CURSOR_UPDATE(map_bundle->gain_map_size); SAFE_COPY(map_bundle->gain_map, map_bundle->gain_map_size); #undef SAFE_COPY #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; // Compute remaining buffer size and ensure it fits into the public // `uint32_t gain_map_size` field to avoid silent truncation on platforms // where `size_t` is wider than 32 bits. size_t remaining = input_buffer_size - cursor; if (remaining > std::numeric_limits::max()) { return JXL_FALSE; } map_bundle->gain_map_size = static_cast(remaining); 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-libjxl-5a7da83/lib/extras/gain_map_test.cc000066400000000000000000000145021527504724200221050ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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/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-libjxl-5a7da83/lib/extras/hlg.cc000066400000000000000000000043121527504724200200430ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/status.h" #include "lib/jxl/cms/color_encoding_cms.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/image_bundle.h" 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-libjxl-5a7da83/lib/extras/hlg.h000066400000000000000000000011721527504724200177060ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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/base/data_parallel.h" #include "lib/jxl/base/status.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-libjxl-5a7da83/lib/extras/include_jpeglib.h000066400000000000000000000011221527504724200222460ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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_INCLUDE_JPEGLIB_H_ #define LIB_EXTRAS_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: export #include // IWYU pragma: export /* clang-format on */ // NOLINTEND #endif // LIB_EXTRAS_INCLUDE_JPEGLIB_H_ libjxl-libjxl-5a7da83/lib/extras/metrics.cc000066400000000000000000000173461527504724200207520ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include #include #include #include "lib/jxl/butteraugli/butteraugli.h" #include "lib/jxl/image.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/image_ops.h" #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" #include "lib/jxl/memory_manager_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; StatusOr ComputeDistanceP(const ImageF& distmap, const ButteraugliParams& params, double p) { if (distmap.xsize() == 0 || distmap.ysize() == 0) { return 0.0; } JxlMemoryManager* memory_manager = distmap.memory_manager(); JXL_ENSURE(memory_manager != nullptr); 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; JXL_ASSIGN_OR_RETURN( AlignedMemory sum_totals, AlignedMemory::Create(memory_manager, 3 * Lanes(d) * sizeof(T))); // Manually aligned storage to avoid asan crash on clang-7 due to // unaligned spill. T* sum_totals0 = sum_totals.address(); T* sum_totals1 = sum_totals0 + Lanes(d); T* sum_totals2 = sum_totals1 + Lanes(d); Store(Zero(d), d, sum_totals0); Store(Zero(d), d, sum_totals1); Store(Zero(d), d, sum_totals2); 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] += static_cast(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); StatusOr 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-libjxl-5a7da83/lib/extras/metrics.h000066400000000000000000000016131527504724200206020ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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/base/status.h" #include "lib/jxl/butteraugli/butteraugli.h" #include "lib/jxl/image.h" #include "lib/jxl/image_bundle.h" namespace jxl { // Computes p-norm given the butteraugli distmap. StatusOr 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-libjxl-5a7da83/lib/extras/mmap.cc000066400000000000000000000100031527504724200202150ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include #include "lib/jxl/base/common.h" #include "lib/jxl/base/status.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-libjxl-5a7da83/lib/extras/mmap.h000066400000000000000000000015701527504724200200700ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #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-libjxl-5a7da83/lib/extras/packed_image.cc000066400000000000000000000144621527504724200216710ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // 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 "packed_image.h" #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. StatusOr PackedImage::Create(size_t xsize, size_t ysize, const JxlPixelFormat& format) { JXL_ASSIGN_OR_RETURN(size_t stride, CalcStride(format, xsize)); size_t pixels_size = ysize * stride; if ((pixels_size / stride) != ysize) { return JXL_FAILURE("Image too big"); } PackedImage image(xsize, ysize, format, stride); if (!image.pixels()) { // TODO(szabadka): use specialized OOM error code return JXL_FAILURE("Failed to allocate memory for image"); } return image; } StatusOr PackedImage::Copy() const { // Resulting copy_stride have to be less or equal to original -> always ok. JXL_ASSIGN_OR_RETURN(size_t copy_stride, CalcStride(format, xsize)); PackedImage copy(xsize, ysize, format, copy_stride); const uint8_t* orig_pixels = reinterpret_cast(pixels()); uint8_t* copy_pixels = reinterpret_cast(copy.pixels()); if (stride == copy_stride) { // Same stride -> copy in one go. memcpy(copy_pixels, orig_pixels, ysize * stride); } else { // Otherwise, copy row-wise. JXL_DASSERT(copy_stride < stride); for (size_t y = 0; y < ysize; ++y) { memcpy(copy_pixels + y * copy_stride, orig_pixels + y * stride, copy_stride); } } return copy; } Status PackedImage::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; } size_t PackedImage::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; } } // Logical resize; use Copy() for storage reallocation, if necessary. Status PackedImage::ShrinkTo(size_t new_xsize, size_t new_ysize) { if (new_xsize > xsize || new_ysize > ysize) { return JXL_FAILURE("Cannot shrink PackedImage to a larger size"); } xsize = new_xsize; ysize = new_ysize; return true; } PackedImage::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); } StatusOr PackedImage::CalcStride(const JxlPixelFormat& format, size_t xsize) { size_t multiplier = (BitsPerChannel(format.data_type) * format.num_channels / jxl::kBitsPerByte); size_t stride; if (!SafeMul(xsize, multiplier, stride)) { return JXL_FAILURE("Image too big"); } if (!SafeRoundUpTo(stride, format.align, stride)) { return JXL_FAILURE("Image too big"); } return stride; } PackedFrame::PackedFrame(PackedImage&& image) : color(std::move(image)) {} PackedFrame::PackedFrame(PackedFrame&& other) = default; PackedFrame& PackedFrame::operator=(PackedFrame&& other) = default; PackedFrame::~PackedFrame() = default; StatusOr PackedFrame::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 PackedFrame::Copy() const { JXL_ASSIGN_OR_RETURN( PackedFrame copy, PackedFrame::Create(color.xsize, color.ysize, color.format)); copy.frame_info = frame_info; copy.name = name; JXL_ASSIGN_OR_RETURN(copy.color, color.Copy()); for (const auto& ec : extra_channels) { JXL_ASSIGN_OR_RETURN(PackedImage ec_copy, ec.Copy()); copy.extra_channels.emplace_back(std::move(ec_copy)); } return copy; } // Logical resize; use Copy() for storage reallocation, if necessary. Status PackedFrame::ShrinkTo(size_t new_xsize, size_t new_ysize) { JXL_RETURN_IF_ERROR(color.ShrinkTo(new_xsize, new_ysize)); for (auto& ec : extra_channels) { JXL_RETURN_IF_ERROR(ec.ShrinkTo(new_xsize, new_ysize)); } frame_info.layer_info.xsize = new_xsize; frame_info.layer_info.ysize = new_ysize; return true; } ChunkedPackedFrame::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); } PackedPixelFile::PackedPixelFile() { JxlEncoderInitBasicInfo(&info); }; Status PackedPixelFile::Validate() const { for (const auto& frame : frames) { if (frame.extra_channels.size() != extra_channels_info.size()) { return JXL_FAILURE("Extra channel count mismatch"); } } return true; } Status PackedPixelFile::ShrinkTo(size_t new_xsize, size_t new_ysize) { for (auto& frame : frames) { JXL_RETURN_IF_ERROR(frame.ShrinkTo(new_xsize, new_ysize)); } info.xsize = new_xsize; info.ysize = new_ysize; return true; } } // namespace extras } // namespace jxl libjxl-libjxl-5a7da83/lib/extras/packed_image.h000066400000000000000000000165341527504724200215350ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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); StatusOr Copy() const; // 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); static size_t BitsPerChannel(JxlDataType data_type); 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"); } } // Logical resize; use Copy() for storage reallocation, if necessary. Status ShrinkTo(size_t new_xsize, size_t new_ysize); private: PackedImage(size_t xsize, size_t ysize, const JxlPixelFormat& format, size_t stride); static StatusOr CalcStride(const JxlPixelFormat& format, size_t xsize); 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); PackedFrame(PackedFrame&& other); PackedFrame& operator=(PackedFrame&& other); ~PackedFrame(); static StatusOr Create(size_t xsize, size_t ysize, const JxlPixelFormat& format); StatusOr Copy() const; // Logical resize; use Copy() for storage reallocation, if necessary. Status ShrinkTo(size_t new_xsize, size_t new_ysize); // 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); 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(); 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; } // Each frame must supply exactly the declared extra channels. Status Validate() const; // Logical resize; storage is not reallocated; stride is unchanged. Status ShrinkTo(size_t new_xsize, size_t new_ysize); }; } // namespace extras } // namespace jxl #endif // LIB_EXTRAS_PACKED_IMAGE_H_ libjxl-libjxl-5a7da83/lib/extras/packed_image_convert.cc000066400000000000000000000362211527504724200234260ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include "lib/extras/codec_in_out.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/rect.h" #include "lib/jxl/base/span.h" #include "lib/jxl/base/status.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/dec_cache.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/image.h" #include "lib/jxl/image_bundle.h" #include "lib/jxl/image_metadata.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)); bool has_interleaved_alpha = (info.alpha_bits != 0); 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, has_interleaved_alpha)); 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"); } } // 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(); 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-libjxl-5a7da83/lib/extras/packed_image_convert.h000066400000000000000000000031371527504724200232700ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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/codec_in_out.h" #include "lib/extras/packed_image.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/status.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/image.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-libjxl-5a7da83/lib/extras/size_constraints.h000066400000000000000000000025471527504724200225440ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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. // Default values correspond to JXL level 10. uint32_t dec_max_xsize = 1u << 30; uint32_t dec_max_ysize = 1u << 30; uint64_t dec_max_pixels = static_cast(1u) << 40; }; template ::value>::type> Status VerifyDimensions(const SizeConstraints* constraints, T xs, T ys) { SizeConstraints limit = {}; if (constraints) limit = *constraints; if (xs == 0 || ys == 0) return JXL_FAILURE("Empty image."); if (xs > limit.dec_max_xsize) return JXL_FAILURE("Image too wide."); if (ys > limit.dec_max_ysize) return JXL_FAILURE("Image too tall."); const uint64_t num_pixels = static_cast(xs) * ys; if (num_pixels > limit.dec_max_pixels) { return JXL_FAILURE("Image too big."); } return true; } } // namespace jxl #endif // LIB_JXL_SIZE_CONSTRAINTS_H_ libjxl-libjxl-5a7da83/lib/extras/time.cc000066400000000000000000000026141527504724200202320ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 "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-libjxl-5a7da83/lib/extras/time.h000066400000000000000000000007461527504724200201000ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/extras/tone_mapping.cc000066400000000000000000000116341527504724200217560ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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" #include #include #include "lib/extras/codec_in_out.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/matrix_ops.h" #include "lib/jxl/base/status.h" #include "lib/jxl/cms/color_encoding_cms.h" #include "lib/jxl/color_encoding_internal.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 Range& 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 Range& 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[1]); 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-libjxl-5a7da83/lib/extras/tone_mapping.h000066400000000000000000000023261527504724200216160ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include "lib/extras/codec_in_out.h" #include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/status.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(const Range& 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-libjxl-5a7da83/lib/extras/tone_mapping_gbench.cc000066400000000000000000000042271527504724200232640ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 "benchmark/benchmark.h" #include "lib/extras/codec_in_out.h" #include "lib/extras/tone_mapping.h" #include "lib/jxl/base/common.h" #include "lib/jxl/base/status.h" #include "lib/jxl/cms/color_encoding_cms.h" #include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/image.h" #include "lib/jxl/image_ops.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(); auto tone_mapping_input = jxl::make_unique(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.get())); } state.SetItemsProcessed(state.iterations() * color.xsize() * color.ysize()); } BENCHMARK(BM_ToneMapping); } // namespace jxl libjxl-libjxl-5a7da83/lib/gbench_main.cc000066400000000000000000000011661527504724200202210ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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" void RegisterDctBenchmarks(); int main(int argc, char** argv) { char arg0_default[] = "benchmark"; char* args_default = arg0_default; if (!argv) { argc = 1; argv = &args_default; } RegisterDctBenchmarks(); ::benchmark ::Initialize(&argc, argv); if (::benchmark ::ReportUnrecognizedArguments(argc, argv)) return 1; ::benchmark ::RunSpecifiedBenchmarks(); ::benchmark ::Shutdown(); return 0; } libjxl-libjxl-5a7da83/lib/include/000077500000000000000000000000001527504724200170775ustar00rootroot00000000000000libjxl-libjxl-5a7da83/lib/include/jxl/000077500000000000000000000000001527504724200176745ustar00rootroot00000000000000libjxl-libjxl-5a7da83/lib/include/jxl/cms.h000066400000000000000000000007321527504724200206310ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/include/jxl/cms_interface.h000066400000000000000000000237321527504724200226560ustar00rootroot00000000000000/* Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/include/jxl/codestream_header.h000066400000000000000000000353541527504724200235150ustar00rootroot00000000000000/* Copyright (c) the JPEG XL Project 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 and @ref JxlDecoderSetOutputColorProfile is * not called, the decoder converts the data to the original profile if it's * encoded as a @ref JxlColorEncoding rather than an ICC color profile, and * falls back to linear sRGB otherwise. 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-libjxl-5a7da83/lib/include/jxl/color_encoding.h000066400000000000000000000130241527504724200230310ustar00rootroot00000000000000/* Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/include/jxl/compressed_icc.h000066400000000000000000000052611527504724200230330ustar00rootroot00000000000000/* Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/include/jxl/decode.h000066400000000000000000002125021527504724200212720ustar00rootroot00000000000000/* Copyright (c) the JPEG XL Project 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 JxlDecoder 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. 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. This must be * set when the ::JXL_DEC_NEED_PREVIEW_OUT_BUFFER event occurs. 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. Attempt to set * preview buffer before ::JXL_DEC_NEED_PREVIEW_OUT_BUFFER or after * ::JXL_DEC_PREVIEW_IMAGE will fail. * * @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 * ::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. Attempt to set * image buffer while preview buffer is expected will fail. * * @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-libjxl-5a7da83/lib/include/jxl/decode_cxx.h000066400000000000000000000035271527504724200221610ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/include/jxl/encode.h000066400000000000000000002155141527504724200213120ustar00rootroot00000000000000/* Copyright (c) the JPEG XL Project 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 JxlEncoder 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 JxlEncoderFrameSettings 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 input buffering is used, when using chunked image * frames. * When using streaming input the encoder minimizes memory usage, potentially * at a cost in compression density (though not necessarily). * -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 2048 x 2048 or smaller, and * uses streaming input and buffered output for larger images * 2 = same as 1, but the threshold to use streaming input is lower * 3 = deprecated; same as 2, but also sets output mode to 1. * * Output buffering is controlled via @ref JXL_ENC_FRAME_SETTING_OUTPUT_MODE. */ 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, /** Control how codestream bytes are written to the output. Unlike * @ref JXL_ENC_FRAME_SETTING_BUFFERING (which controls input buffering), * this setting controls the output ordering and memory trade-offs. * * Modes 1 and 2 reduce peak memory usage by avoiding buffering the output * bitstream, but produce codestreams in an order not suitable for * progressive decoding. * * -1 = default (let the encoder decide). * 0 = buffer the output bitstream internally; write frames in normal order. * No seeking required. The codestream can be decoded progressively. * This is the most compatible mode. * 1 = seek-based streaming: write group data first, then seek back to write * the frame header and TOC. Reduces peak memory for large images. * Requires a seekable output stream. Produces maximally compatible files. * 2 = out-of-order jxlp streaming: each codestream section is a separate * jxlp box written in encoding order; jxlp counters reflect codestream * order so a decoder can reassemble a standard, progressively decodable * codestream by sorting the boxes. Reduces peak memory without requiring * output seeking. Requires ftyp version 1, which is not supported by * older decoders. If mode 2 is used for any frame, it must also be used * for the first frame (the ftyp version cannot be changed once written). */ JXL_ENC_FRAME_SETTING_OUTPUT_MODE = 40, /** Strips the alpha channel from the frame/image. * -1 = default (let the encoder decide: strip if fully opaque in lossy mode, * keep in lossless). * 0 = never strip (always keep alpha). * 1 = always strip alpha (force strip). * 2 = strip alpha if fully opaque. * * Note: Only applies when the image has exactly one extra channel (alpha). * For single-frame auto-detection, the encoder must know there are no further * frames (e.g. via @ref JxlEncoderCloseFrames or @ref JxlEncoderCloseInput) * before output processing begins. The setting on the first frame dictates * codestream behavior. */ JXL_ENC_FRAME_SETTING_STRIP_ALPHA = 41, /** Enable or disable LF Smoothing for lossless JPEG recompression. * -1 = default, 0 = disable smoothing, 1 = enable smoothing. */ JXL_ENC_FRAME_SETTING_JPEG_RECON_LFS = 42, /** 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 1, an * 8-byte 64-bit size follows instead. If set to 0, the box extends to the * end of the file.) * - 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. * * The returned value could be NULL in case of out of memory situatiton. * * @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-libjxl-5a7da83/lib/include/jxl/encode_cxx.h000066400000000000000000000035261527504724200221720ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/include/jxl/gain_map.h000066400000000000000000000122141527504724200216200ustar00rootroot00000000000000/* Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/include/jxl/memory_manager.h000066400000000000000000000040751527504724200230550ustar00rootroot00000000000000/* Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/include/jxl/parallel_runner.h000066400000000000000000000152731527504724200232420ustar00rootroot00000000000000/* Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/include/jxl/resizable_parallel_runner.h000066400000000000000000000052531527504724200252770ustar00rootroot00000000000000/* Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/include/jxl/resizable_parallel_runner_cxx.h000066400000000000000000000044441527504724200261620ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/include/jxl/stats.h000066400000000000000000000054661527504724200212160ustar00rootroot00000000000000/* Copyright (c) the JPEG XL Project 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 JxlEncoderStats 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-libjxl-5a7da83/lib/include/jxl/thread_parallel_runner.h000066400000000000000000000046331527504724200245670ustar00rootroot00000000000000/* Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/include/jxl/thread_parallel_runner_cxx.h000066400000000000000000000045051527504724200254470ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/include/jxl/types.h000066400000000000000000000117651527504724200212230ustar00rootroot00000000000000/* Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/jxl.cmake000066400000000000000000000251711527504724200172610ustar00rootroot00000000000000# Copyright (c) the JPEG XL Project 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() if (MSVC) if (CMAKE_BUILD_TYPE IN_LIST "Release;RelWithDebInfo") set(FJXL_COMPILE_FLAGS "/O2") else() set(FJXL_COMPILE_FLAGS "") endif() else() set(FJXL_COMPILE_FLAGS "-O3") endif() 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 BEFORE INTERFACE "$" ) target_include_directories(jxl_base BEFORE INTERFACE ${PROJECT_SOURCE_DIR} ${JXL_HWY_INCLUDE_DIRS} ) target_compile_definitions(jxl_base INTERFACE "$<$>:JXL_STATIC_DEFINE>") # 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 if (MSVC) # MSVC ignores this flag (with a warning), so CMake thinks it supports that. set(LINKER_EXCLUDE_LIBS_FLAG "") set(LINKER_SUPPORT_EXCLUDE_LIBS FALSE) else() set(LINKER_EXCLUDE_LIBS_FLAG "-Wl,--exclude-libs=ALL") include(CheckCSourceCompiles) list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${LINKER_EXCLUDE_LIBS_FLAG}) check_c_source_compiles("int main(){return 0;}" LINKER_SUPPORT_EXCLUDE_LIBS) list(REMOVE_ITEM CMAKE_REQUIRED_LINK_OPTIONS ${LINKER_EXCLUDE_LIBS_FLAG}) 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") # MSVCRT bundles math functions so no explicit libm dependency is required if (BUILD_SHARED_LIBS) set(JPEGXL_REQUIRES_TYPE "Requires.private") if(NOT MSVC AND NOT APPLE) set(JPEGXL_PRIVATE_LIBS "-lm ${PKGCONFIG_CXX_LIB}") endif() else() set(JPEGXL_REQUIRES_TYPE "Requires") if(NOT MSVC AND NOT APPLE) set(JPEGXL_PUBLIC_LIBS "-lm ${PKGCONFIG_CXX_LIB}") endif() endif() set(JPEGXL_LIBRARY_MAIN jxl) # Fix pkg-config file on MSVC when building static libraries. if (MSVC AND NOT BUILD_SHARED_LIBS) set(JPEGXL_LIBRARY_MAIN jxl-static) 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-libjxl-5a7da83/lib/jxl/000077500000000000000000000000001527504724200162515ustar00rootroot00000000000000libjxl-libjxl-5a7da83/lib/jxl/ac_context.h000066400000000000000000000143071527504724200205560ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include "lib/jxl/base/compiler_specific.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-libjxl-5a7da83/lib/jxl/ac_strategy.cc000066400000000000000000000065501527504724200210730ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include "lib/jxl/base/bits.h" #include "lib/jxl/base/compiler_specific.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" 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-libjxl-5a7da83/lib/jxl/ac_strategy.h000066400000000000000000000224111527504724200207270ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/jxl/ac_strategy_test.cc000066400000000000000000000243631527504724200221340ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 k = 0; k < 64u << acs.log2_covered_blocks(); k++) { ASSERT_NEAR(idct[k], k == i ? 0.2f : 0, 2e-6) << "k = " << k << " 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, scratch_space); 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* const idct = mem.address(); float* idct_acs_downsampled = idct + AcStrategy::kMaxCoeffArea; float* const 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; } 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, scratch_space); // 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], 2e-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-libjxl-5a7da83/lib/jxl/alpha.cc000066400000000000000000000076421527504724200176560ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include "lib/jxl/base/common.h" #include "lib/jxl/base/compiler_specific.h" namespace jxl { static float Clamp(float x) { return jxl::Clamp1(x, 0.0f, 1.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 ? Clamp(fga[x]) : 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 ? Clamp(fga[x]) : fga[x]; out[x] = (fg[x] + bg[x] * (1.f - fa)); } } else { for (size_t x = 0; x < num_pixels; ++x) { float fa = clamp ? Clamp(fga[x]) : 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-libjxl-5a7da83/lib/jxl/alpha.h000066400000000000000000000044041527504724200175110ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 "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-libjxl-5a7da83/lib/jxl/alpha_test.cc000066400000000000000000000102461527504724200207070ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/jxl/ans_common.cc000066400000000000000000000137301527504724200207150ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 "lib/jxl/ans_params.h" #include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/status.h" namespace jxl { // 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-libjxl-5a7da83/lib/jxl/ans_common.h000066400000000000000000000144561527504724200205650ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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. static JXL_MAYBE_UNUSED JXL_INLINE 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; } // 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; static_assert(sizeof(table[i]) == sizeof(entry), "ANS table entries must match the lookup word size"); memcpy(&entry, &table[i], 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-libjxl-5a7da83/lib/jxl/ans_common_test.cc000066400000000000000000000032421527504724200217510ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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" #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; std::vector table(1 << log_alpha_size); Status ok = InitAliasTable(distribution, log_range, log_alpha_size, table.data()); ASSERT_TRUE(ok); 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.data(), 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-libjxl-5a7da83/lib/jxl/ans_params.h000066400000000000000000000020631527504724200205470ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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. 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-libjxl-5a7da83/lib/jxl/ans_test.cc000066400000000000000000000315241527504724200204050ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project Authors. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include #include #include #include #include #include #include "lib/jxl/ans_params.h" #include "lib/jxl/base/common.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_ans_params.h" #include "lib/jxl/enc_ans_simd.h" #include "lib/jxl/enc_aux_out.h" #include "lib/jxl/enc_bit_writer.h" #include "lib/jxl/memory_manager_internal.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; })); 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, &writer, LayerType::Header, nullptr)); (void)cost; ASSERT_TRUE(WriteTokens(input_values_vec[0], codes, 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, codes.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); } EntropyEncodingData codes; HistogramParams params; params.lz77_method = lz77 ? HistogramParams::LZ77Method::kLZ77b3w3f : 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, &writer, LayerType::Header, nullptr)); (void)cost; ASSERT_TRUE(WriteTokens(input_values_copy[0], codes, 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, codes.context_map); JXL_TEST_ASSIGN_OR_DIE(ANSSymbolReader reader, ANSSymbolReader::Create(&decoded_codes, &br)); auto checkpoint = jxl::make_unique(); 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.get()); 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); } std::string AnsSimdTestDescription( const testing::TestParamInfo& info) { std::stringstream name; const HybridUintConfig& cfg = info.param; name << cfg.split_exponent << "_" << cfg.msb_in_token << "_" << cfg.lsb_in_token; return name.str(); } std::vector GenerateAnsSimdTests() { return {HybridUintConfig{0, 0, 0}, HybridUintConfig{2, 0, 1}, HybridUintConfig{3, 1, 0}, HybridUintConfig{3, 1, 2}, HybridUintConfig{3, 2, 0}, HybridUintConfig{3, 2, 1}, HybridUintConfig{4, 1, 0}, HybridUintConfig{4, 1, 2}, HybridUintConfig{4, 1, 3}, HybridUintConfig{4, 2, 0}, HybridUintConfig{4, 2, 1}, HybridUintConfig{4, 2, 2}, HybridUintConfig{5, 1, 0}, HybridUintConfig{5, 1, 2}, HybridUintConfig{5, 1, 4}, HybridUintConfig{5, 2, 0}, HybridUintConfig{5, 2, 1}, HybridUintConfig{5, 2, 2}, HybridUintConfig{5, 2, 3}, HybridUintConfig{6, 0, 0}, HybridUintConfig{6, 2, 4}, HybridUintConfig{7, 0, 0}, HybridUintConfig{8, 0, 0}, HybridUintConfig{9, 0, 0}, HybridUintConfig{10, 0, 0}, HybridUintConfig{11, 0, 0}, HybridUintConfig{12, 0, 0}}; } class AnsSimdTest : public ::testing::TestWithParam {}; JXL_GTEST_INSTANTIATE_TEST_SUITE_P(AnsSimdTestAll, AnsSimdTest, testing::ValuesIn(GenerateAnsSimdTests()), AnsSimdTestDescription); TEST_P(AnsSimdTest, EstimateTokenCost) { const HybridUintConfig& cfg = GetParam(); JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); constexpr size_t kNumItems = 8 * 1024 * 1024; std::vector in; std::vector expected_out; constexpr size_t kTail = 1024; in.reserve(kNumItems + 311306 + kTail); uint32_t expected_eb = 0; for (size_t i = 0; i < kNumItems; ++i) { in.push_back(i); } for (size_t b = 23; b <= 31; ++b) { size_t base = 1 << b; for (size_t k = base - 16384; k <= base + 16384; ++k) { in.push_back(k); } } for (size_t n = 0; n <= 16384; ++n) { in.push_back(static_cast(~0u) - n); } expected_out.reserve(in.size() + 1024); for (size_t i = 0; i < in.size(); ++i) { uint32_t v = in[i]; uint32_t tok, nbits, bits; cfg.Encode(v, &tok, &nbits, &bits); expected_out.push_back(tok); expected_eb += nbits; } size_t data_size = in.size(); in.resize(data_size + kTail); JXL_ASSIGN_OR_QUIT( AlignedMemory out, AlignedMemory::Create(memory_manager, data_size * sizeof(uint32_t)), "Failed to rallocate memory."); uint32_t eb = EstimateTokenCost(in.data(), data_size, cfg, out); ASSERT_EQ(eb, expected_eb); uint32_t* actual_out = out.address(); for (size_t i = 0; i < data_size; ++i) { ASSERT_EQ(actual_out[i], expected_out[i]) << i; } } } // namespace } // namespace jxl libjxl-libjxl-5a7da83/lib/jxl/base/000077500000000000000000000000001527504724200171635ustar00rootroot00000000000000libjxl-libjxl-5a7da83/lib/jxl/base/arch_macros.h000066400000000000000000000012421527504724200216140ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/jxl/base/bits.h000066400000000000000000000100331527504724200202720ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/jxl/base/byte_order.h000066400000000000000000000165741527504724200215070ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/jxl/base/c_callback_support.h000066400000000000000000000015411527504724200231670ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/jxl/base/common.h000066400000000000000000000133751527504724200206350ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 #include #include "lib/jxl/base/compiler_specific.h" #if JXL_COMPILER_MSVC #include #endif 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); } template ::value>::type> static inline bool SafeAdd(const U a, const U b, U& sum) { sum = a + b; return sum >= a; // no need to check b - either sum >= both or < both. } static inline bool SafeMul(size_t a, size_t b, size_t& product) { product = 0; if (a == 0 || b == 0) return true; if (b > (std::numeric_limits::max() / a)) return false; product = a * b; return true; } static inline bool SubOverflow(const int32_t a, const int32_t b, int32_t& c) { // Clang 3.8+ / GCC 5.1+ #if JXL_COMPILER_GCC || JXL_COMPILER_CLANG return __builtin_sub_overflow(a, b, &c); #elif JXL_COMPILER_MSVC >= 1937 && (defined(_M_AMD64) || defined(_M_IX86)) return _sub_overflow_i32(/*carry*/ 0, a, b, &c); #else uint32_t ua = static_cast(a); uint32_t ub = static_cast(b); uint32_t uc = ua - ub; c = static_cast(uc); return !!(((ua ^ ub) & (ua ^ uc)) >> 31); #endif } 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; } // `align <= 1` means no rounding. static inline bool SafeRoundUpTo(size_t what, size_t align, size_t& result) { if (align < 2) { result = what; return true; } size_t reminder = what % align; if (reminder == 0) { result = what; return true; } return SafeAdd(what, align - reminder, result); } constexpr double kPi = 3.14159265358979323846264338327950288; // Multiplier for conversion of log2(x) result to ln(x). // print(1.0 / math.log2(math.e)) constexpr float kInvLog2e = 0.6931471805599453; // 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 template struct UninitializedAllocator : std::allocator { static_assert(std::is_trivially_copyable::value, "Uninitialized values have to be trivially destructible"); using value_type = T; UninitializedAllocator() noexcept = default; UninitializedAllocator(const UninitializedAllocator& other) noexcept = default; template explicit UninitializedAllocator( const UninitializedAllocator& other) noexcept {} template struct rebind { using other = UninitializedAllocator; }; template void construct(U* place, Args&&... args) {} template void destroy(U* place) {} }; template using uninitialized_vector = std::vector>; template uninitialized_vector make_uninitialized_vector(size_t n) { return uninitialized_vector(n, UninitializedAllocator()); } 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-libjxl-5a7da83/lib/jxl/base/compiler_specific.h000066400000000000000000000147711527504724200230250ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 // IWYU pragma: export #ifdef __clang_analyzer__ #include // IWYU pragma: export #endif #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 && !defined(__clang__) #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 // 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-libjxl-5a7da83/lib/jxl/base/data_parallel.h000066400000000000000000000122161527504724200221230ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 { struct ThreadPoolNoInit {}; 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 constexpr ThreadPoolNoInit NoInit{}; 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_ = 1; 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_ = 1; } } bool HasError() const { return has_error_ != 0; } private: const InitFunc& init_func_; const DataFunc& data_func_; std::atomic has_error_{0}; }; // 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); } } template Status RunOnPool(ThreadPool* pool, const uint32_t begin, const uint32_t end, const ThreadPoolNoInit& no_init_func, const DataFunc& data_func, const char* caller) { const auto init_func = [](size_t num_threads) -> Status { return true; }; return RunOnPool(pool, 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-libjxl-5a7da83/lib/jxl/base/exif.h000066400000000000000000000056331527504724200202760ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/jxl/base/fast_math-inl.h000066400000000000000000000203311527504724200220610ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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) #include #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-libjxl-5a7da83/lib/jxl/base/float.h000066400000000000000000000054411527504724200204450ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 { 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 == 0b11111 ? 0b11111111 : 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-libjxl-5a7da83/lib/jxl/base/iaca.h000066400000000000000000000036001527504724200202300ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/jxl/base/matrix_ops.h000066400000000000000000000057641527504724200215350ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 { using Vector3 = std::array; using Vector3d = std::array; using Matrix3x3 = std::array; using Matrix3x3d = std::array; // 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 += static_cast(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-libjxl-5a7da83/lib/jxl/base/os_macros.h000066400000000000000000000015401527504724200213210ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/jxl/base/override.h000066400000000000000000000015421527504724200211550ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/jxl/base/printf_macros.h000066400000000000000000000014701527504724200222040ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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 ptrdiff_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-libjxl-5a7da83/lib/jxl/base/random.h000066400000000000000000000053621527504724200206220ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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-libjxl-5a7da83/lib/jxl/base/rational_polynomial-inl.h000066400000000000000000000076621527504724200242030ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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::ApproximateReciprocal; 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-libjxl-5a7da83/lib/jxl/base/rect.h000066400000000000000000000141111527504724200202670ustar00rootroot00000000000000// Copyright (c) the JPEG XL Project 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