Bug 1782272 - Update libjxl to 59f7d19e454bc5e1edd692187e1178f5926fdfd9 r=saschanaz

Differential Revision: https://phabricator.services.mozilla.com/D153232
This commit is contained in:
Updatebot 2022-08-01 10:27:42 +00:00
parent 771c955e18
commit 760010bb23
219 changed files with 11566 additions and 6979 deletions

View file

@ -26,7 +26,11 @@ SOURCES += [
"/third_party/jpeg-xl/lib/jxl/color_encoding_internal.cc",
"/third_party/jpeg-xl/lib/jxl/color_management.cc",
"/third_party/jpeg-xl/lib/jxl/compressed_dc.cc",
"/third_party/jpeg-xl/lib/jxl/convolve.cc",
"/third_party/jpeg-xl/lib/jxl/convolve_separable5.cc",
"/third_party/jpeg-xl/lib/jxl/convolve_separable7.cc",
"/third_party/jpeg-xl/lib/jxl/convolve_slow.cc",
"/third_party/jpeg-xl/lib/jxl/convolve_symmetric3.cc",
"/third_party/jpeg-xl/lib/jxl/convolve_symmetric5.cc",
"/third_party/jpeg-xl/lib/jxl/dct_scales.cc",
"/third_party/jpeg-xl/lib/jxl/dec_ans.cc",
"/third_party/jpeg-xl/lib/jxl/dec_cache.cc",
@ -79,11 +83,14 @@ SOURCES += [
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_blending.cc",
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_chroma_upsampling.cc",
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.cc",
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_from_linear.cc",
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_gaborish.cc",
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_noise.cc",
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_patches.cc",
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_splines.cc",
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_spot.cc",
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_to_linear.cc",
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_tone_mapping.cc",
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_upsampling.cc",
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.cc",
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_xyb.cc",

View file

@ -10,9 +10,9 @@ origin:
url: https://github.com/libjxl/libjxl
release: commit 318c592d98b97d103941b90d47107f06a10c71da (2022-03-21T20:44:46Z).
release: 59f7d19e454bc5e1edd692187e1178f5926fdfd9 (2022-07-28T11:13:01Z).
revision: 318c592d98b97d103941b90d47107f06a10c71da
revision: 59f7d19e454bc5e1edd692187e1178f5926fdfd9
license: Apache-2.0

View file

@ -27,3 +27,11 @@ If applicable, add screenshots or example input/output images to help explain yo
**Additional context**
Add any other context about the problem here.
<!--
Currently github does not allow uploading files that end in `.jxl`, but when you
rename them for example as `image.jxl.jpg`, it will be possible to upload them
and also view them in browsers that are configured to support it.
See https://github.com/orgs/github-community/discussions/18139
-->

View file

@ -90,7 +90,7 @@ jobs:
# Whether we track the stack size.
STACK_SIZE: ${{ matrix.env_stack_size }}
TEST_STACK_LIMIT: ${{ matrix.env_test_stack_size }}
WILL_RUN_TESTS: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && (matrix.test_in_pr || contains(github.event.pull_request.labels.*.names, 'CI:full'))) }}
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: Install build deps
@ -105,7 +105,6 @@ jobs:
libbenchmark-tools \
libbrotli-dev \
libgdk-pixbuf2.0-dev \
libgflags-dev \
libgif-dev \
libgtest-dev \
libgtk2.0-dev \
@ -125,6 +124,28 @@ jobs:
with:
submodules: true
fetch-depth: 2
- name: Setup the LLVM source path
if: matrix.name == 'msan'
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'
uses: actions/cache@v2
with:
path: ${{ env.LLVM_ROOT }}
key: llvm
- name: Checkout the LLVM source
if: matrix.name == 'msan'
uses: actions/checkout@v2
with:
submodules: false
repository: llvm/llvm-project
ref: llvmorg-7.0.1
path: llvm_root
- name: Sphinx dependencies
# Dependencies for sphinx HTML documentation
if: matrix.name == 'release'
@ -162,6 +183,9 @@ jobs:
env:
SKIP_TEST: 1
CMAKE_CXX_FLAGS: ${{ matrix.cxxflags }}
- name: Build stats
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 }}
@ -185,7 +209,7 @@ jobs:
exit 1
fi
# Test that the built binaries run.
echo -e -n "PF\n1 1\n-1.0\nrrrrggggbbbb" > test.pfm
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/encode_oneshot_static test.pfm test-static.jxl
build-example/decode_oneshot test.jxl dec.pfm dec.icc
@ -218,7 +242,7 @@ jobs:
matrix.name != 'coverage' && (github.event_name == 'push' ||
(github.event_name == 'pull_request' && (
matrix.test_in_pr ||
contains(github.event.pull_request.labels.*.names, 'CI:full'))))
contains(github.event.pull_request.labels.*.name, 'CI:full'))))
run: |
STORE_IMAGES=0 ./ci.sh fast_benchmark
# Run gbench once, just to make sure it runs, not for actual benchmarking.
@ -231,173 +255,22 @@ jobs:
run: |
./ci.sh gbench --benchmark_min_time=0
cross_compile_ubuntu:
name: Cross-compiling ${{ matrix.build_target }} ${{ matrix.lowprecision }}
runs-on: [ubuntu-18.04]
strategy:
matrix:
include:
- arch: arm64
lowprecision:
build_target: aarch64-linux-gnu
cmake_args: -DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/qemu-aarch64-static
- arch: arm64
lowprecision: lowprecision
build_target: aarch64-linux-gnu
cmake_args: -DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/qemu-aarch64-static -DCMAKE_CXX_FLAGS=-DJXL_HIGH_PRECISION=0
- arch: armhf
lowprecision:
build_target: arm-linux-gnueabihf
cmake_args: -DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/qemu-arm-static
- arch: i386
lowprecision:
test_in_pr: true
build_target: i686-linux-gnu
env:
BUILD_DIR: build
WILL_RUN_TESTS: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && (matrix.test_in_pr || contains(github.event.pull_request.labels.*.names, 'CI:full'))) }}
steps:
- name: Setup apt
shell: bash
run: |
set -x
sudo apt-get update -y
sudo apt-get install -y curl gnupg ca-certificates
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1E9377A2BA9EF27F
if [[ "${{ matrix.arch }}" != "amd64" ]]; then
sudo dpkg --add-architecture "${{ matrix.arch }}"
# Update the sources.list with the split of supported architectures.
bkplist="/etc/apt/sources.list.bkp"
sudo mv /etc/apt/sources.list "${bkplist}"
newlist="/etc/apt/sources.list"
sudo rm -f "${newlist}"
main_list="amd64"
port_list=""
if [[ "${{ matrix.arch }}" == "i386" ]]; then
main_list="amd64,i386"
else
port_list="${{ matrix.arch }}"
fi
if [[ -n "${port_list}" ]]; then
port_url="http://ports.ubuntu.com/ubuntu-ports/"
grep -v -E '^#' "${bkplist}" |
sed -E "s;^deb (http[^ ]+) (.*)\$;deb [arch=${{ matrix.arch }}] ${port_url} \\2;" \
| sudo tee -a "${newlist}"
fi
grep -v -E '^#' "${bkplist}" |
sed -E "s;^deb (http[^ ]+) (.*)\$;deb [arch=${main_list}] \\1 \\2\ndeb-src [arch=${main_list}] \\1 \\2;" \
| sudo tee -a "${newlist}"
fi
- name: Install build deps
shell: bash
run: |
set -x
sudo apt update
pkgs=(
# Build dependencies
cmake
doxygen
ninja-build
pkg-config
qemu-user-static
xvfb
# Toolchain for cross-compiling.
clang-7
# libclang-common-7-dev:${{ matrix.arch }}
libc6-dev-${{ matrix.arch }}-cross
libstdc++-8-dev-${{ matrix.arch }}-cross
libstdc++-8-dev:${{ matrix.arch }}
# Dependencies
libbrotli-dev:${{ matrix.arch }}
libgif-dev:${{ matrix.arch }}
libjpeg-dev:${{ matrix.arch }}
libpng-dev:${{ matrix.arch }}
libwebp-dev:${{ matrix.arch }}
# For OpenEXR:
libilmbase-dev:${{ matrix.arch }}
libopenexr-dev:${{ matrix.arch }}
# GTK plugins
libgdk-pixbuf2.0-dev:${{ matrix.arch }}
libgtk2.0-dev:${{ matrix.arch }}
# QT
libqt5x11extras5-dev:${{ matrix.arch }}
qtbase5-dev:${{ matrix.arch }}
)
if [[ "${{ matrix.build_target }}" != "x86_64-linux-gnu" ]]; then
pkgs+=(
binutils-${{ matrix.build_target }}
gcc-${{ matrix.build_target }}
)
fi
if [[ "${{ matrix.arch }}" != "i386" ]]; then
pkgs+=(
# TCMalloc
libgoogle-perftools-dev:${{ matrix.arch }}
libgoogle-perftools4:${{ matrix.arch }}
libtcmalloc-minimal4:${{ matrix.arch }}
libunwind-dev:${{ matrix.arch }}
)
fi
DEBIAN_FRONTEND=noninteractive sudo apt install -y "${pkgs[@]}"
echo "CC=clang-7" >> $GITHUB_ENV
echo "CXX=clang++-7" >> $GITHUB_ENV
- name: Checkout the source
uses: actions/checkout@v2
with:
submodules: true
fetch-depth: 1
- name: Build
run: |
./ci.sh release \
-DJPEGXL_FORCE_SYSTEM_BROTLI=ON \
-DJPEGXL_BUNDLE_GFLAGS=ON \
-DJPEGXL_ENABLE_JNI=OFF \
${{ matrix.cmake_args }}
env:
SKIP_TEST: 1
BUILD_TARGET: ${{ matrix.build_target }}
- name: Build stats ${{ matrix.build_target }}
run: |
tools/build_stats.py --save build/stats.json \
--binutils ${{ matrix.build_target }}- \
--max-stack ${{ matrix.max_stack || '0' }} \
cjxl djxl libjxl.so libjxl_dec.so
# Run the tests on push and when requested in pull_request.
- name: Test
if: env.WILL_RUN_TESTS == 'true'
run: |
./ci.sh test
env:
BUILD_TARGET: ${{ matrix.build_target }}
windows_msys:
name: Windows MSYS2 / ${{ matrix.arch }}
name: Windows MSYS2 / ${{ matrix.msystem }}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
msystem: mingw64
- arch: i686
msystem: mingw32
- msystem: mingw64
- msystem: clang64
- msystem: mingw32
# TODO(eustas): investigate HWY Mul failures
disable_tests: HwyMulTestGroup/HwyMulTest\.TestAllMulHigh/EMU128|HwyMulTestGroup/HwyMulTest\.TestAllMulFixedPoint15/EMU128
- msystem: clang32
# TODO(eustas): investigate HWY Sort and JXL ANS failures
disable_tests: SortTestGroup/SortTest\.TestAllSort/.*|ANSTest\.RandomUnbalancedStreamRoundtrip3|ANSTest\.RandomUnbalancedStreamRoundtripBig
defaults:
run:
shell: msys2 {0}
@ -415,24 +288,22 @@ jobs:
install: >-
base-devel
git
mingw-w64-${{ matrix.arch }}-brotli
mingw-w64-${{ matrix.arch }}-cmake
mingw-w64-${{ matrix.arch }}-gcc
mingw-w64-${{ matrix.arch }}-gflags
mingw-w64-${{ matrix.arch }}-giflib
mingw-w64-${{ matrix.arch }}-gtest
mingw-w64-${{ matrix.arch }}-libavif
mingw-w64-${{ matrix.arch }}-libjpeg-turbo
mingw-w64-${{ matrix.arch }}-libpng
mingw-w64-${{ matrix.arch }}-libwebp
mingw-w64-${{ matrix.arch }}-ninja
pacboy: >-
brotli:p
cmake:p
giflib:p
gtest:p
libavif:p
libjpeg-turbo:p
libpng:p
libwebp:p
ninja:p
toolchain:p
- name: CMake configure
# AVX2 tests fail with segfault when built in MSYS2.
run: |
cmake \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_FLAGS="-DHWY_DISABLED_TARGETS=\"HWY_AVX2|HWY_AVX3\"" \
-DJPEGXL_ENABLE_JNI=OFF \
-DJPEGXL_ENABLE_MANPAGES=OFF \
-DJPEGXL_FORCE_SYSTEM_BROTLI=ON \
@ -445,20 +316,17 @@ jobs:
if: |
github.event_name == 'push' ||
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.names, 'CI:full'))
# LibraryCLinkageTest doesn't work in here because it needs the DLL
# which is not installed.
run: ctest --test-dir build --parallel 2 --output-on-failure -E LibraryCLinkageTest
contains(github.event.pull_request.labels.*.name, 'CI:full'))
run: ctest --test-dir build --parallel 2 --output-on-failure -E "${{ matrix.disable_tests }}"
wasm32_build:
name: WASM wasm32/${{ matrix.variant }}
runs-on: ubuntu-latest
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
EM_VERSION: 3.1.4
V8_VERSION: 9.8.177
V8: ${{ github.workspace }}/.jsvu/v8
BUILD_TARGET: wasm32
EM_VERSION: 3.1.1
NODE_VERSION: 18
strategy:
matrix:
@ -499,6 +367,14 @@ jobs:
restore-keys: |
${{ runner.os }}-${{ steps.git-env.outputs.parent }}-${{ matrix.variant }}
- name: Install node
uses: actions/setup-node@v3
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@v11
# TODO(deymo): We could cache this action but it doesn't work when running
@ -506,13 +382,12 @@ jobs:
with:
version: ${{env.EM_VERSION}}
no-cache: true
- name: Install v8
- name: Set EMSDK node version
run: |
npm install jsvu -g
HOME="${{ github.workspace }}" jsvu --os=linux64 "v8@${V8_VERSION}"
rm -f "${{ github.workspace }}/.jsvu/v8"
ln -s "${{ github.workspace }}/.jsvu/v8-${V8_VERSION}" \
"${{ github.workspace }}/.jsvu/v8"
echo "NODE_JS='$(cat $HOME/.base_node_path)'" >> $EM_CONFIG
emsdk construct_env
# TODO(deymo): Build and install other dependencies like libpng, libjpeg,
# etc.
- name: Build
@ -522,12 +397,9 @@ jobs:
if [[ "${{ matrix.variant }}" == "simd" ]]; then
export ENABLE_WASM_SIMD=1
fi
emconfigure ./ci.sh release \
./ci.sh release \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
-DBUILD_SHARED_LIBS=OFF \
-DJPEGXL_ENABLE_JNI=OFF
-DCMAKE_C_COMPILER_LAUNCHER=ccache
env:
SKIP_TEST: 1
- name: ccache stats
@ -537,6 +409,6 @@ jobs:
if: |
github.event_name == 'push' ||
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.names, 'CI:full'))
contains(github.event.pull_request.labels.*.name, 'CI:full'))
run: |
emconfigure ./ci.sh test
./ci.sh test

View file

@ -0,0 +1,170 @@
# Copyright (c) the JPEG XL Project 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:
push:
branches:
- main
- v*.*.x
pull_request:
types: [opened, reopened, labeled, synchronize]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
cross_compile_ubuntu:
name: Cross-compiling ${{ matrix.build_target }} ${{ matrix.lowprecision }}
runs-on: [ubuntu-22.04]
container:
image: debian:bullseye
strategy:
fail-fast: false
matrix:
include:
- arch: arm64
lowprecision:
build_target: aarch64-linux-gnu
cmake_args: -DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/qemu-aarch64-static
- arch: arm64
lowprecision: lowprecision
build_target: aarch64-linux-gnu
cmake_args: -DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/qemu-aarch64-static -DCMAKE_CXX_FLAGS=-DJXL_HIGH_PRECISION=0
- arch: armhf
lowprecision:
build_target: arm-linux-gnueabihf
cmake_args: -DCMAKE_CROSSCOMPILING_EMULATOR=/usr/bin/qemu-arm-static
- arch: i386
lowprecision:
test_in_pr: true
build_target: i686-linux-gnu
env:
BUILD_DIR: build
WILL_RUN_TESTS: ${{ github.event_name == 'push' || (github.event_name == 'pull_request' && (matrix.test_in_pr || contains(github.event.pull_request.labels.*.name, 'CI:full'))) }}
steps:
- name: Setup apt
shell: bash
run: |
set -x
apt-get update -y
apt-get install -y ca-certificates debian-ports-archive-keyring
dpkg --add-architecture "${{ matrix.arch }}"
# Update the sources.list with the split of supported architectures.
bkplist="/etc/apt/sources.list.bkp"
mv /etc/apt/sources.list "${bkplist}"
newlist="/etc/apt/sources.list"
rm -f "${newlist}"
main_list="amd64,${{ matrix.arch }}"
port_list=""
if [[ "${{ matrix.arch }}" == "i386" ]]; then
main_list="amd64,i386"
else
port_list="${{ matrix.arch }}"
fi
grep -v -E '^#' "${bkplist}" |
sed -E "s;^deb (http[^ ]+) (.*)\$;deb [arch=${main_list}] \\1 \\2\ndeb-src [arch=${main_list}] \\1 \\2;" \
| tee -a "${newlist}"
- name: Install build deps
shell: bash
run: |
set -x
apt update
pkgs=(
# Build dependencies
cmake
doxygen
git
graphviz
ninja-build
pkg-config
qemu-user-static
xdg-utils
xvfb
# Toolchain for cross-compiling.
clang-11
libc6-dev-${{ matrix.arch }}-cross
libstdc++-10-dev-${{ matrix.arch }}-cross
libstdc++-10-dev:${{ matrix.arch }}
# Dependencies
libbrotli-dev:${{ matrix.arch }}
libgif-dev:${{ matrix.arch }}
libjpeg-dev:${{ matrix.arch }}
libpng-dev:${{ matrix.arch }}
libwebp-dev:${{ matrix.arch }}
# For OpenEXR:
libilmbase-dev:${{ matrix.arch }}
libopenexr-dev:${{ matrix.arch }}
# GTK plugins
libgdk-pixbuf2.0-dev:${{ matrix.arch }}
libgtk2.0-dev:${{ matrix.arch }}
# QT
libqt5x11extras5-dev:${{ matrix.arch }}
qtbase5-dev:${{ matrix.arch }}
)
if [[ "${{ matrix.build_target }}" != "x86_64-linux-gnu" ]]; then
pkgs+=(
binutils-${{ matrix.build_target }}
gcc-${{ matrix.build_target }}
)
fi
if [[ "${{ matrix.arch }}" != "i386" ]]; then
pkgs+=(
# TCMalloc
libgoogle-perftools-dev:${{ matrix.arch }}
libgoogle-perftools4:${{ matrix.arch }}
libtcmalloc-minimal4:${{ matrix.arch }}
libunwind-dev:${{ matrix.arch }}
)
fi
DEBIAN_FRONTEND=noninteractive apt install -y "${pkgs[@]}"
echo "CC=clang-11" >> $GITHUB_ENV
echo "CXX=clang++-11" >> $GITHUB_ENV
- name: Checkout the source
uses: actions/checkout@v2
with:
submodules: true
fetch-depth: 1
- name: Build
run: |
./ci.sh release \
-DJPEGXL_FORCE_SYSTEM_BROTLI=ON \
-DJPEGXL_ENABLE_JNI=OFF \
${{ matrix.cmake_args }}
env:
SKIP_TEST: 1
BUILD_TARGET: ${{ matrix.build_target }}
- name: Build stats ${{ matrix.build_target }}
run: |
tools/build_stats.py --save build/stats.json \
--binutils ${{ matrix.build_target }}- \
--max-stack ${{ matrix.max_stack || '0' }} \
cjxl djxl libjxl.so libjxl_dec.so
# Run the tests on push and when requested in pull_request.
- name: Test
if: env.WILL_RUN_TESTS == 'true'
run: |
./ci.sh test
env:
BUILD_TARGET: ${{ matrix.build_target }}

View file

@ -14,13 +14,33 @@ on:
pull_request:
types: [opened, reopened, labeled, synchronize]
concurrency:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
build:
name: Conformance Build ${{ matrix.name }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: AVX3
cflags: -DHWY_DISABLED_TARGETS=HWY_AVX3-1
- name: AVX2
cflags: -DHWY_DISABLED_TARGETS=HWY_AVX2-1
- name: SSE4
cflags: -DHWY_DISABLED_TARGETS=HWY_SSE4-1
- name: SSSE3
cflags: -DHWY_DISABLED_TARGETS=HWY_SSSE3-1
- name: EMU128
cflags: -DHWY_COMPILE_ONLY_EMU128=1
- name: SCALAR
cflags: -DHWY_COMPILE_ONLY_SCALAR=1
- name: SCALAR_ASAN
cflags: -DHWY_COMPILE_ONLY_SCALAR=1
build_type: asan
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
steps:
@ -36,7 +56,6 @@ jobs:
libbenchmark-tools \
libbrotli-dev \
libgdk-pixbuf2.0-dev \
libgflags-dev \
libgif-dev \
libgtest-dev \
libgtk2.0-dev \
@ -76,29 +95,40 @@ jobs:
run: |
mkdir -p ${CCACHE_DIR}
echo "max_size = 200M" > ${CCACHE_DIR}/ccache.conf
./ci.sh release -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \
CMAKE_FLAGS="${{ matrix.cflags }}" \
./ci.sh ${{ matrix.build_type || 'release' }} -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DBUILD_TESTING=OFF
# Copy library to flatten the artifacts directory structure
# 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/libjxl_dec.so.0.7.0 build/tools/conformance
cp build/libjxl_threads.so.0.7.0 build/tools/conformance
env:
SKIP_TEST: 1
- uses: actions/upload-artifact@v2
with:
name: conformance_binary
name: conformance_binary-${{ matrix.name }}
path: |
build/tools/conformance/djxl_conformance
build/tools/conformance/conformance.py
build/tools/conformance/lcms2.py
build/tools/conformance/djxl
build/tools/conformance/libjxl_dec.so.0.7.0
build/tools/conformance/libjxl_threads.so.0.7.0
- name: ccache stats
run: ccache --show-stats
run:
name: Conformance Test ${{ matrix.name }} on ${{ matrix.target}}
needs: build
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
name: [main_level5, main_level10]
target: [AVX3, AVX2, SSE4, SSSE3, EMU128, SCALAR, SCALAR_ASAN]
steps:
- name: Install deps
run: |
@ -107,19 +137,20 @@ jobs:
uses: actions/checkout@v2
with:
repository: libjxl/conformance
ref: ce5a804a3ff834097b93c39d2f93e5445f6bf340
ref: a6a44bbbd69830e1dc862174599ce5738a0a414f
path: conformance
- name: Download and link conformance files
run: |
${{ github.workspace }}/conformance/scripts/download_and_symlink.sh
- uses: actions/download-artifact@v2
with:
name: conformance_binary
name: conformance_binary-${{ matrix.target }}
- name: Run conformance tests
run: |
chmod +x djxl_conformance
chmod +x djxl
ln -s libjxl_dec.so.0.7.0 libjxl_dec.so.0.7
ln -s libjxl_threads.so.0.7.0 libjxl_threads.so.0.7
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:`pwd`
python conformance/scripts/conformance.py \
--decoder=`pwd`/djxl_conformance \
python conformance.py \
--decoder=`pwd`/djxl \
--corpus=`pwd`/conformance/testcases/${{ matrix.name }}.txt

View file

@ -27,7 +27,6 @@ jobs:
doxygen \
libbrotli-dev \
libgdk-pixbuf2.0-dev \
libgflags-dev \
libgif-dev \
libgtest-dev \
libgtk2.0-dev \

View file

@ -35,7 +35,6 @@ jobs:
doxygen \
libbrotli-dev \
libgdk-pixbuf2.0-dev \
libgflags-dev \
libgif-dev \
libgtest-dev \
libgtk2.0-dev \
@ -146,6 +145,20 @@ jobs:
cd "git-${git_version}"
make prefix=/usr -j4 install
- name: Install gcc-8 (only 18.04)
if: matrix.os == 'ubuntu:18.04'
# Compiler bug workaround: install and use gcc-8
shell: 'bash'
run: |
apt install -y \
gcc-8 \
g++-8 \
#
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 100
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 100
update-alternatives --set g++ /usr/bin/g++-8
update-alternatives --set gcc /usr/bin/gcc-8
- name: Set git safe dir
run: |
export GIT_CEILING_DIRECTORIES=/__w # only work before git v2.35.2
@ -251,7 +264,7 @@ jobs:
windows_build:
name: Windows Build (vcpkg / ${{ matrix.triplet }})
runs-on: [windows-latest]
runs-on: [windows-2019]
strategy:
fail-fast: false
matrix:
@ -262,7 +275,7 @@ jobs:
arch: '-A x64'
env:
VCPKG_VERSION: '2021.05.12'
VCPKG_VERSION: '2022.06.16.1'
VCPKG_ROOT: vcpkg
VCPKG_DISABLE_METRICS: 1
@ -302,7 +315,6 @@ jobs:
run: |
set -x
${VCPKG_ROOT}/vcpkg --triplet ${{ matrix.triplet }} install \
gflags \
giflib \
libjpeg-turbo \
libpng \
@ -324,7 +336,6 @@ jobs:
-DJPEGXL_ENABLE_TCMALLOC=OFF \
-DJPEGXL_ENABLE_VIEWERS=OFF \
-DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} \
-DJPEGXL_BUNDLE_GFLAGS=ON \
#
- name: Build
shell: 'bash'

View file

@ -19,25 +19,33 @@ Google LLC <*@google.com>
Alex Xu (Hello71) <alex_y_xu@yahoo.ca>
Alexander Sago <cagelight@gmail.com>
Andrius Lukas Narbutas <andrius4669@gmail.com>
Aous Naman <aous@unsw.edu.au>
Artem Selishchev
Biswapriyo Nath <nathbappai@gmail.com>
CanadianBaconBoi <beamconnor@gmail.com>
Daniel Novomeský <dnovomesky@gmail.com>
David Burnett <vargolsoft@gmail.com>
Dirk Lemstra <dirk@lemstra.org>
Don Olmstead <don.j.olmstead@gmail.com>
Even Rouault <even.rouault@spatialys.com>
Heiko Becker <heirecka@exherbo.org>
Jon Sneyers <jon@cloudinary.com>
Kai Hollberg <Schweinepriester@users.noreply.github.com>
Kleis Auke Wolthuizen <github@kleisauke.nl>
L. E. Segovia
Leo Izen <leo.izen@gmail.com>
Lovell Fuller
Maarten DB <anonymous.maarten@gmail.com>
Marcin Konicki <ahwayakchih@gmail.com>
Martin Strunz
Mathieu Malaterre <mathieu.malaterre@gmail.com>
Mikk Leini <mikk.leini@krakul.eu>
Misaki Kasumi <misakikasumi@outlook.com>
Petr Diblík
Pieter Wuille
roland-rollo
Samuel Leong <wvvwvvvvwvvw@gmail.com>
Sandro <sandro.jaeckel@gmail.com>
Stephan T. Lavavej <stl@nuwen.net>
Vincent Torri <vincent.torri@gmail.com>
xiota

View file

@ -6,18 +6,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## [0.7] - 2022-07-21
### Added
- decoder API: Ability to decode the content of metadata boxes:
`JXL_DEC_BOX`, `JXL_DEC_BOX_NEED_MORE_OUTPUT`, `JxlDecoderSetBoxBuffer`,
`JXL_DEC_BOX`, `JXL_DEC_BOX_NEED_MORE_OUTPUT`, `JxlDecoderSetBoxBuffer`,
`JxlDecoderGetBoxType`, `JxlDecoderGetBoxSizeRaw` and
`JxlDecoderSetDecompressBoxes`
- decoder API: ability to mark the input is finished: `JxlDecoderCloseInput`
- encoder API: ability to add metadata boxes, added new functions
`JxlEncoderAddBox`, `JxlEncoderUseBoxes`, `JxlEncoderCloseBoxes` and
`JxlEncoderCloseFrames`.
`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.
@ -26,37 +34,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- decoder API: new function `JxlDecoderSetMultithreadedImageOutCallback`,
allowing output callbacks to receive more information about the number of
threads on which they are running.
- encoder API: added ability to set several encoder options to frames using
`JxlEncoderFrameSettingsSetOption`
- 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.
- decoder/encoder API: add two fields to `JXLBasicInfo`: `intrinsic_xsize`
and `intrinsic_ysize` to signal the intrinsic size.
- 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 compatiblity.
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
- encoder API: `JxlEncoderOptions`: use `JxlEncoderFrameSettings` instead
- 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
instead.
- encoder API: `JxlEncoderOptionsSetDistance`: use `JxlEncoderSetFrameDistance`
instead
instead.
- encoder API: `JxlEncoderOptionsSetLossless`: use `JxlEncoderSetFrameLossless`
instead
- encoder API: `JxlEncoderOptionsSetEffort`: use `JxlEncoderFrameSettingsSetOption(
frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, effort)` instead.
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.
`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`.

View file

@ -34,6 +34,16 @@ check_cxx_source_compiles(
JPEGXL_EMSCRIPTEN
)
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
)
message(STATUS "CMAKE_SYSTEM_PROCESSOR is ${CMAKE_SYSTEM_PROCESSOR}")
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-fsanitize=fuzzer-no-link" CXX_FUZZERS_SUPPORTED)
@ -51,12 +61,12 @@ if(CHECK_PIE_SUPPORTED)
endif()
### Project build options:
if(${CXX_FUZZERS_SUPPORTED})
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")
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
@ -76,14 +86,13 @@ set(WARNINGS_AS_ERRORS_DEFAULT false)
if((SANITIZER STREQUAL "msan") OR JPEGXL_EMSCRIPTEN)
set(BUNDLE_LIBPNG_DEFAULT YES)
set(BUNDLE_GFLAGS_DEFAULT YES)
else()
set(BUNDLE_LIBPNG_DEFAULT NO)
set(BUNDLE_GFLAGS_DEFAULT NO)
endif()
# Standard cmake naming for building shared libraries.
option(BUILD_SHARED_LIBS "Build shared libraries instead of static ones" ON)
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.")
@ -99,8 +108,6 @@ 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_GFLAGS ${BUNDLE_GFLAGS_DEFAULT} CACHE BOOL
"Build gflags from source and link it statically.")
set(JPEGXL_BUNDLE_LIBPNG ${BUNDLE_LIBPNG_DEFAULT} CACHE BOOL
"Build libpng from source and link it statically.")
set(JPEGXL_ENABLE_JNI true CACHE BOOL
@ -149,20 +156,20 @@ set(JPEGXL_FORCE_SYSTEM_HWY false CACHE BOOL
# 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})
if (NOT CMAKE_C_COMPILER_ID STREQUAL CMAKE_CXX_COMPILER_ID)
message(FATAL_ERROR "Different C/C++ compilers set: "
"${CMAKE_C_COMPILER_ID} vs ${CMAKE_CXX_COMPILER_ID}")
endif()
if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
# Android NDK's toolchain.cmake fakes the clang version in
# CMAKE_CXX_COMPILER_VERSION with an incorrect number, so ignore this.
if (NOT ${CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION} MATCHES "clang"
AND ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 6)
if (NOT CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION MATCHES "clang"
AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5)
message(FATAL_ERROR
"Minimum Clang version required is Clang 6, please update.")
"Minimum Clang version required is Clang 5, please update.")
endif()
elseif (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")
if (${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 7)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7)
message(FATAL_ERROR
"Minimum GCC version required is 7, please update.")
endif()
@ -234,7 +241,13 @@ if(JPEGXL_STATIC)
endif()
endif() # JPEGXL_STATIC
if ("${CXX_MACRO_PREFIX_MAP}")
if (JPEGXL_EMSCRIPTEN)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
endif()
if (CXX_MACRO_PREFIX_MAP)
add_compile_options(-fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.)
endif()
@ -254,6 +267,12 @@ add_definitions(
-D__TIME__="redacted"
)
# TODO(eustas): see https://github.com/google/highway/issues/836
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND NOT JXL_HWY_DISABLED_TARGETS_FORCED)
add_definitions(-DHWY_DISABLED_TARGETS=\(HWY_SVE|HWY_SVE2|HWY_SVE_256|HWY_SVE2_128|HWY_RVV\))
message("Warning: HWY_SVE, HWY_SVE2, HWY_SVE_256, HWY_SVE2_128 and HWY_RVV CPU targets are disabled")
endif()
# Avoid log spam from fopen etc.
if(MSVC)
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
@ -265,10 +284,10 @@ endif()
# Machine flags.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -funwind-tables")
if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -mrelax-all")
endif()
if ("${CXX_CONSTRUCTOR_ALIASES_SUPPORTED}")
if (CXX_CONSTRUCTOR_ALIASES_SUPPORTED)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -mconstructor-aliases")
endif()
@ -281,7 +300,7 @@ 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(CMAKE_SYSTEM_PROCESSOR MATCHES "arm")
if(JPEGXL_FORCE_NEON)
# GCC requires these flags, otherwise __ARM_NEON is undefined.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
@ -307,8 +326,10 @@ 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)
@ -318,7 +339,7 @@ enable_testing()
include(CTest)
# Specify default location of `testdata`:
if(NOT DEFINED JPEGXL_TEST_DATA_PATH)
set(JPEGXL_TEST_DATA_PATH "${PROJECT_SOURCE_DIR}/third_party/testdata")
set(JPEGXL_TEST_DATA_PATH "${PROJECT_SOURCE_DIR}/testdata")
endif()
# Libraries.
@ -382,9 +403,9 @@ endif() # JPEGXL_ENABLE_DOXYGEN
if(JPEGXL_ENABLE_MANPAGES)
find_program(ASCIIDOC a2x)
if(NOT "${ASCIIDOC}" STREQUAL "ASCIIDOC-NOTFOUND")
if(ASCIIDOC)
file(STRINGS "${ASCIIDOC}" ASCIIDOC_SHEBANG LIMIT_COUNT 1)
if(ASCIIDOC_SHEBANG MATCHES "/sh")
if(ASCIIDOC_SHEBANG MATCHES "/sh|/bash")
set(ASCIIDOC_PY_FOUND ON)
# Run the program directly and set ASCIIDOC as empty.
set(ASCIIDOC_PY "${ASCIIDOC}")
@ -401,7 +422,7 @@ else()
find_package(Python COMPONENTS Interpreter QUIET)
if(NOT Python_Interpreter_FOUND)
find_program(ASCIIDOC_PY python)
if(NOT ASCIIDOC_PY STREQUAL "ASCIIDOC_PY-NOTFOUND")
if(ASCIIDOC_PY)
set(ASCIIDOC_PY_FOUND ON)
endif()
else()
@ -432,16 +453,16 @@ if (ASCIIDOC_PY_FOUND)
endif() # ASCIIDOC_PY_FOUND
else()
message(WARNING "asciidoc was not found, the man pages will not be installed.")
endif() # ASCIIDOC != "ASCIIDOC-NOTFOUND"
endif() # ASCIIDOC
endif() # JPEGXL_ENABLE_MANPAGES
# Example usage code.
if (${JPEGXL_ENABLE_EXAMPLES})
if (JPEGXL_ENABLE_EXAMPLES)
include(examples/examples.cmake)
endif ()
# Plugins for third-party software
if (${JPEGXL_ENABLE_PLUGINS})
if (JPEGXL_ENABLE_PLUGINS)
add_subdirectory(plugins)
endif ()

View file

@ -1,7 +1,13 @@
# JPEG XL reference implementation
[![Build&Test](https://github.com/libjxl/libjxl/actions/workflows/build_test.yml/badge.svg)](
[![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)](
@ -63,7 +69,7 @@ Required dependencies for compiling the code, in a Debian/Ubuntu based
distribution run:
```bash
sudo apt install cmake pkg-config libbrotli-dev libgflags-dev
sudo apt install cmake pkg-config libbrotli-dev
```
Optional dependencies for supporting other formats in the `cjxl`/`djxl` tools,
@ -168,7 +174,7 @@ format: Cloudinary and Google.
* [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](http://ds.jpeg.org/whitepapers/jpeg-xl-whitepaper.pdf)
* [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)

View file

@ -103,6 +103,12 @@ test_printf_size_t() {
ret=1
fi
if grep -n -E 'gmock\.h' \
$(git ls-files | grep -E '(\.c|\.cc|\.cpp|\.h)$' | grep -v -F /test_utils.h); then
echo "Don't include gmock directly, instead include 'test_utils.h'. " >&2
ret=1
fi
local f
for f in $(git ls-files | grep -E "\.cc$" | xargs grep 'PRI[udx]S' |
cut -f 1 -d : | uniq); do
@ -116,7 +122,7 @@ test_printf_size_t() {
fi
done
for f in $(git ls-files | grep -E "\.h$" | grep -v -F printf_macros.h |
for f in $(git ls-files | grep -E "\.h$" | grep -v -E '(printf_macros\.h|test_utils\.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.

View file

@ -15,13 +15,14 @@ OS=`uname -s`
MYDIR=$(dirname $(realpath "$0"))
### Environment parameters:
TEST_STACK_LIMIT="${TEST_STACK_LIMIT:-128}"
TEST_STACK_LIMIT="${TEST_STACK_LIMIT:-256}"
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_TEST="${SKIP_TEST:-0}"
TEST_SELECTOR="${TEST_SELECTOR:-}"
BUILD_TARGET="${BUILD_TARGET:-}"
ENABLE_WASM_SIMD="${ENABLE_WASM_SIMD:-0}"
if [[ -n "${BUILD_TARGET}" ]]; then
@ -41,27 +42,11 @@ FUZZER_MAX_TIME="${FUZZER_MAX_TIME:-0}"
SANITIZER="none"
if [[ "${BUILD_TARGET}" == wasm* ]]; then
# Check that environment is setup for the WASM build target.
if [[ -z "${EMSCRIPTEN}" ]]; then
echo "'EMSCRIPTEN' is not defined. Use 'emconfigure' wrapper to setup WASM build environment" >&2
return 1
fi
# Remove the side-effect of "emconfigure" wrapper - it considers NodeJS environment.
unset EMMAKEN_JUST_CONFIGURE
EMS_TOOLCHAIN_FILE="${EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake"
if [[ -f "${EMS_TOOLCHAIN_FILE}" ]]; then
CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE:-${EMS_TOOLCHAIN_FILE}}
else
echo "Warning: EMSCRIPTEN CMake module not found" >&2
fi
CMAKE_CROSSCOMPILING_EMULATOR="${MYDIR}/js-wasm-wrapper.sh"
fi
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_SCALAR}
HWY_BASELINE_TARGETS=${HWY_BASELINE_TARGETS:-HWY_EMU128}
else
HWY_BASELINE_TARGETS=${HWY_BASELINE_TARGETS:-}
fi
@ -350,7 +335,6 @@ cmake_configure() {
-G Ninja
-DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS}"
-DCMAKE_C_FLAGS="${CMAKE_C_FLAGS}"
-DCMAKE_TOOLCHAIN_FILE="${CMAKE_TOOLCHAIN_FILE}"
-DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}"
-DCMAKE_MODULE_LINKER_FLAGS="${CMAKE_MODULE_LINKER_FLAGS}"
-DCMAKE_SHARED_LINKER_FLAGS="${CMAKE_SHARED_LINKER_FLAGS}"
@ -392,11 +376,14 @@ cmake_configure() {
# 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
# sjpeg confuses WASM SIMD with SSE.
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+=(
@ -457,7 +444,11 @@ cmake_configure() {
-DCMAKE_MAKE_PROGRAM="${CMAKE_MAKE_PROGRAM}"
)
fi
cmake "${args[@]}" "$@"
if [[ "${BUILD_TARGET}" == wasm* ]]; then
emcmake cmake "${args[@]}" "$@"
else
cmake "${args[@]}" "$@"
fi
}
cmake_build_and_test() {
@ -485,7 +476,7 @@ cmake_build_and_test() {
(cd "${BUILD_DIR}"
export UBSAN_OPTIONS=print_stacktrace=1
[[ "${TEST_STACK_LIMIT}" == "none" ]] || ulimit -s "${TEST_STACK_LIMIT}"
ctest -j $(nproc --all || echo 1) --output-on-failure)
ctest -j $(nproc --all || echo 1) ${TEST_SELECTOR} --output-on-failure)
fi
}
@ -494,7 +485,7 @@ cmake_build_and_test() {
# library.
strip_dead_code() {
# Emscripten does tree shaking without any extra flags.
if [[ "${CMAKE_TOOLCHAIN_FILE##*/}" == "Emscripten.cmake" ]]; then
if [[ "${BUILD_TARGET}" == wasm* ]]; then
return 0
fi
# -ffunction-sections, -fdata-sections and -Wl,--gc-sections effectively
@ -711,20 +702,24 @@ cmd_msan_install() {
export CC="${CC:-clang}"
export CXX="${CXX:-clang++}"
detect_clang_version
local llvm_tag="llvmorg-${CLANG_VERSION}.0.0"
case "${CLANG_VERSION}" in
"6.0")
llvm_tag="llvmorg-6.0.1"
;;
"7")
llvm_tag="llvmorg-7.0.1"
;;
esac
local llvm_targz="${tmpdir}/${llvm_tag}.tar.gz"
curl -L --show-error -o "${llvm_targz}" \
"https://github.com/llvm/llvm-project/archive/${llvm_tag}.tar.gz"
tar -C "${tmpdir}" -zxf "${llvm_targz}"
local llvm_root="${tmpdir}/llvm-project-${llvm_tag}"
# Allow overriding the LLVM checkout.
local llvm_root="${LLVM_ROOT}"
if [ -z "${llvm_root}" ]; then
local llvm_tag="llvmorg-${CLANG_VERSION}.0.0"
case "${CLANG_VERSION}" in
"6.0")
llvm_tag="llvmorg-6.0.1"
;;
"7")
llvm_tag="llvmorg-7.0.1"
;;
esac
local llvm_targz="${tmpdir}/${llvm_tag}.tar.gz"
curl -L --show-error -o "${llvm_targz}" \
"https://github.com/llvm/llvm-project/archive/${llvm_tag}.tar.gz"
tar -C "${tmpdir}" -zxf "${llvm_targz}"
llvm_root="${tmpdir}/llvm-project-${llvm_tag}"
fi
local msan_prefix="${HOME}/.msan/${CLANG_VERSION}"
rm -rf "${msan_prefix}"
@ -1020,11 +1015,11 @@ cmd_arm_benchmark() {
)
local images=(
"third_party/testdata/third_party/imagecompression.info/flower_foveon.png"
"testdata/jxl/flower/flower.png"
)
local jpg_images=(
"third_party/testdata/third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg"
"testdata/jxl/flower/flower.png.im_q85_420.jpg"
)
if [[ "${SKIP_CPUSET:-}" == "1" ]]; then
@ -1219,10 +1214,10 @@ cmd_lint() {
# 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}"
--style=file --diff "${MR_ANCESTOR_SHA}" -- >"${tmppatch}" || true
{ set +x; } 2>/dev/null
if grep -E '^--- ' "${tmppatch}">/dev/null; then
if [[ -n "${LINT_OUTPUT:-}" ]]; then
cp "${tmppatch}" "${LINT_OUTPUT}"
@ -1423,11 +1418,14 @@ cmd_bump_version() {
# Check that the AUTHORS file contains the email of the committer.
cmd_authors() {
merge_request_commits
# TODO(deymo): Handle multiple commits and check that they are all the same
# author.
local email=$(git log --format='%ae' "${MR_HEAD_SHA}^!")
local name=$(git log --format='%an' "${MR_HEAD_SHA}^!")
"${MYDIR}"/tools/check_author.py "${email}" "${name}"
local emails
local names
readarray -t emails < <(git log --format='%ae' "${MR_HEAD_SHA}...${MR_ANCESTOR_SHA}")
readarray -t names < <(git log --format='%an' "${MR_HEAD_SHA}...${MR_ANCESTOR_SHA}")
for i in "${!names[@]}"; do
echo "Checking name '${names[$i]}' with email '${emails[$i]}' ..."
"${MYDIR}"/tools/check_author.py "${emails[$i]}" "${names[$i]}"
done
}
main() {
@ -1490,6 +1488,7 @@ You can pass some optional environment variables as well:
- SKIP_TEST=1: Skip the test stage.
- STORE_IMAGES=0: Makes the benchmark discard the computed images.
- TEST_STACK_LIMIT: Stack size limit (ulimit -s) during tests, in KiB.
- TEST_SELECTOR: pass additional arguments to ctest, e.g. "-R .Resample.".
- STACK_SIZE=1: Generate binaries with the .stack_sizes sections.
These optional environment variables are forwarded to the cmake call as

View file

@ -26,7 +26,7 @@ foreach(brlib IN ITEMS ${brlibs})
)
if (${BRPREFIX}_LIBRARY AND NOT TARGET ${brlib})
if(${CMAKE_VERSION} VERSION_LESS "3.13.5")
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})

View file

@ -46,7 +46,7 @@ find_package_handle_standard_args(HWY
if (HWY_LIBRARY AND NOT TARGET hwy)
add_library(hwy INTERFACE IMPORTED GLOBAL)
if(${CMAKE_VERSION} VERSION_LESS "3.13.5")
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})

View file

@ -39,7 +39,7 @@ find_package_handle_standard_args(LCMS2
if (LCMS2_LIBRARY AND NOT TARGET lcms2)
add_library(lcms2 INTERFACE IMPORTED GLOBAL)
if(${CMAKE_VERSION} VERSION_LESS "3.13.5")
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})

View file

@ -9,7 +9,6 @@ Build-Depends:
debhelper (>= 9),
libbrotli-dev,
libgdk-pixbuf-2.0-dev | libgdk-pixbuf2.0-dev,
libgflags-dev | libgflag-dev,
libgif-dev,
libgimp2.0-dev,
libgmock-dev,

View file

@ -38,27 +38,7 @@ License: BSD-3-clause
(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: third_party/testdata/third_party/imagecompression.info/*
Copyright: their respective owners.
License: License without any prohibitive copyright restrictions.
See https://imagecompression.info/test_images/ for details.
.
These Images are available without any prohibitive copyright restrictions.
.
These images are (c) there respective owners. You are granted full
redistribution and publication rights on these images provided:
.
1. The origin of the pictures must not be misrepresented; you must not claim
that you took the original pictures. If you use, publish or redistribute them,
an acknowledgment would be appreciated but is not required.
2. Altered versions must be plainly marked as such, and must not be
misinterpreted as being the originals.
3. No payment is required for distribution of this material, it must be
available freely under the conditions stated here. That is, it is prohibited to
sell the material.
4. This notice may not be removed or altered from any distribution.
Files: third_party/testdata/third_party/pngsuite/*
Files: testdata/external/pngsuite/*
Copyright: Willem van Schaik, 1996, 2011
License: PngSuite License
See http://www.schaik.com/pngsuite/ for details.
@ -66,15 +46,15 @@ License: PngSuite License
Permission to use, copy, modify and distribute these images for any
purpose and without fee is hereby granted.
Files: third_party/testdata/raw.pixls/*
Files: testdata/external/raw.pixls/*
Copyright: their respective owners listed in https://raw.pixls.us/
License: CC0-1.0
Files: third_party/testdata/raw.pixls/*
Files: testdata/external/wesaturate/*
Copyright: their respective owners listed in https://www.wesaturate.com/
License: CC0-1.0
Files: third_party/testdata/third_party/wide-gamut-tests/
Files: testdata/external/wide-gamut-tests/
Copyright: github.com/codelogic/wide-gamut-tests authors.
License: Apache-2.0

View file

@ -14,8 +14,7 @@ MYDIR=$(dirname $(realpath "$0"))
# Git revisions we use for the given submodules. Update these whenever you
# update a git submodule.
THIRD_PARTY_BROTLI="35ef5c554d888bef217d449346067de05e269b30"
THIRD_PARTY_GFLAGS="827c769e5fc98e0f2a34c47cef953cc6328abced"
THIRD_PARTY_HIGHWAY="f13e3b956eb226561ac79427893ec0afd66f91a8"
THIRD_PARTY_HIGHWAY="e41973e26b3a870fcfb63fa2d4f2bcd57fe94de5"
THIRD_PARTY_SKCMS="64374756e03700d649f897dbd98c95e78c30c7da"
THIRD_PARTY_SJPEG="868ab558fad70fcbe8863ba4e85179eeb81cc840"
THIRD_PARTY_ZLIB="cacf7f1d4e3d44d871b605da3b647f07d718623f"
@ -73,7 +72,6 @@ EOF
# Sources downloaded from a tarball.
download_github third_party/brotli google/brotli
download_github third_party/gflags gflags/gflags
download_github third_party/highway google/highway
download_github third_party/sjpeg webmproject/sjpeg
download_github third_party/skcms \

View file

@ -1,19 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# Continuous integration helper module. This module is meant to be called from
# the .gitlab-ci.yml file during the continuous integration build, as well as
# from the command line for developers.
# This wrapper is used to enable WASM SIMD when running tests.
# Unfortunately, it is impossible to pass the option directly via the
# CMAKE_CROSSCOMPILING_EMULATOR variable.
# Fallback to default v8 binary, if override is not set.
V8="${V8:-$(which v8)}"
SCRIPT="$1"
shift
"${V8}" --experimental-wasm-simd "${SCRIPT}" -- "$@"

View file

@ -52,7 +52,7 @@ set(JPEGXL_INTERNAL_FLAGS
)
# Warning flags supported by clang.
if (${CMAKE_CXX_COMPILER_ID} MATCHES "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.
@ -90,7 +90,7 @@ if (WIN32)
-Wno-zero-as-null-pointer-constant
)
if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
list(APPEND JPEGXL_INTERNAL_FLAGS
-Wno-used-but-marked-unused
-Wno-unused-template
@ -110,7 +110,7 @@ else() # WIN32
-fmath-errno
)
if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
list(APPEND JPEGXL_INTERNAL_FLAGS
-fnew-alignment=8
-fno-cxx-exceptions
@ -136,7 +136,9 @@ endif() #!MSVC
include(jxl.cmake)
# Other libraries outside the core jxl library.
include(jxl_extras.cmake)
if(JPEGXL_ENABLE_TOOLS)
include(jxl_extras.cmake)
endif()
include(jxl_threads.cmake)
# Install all the library headers from the source and the generated ones. There

View file

@ -52,7 +52,7 @@ Status SetFromBytes(const Span<const uint8_t> bytes,
Status SetFromFile(const std::string& pathname,
const extras::ColorHints& color_hints, CodecInOut* io,
ThreadPool* pool, extras::Codec* orig_codec) {
PaddedBytes encoded;
std::vector<uint8_t> encoded;
JXL_RETURN_IF_ERROR(ReadFile(pathname, &encoded));
JXL_RETURN_IF_ERROR(SetFromBytes(Span<const uint8_t>(encoded), color_hints,
io, pool, orig_codec));
@ -61,7 +61,7 @@ Status SetFromFile(const std::string& pathname,
Status Encode(const CodecInOut& io, const extras::Codec codec,
const ColorEncoding& c_desired, size_t bits_per_sample,
PaddedBytes* bytes, ThreadPool* pool) {
std::vector<uint8_t>* bytes, ThreadPool* pool) {
JXL_CHECK(!io.Main().c_current().ICC().empty());
JXL_CHECK(!c_desired.ICC().empty());
io.CheckMetadata();
@ -70,18 +70,24 @@ Status Encode(const CodecInOut& io, const extras::Codec codec,
}
extras::PackedPixelFile ppf;
size_t num_channels = io.metadata.m.color_encoding.Channels();
JxlPixelFormat format = {
static_cast<uint32_t>(num_channels),
0, // num_channels is ignored by the converter
bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16,
JXL_NATIVE_ENDIAN, 0};
std::vector<uint8_t> bytes_vector;
const bool floating_point = bits_per_sample > 16;
extras::EncodedImage encoded_image;
switch (codec) {
case extras::Codec::kPNG:
#if JPEGXL_ENABLE_APNG
return extras::EncodeImageAPNG(&io, c_desired, bits_per_sample, pool,
bytes);
format.endianness = JXL_BIG_ENDIAN;
JXL_RETURN_IF_ERROR(extras::ConvertCodecInOutToPackedPixelFile(
io, format, c_desired, pool, &ppf));
JXL_RETURN_IF_ERROR(
extras::GetAPNGEncoder()->Encode(ppf, &encoded_image, pool));
JXL_ASSERT(encoded_image.bitstreams.size() == 1);
*bytes = encoded_image.bitstreams[0];
return true;
#else
return JXL_FAILURE("JPEG XL was built without (A)PNG support");
#endif
@ -109,14 +115,14 @@ Status Encode(const CodecInOut& io, const extras::Codec codec,
}
JXL_RETURN_IF_ERROR(extras::ConvertCodecInOutToPackedPixelFile(
io, format, c_desired, pool, &ppf));
JXL_RETURN_IF_ERROR(extras::EncodeImagePNM(
ppf, bits_per_sample, pool, /*frame_index=*/0, &bytes_vector));
JXL_RETURN_IF_ERROR(
extras::EncodeImagePNM(ppf.frames[0].color, ppf.info.orientation,
bits_per_sample, &bytes_vector));
bytes->assign(bytes_vector.data(),
bytes_vector.data() + bytes_vector.size());
return true;
case extras::Codec::kPGX:
return extras::EncodeImagePGX(&io, c_desired, bits_per_sample, pool,
bytes);
return JXL_FAILURE("Encoding CodecInOut to PGX is not implemented");
case extras::Codec::kGIF:
return JXL_FAILURE("Encoding to GIF is not implemented");
case extras::Codec::kEXR:
@ -163,7 +169,7 @@ Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired,
bits_per_sample = 16;
}
PaddedBytes encoded;
std::vector<uint8_t> encoded;
return Encode(io, codec, c_desired, bits_per_sample, &encoded, pool) &&
WriteFile(encoded, pathname);
}

View file

@ -49,7 +49,7 @@ Status SetFromFile(const std::string& pathname,
// color space to c_desired.
Status Encode(const CodecInOut& io, extras::Codec codec,
const ColorEncoding& c_desired, size_t bits_per_sample,
PaddedBytes* bytes, ThreadPool* pool = nullptr);
std::vector<uint8_t>* bytes, ThreadPool* pool = nullptr);
// Deduces codec, calls Encode and writes to file.
Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired,

View file

@ -9,12 +9,15 @@
#include <stdio.h>
#include <algorithm>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "gtest/gtest.h"
#include "lib/extras/dec/pgx.h"
#include "lib/extras/dec/pnm.h"
#include "lib/extras/enc/encode.h"
#include "lib/extras/packed_image_convert.h"
#include "lib/jxl/base/printf_macros.h"
#include "lib/jxl/base/random.h"
#include "lib/jxl/base/thread_pool_internal.h"
@ -23,12 +26,20 @@
#include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/image_test_utils.h"
#include "lib/jxl/test_utils.h"
#include "lib/jxl/testdata.h"
namespace jxl {
namespace extras {
namespace {
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::NotNull;
using ::testing::SizeIs;
std::string ExtensionFromCodec(Codec codec, const bool is_gray,
const bool has_alpha,
const size_t bits_per_sample) {
@ -54,159 +65,207 @@ std::string ExtensionFromCodec(Codec codec, const bool is_gray,
return std::string();
}
CodecInOut CreateTestImage(const size_t xsize, const size_t ysize,
const bool is_gray, const bool add_alpha,
const size_t bits_per_sample,
const ColorEncoding& c_native) {
Image3F image(xsize, ysize);
Rng rng(129);
if (is_gray) {
for (size_t y = 0; y < ysize; ++y) {
float* JXL_RESTRICT row0 = image.PlaneRow(0, y);
float* JXL_RESTRICT row1 = image.PlaneRow(1, y);
float* JXL_RESTRICT row2 = image.PlaneRow(2, y);
for (size_t x = 0; x < xsize; ++x) {
row0[x] = row1[x] = row2[x] = rng.UniformF(0.0f, 1.0f);
void VerifySameImage(const PackedImage& im0, size_t bits_per_sample0,
const PackedImage& im1, size_t bits_per_sample1) {
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);
auto pixels0 = static_cast<const uint8_t*>(im0.pixels());
auto pixels1 = static_cast<const uint8_t*>(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 = 0.5 * std::min(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;
}
}
} else {
RandomFillImage(&image, 0.0f, 1.0f);
}
CodecInOut io;
}
if (bits_per_sample == 32) {
io.metadata.m.SetFloat32Samples();
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;
return c;
}
std::vector<uint8_t> GenerateICC(JxlColorEncoding color_encoding) {
ColorEncoding c;
JXL_CHECK(ConvertExternalToInternalColorEncoding(color_encoding, &c));
JXL_CHECK(c.CreateICC());
PaddedBytes icc = c.ICC();
return std::vector<uint8_t>(icc.begin(), icc.end());
}
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 {
io.metadata.m.SetUintSamples(bits_per_sample);
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(val, out);
} else {
StoreLE32(val, out);
}
}
io.metadata.m.color_encoding = c_native;
io.SetFromImage(std::move(image), c_native);
if (add_alpha) {
ImageF alpha(xsize, ysize);
RandomFillImage(&alpha, 0.0f, 1.f);
io.metadata.m.SetAlphaBits(bits_per_sample <= 8 ? 8 : 16);
io.Main().SetAlpha(std::move(alpha), /*alpha_is_premultiplied=*/false);
}
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<uint8_t*>(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;
}
}
}
return io;
}
struct TestImageParams {
size_t xsize;
size_t ysize;
size_t bits_per_sample;
bool is_gray;
bool add_alpha;
bool big_endian;
bool ShouldTestRoundtrip(Codec codec) const {
if (codec == Codec::kPNG) {
return true;
} else if (codec == Codec::kPNM) {
return ((bits_per_sample <= 16 && big_endian) ||
(bits_per_sample == 32 && !add_alpha));
} else if (codec == Codec::kPGX) {
return ((bits_per_sample == 8 || bits_per_sample == 16) && is_gray &&
!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;
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;
JxlColorEncoding color_encoding = CreateTestColorEncoding(params.is_gray);
ppf->icc = GenerateICC(color_encoding);
PackedFrame frame(params.xsize, params.ysize, params.PixelFormat());
FillPackedImage(params.bits_per_sample, &frame.color);
ppf->frames.emplace_back(std::move(frame));
}
// Ensures reading a newly written file leads to the same image pixels.
void TestRoundTrip(Codec codec, const size_t xsize, const size_t ysize,
const bool is_gray, const bool add_alpha,
const size_t bits_per_sample, ThreadPool* pool) {
// JPEG encoding is not lossless.
if (codec == Codec::kJPG) return;
if (codec == Codec::kPNM && add_alpha) return;
// Our EXR codec always uses 16-bit premultiplied alpha, does not support
// grayscale, and somehow does not have sufficient precision for this test.
if (codec == Codec::kEXR) return;
printf("Codec %s bps:%" PRIuS " gr:%d al:%d\n",
ExtensionFromCodec(codec, is_gray, add_alpha, bits_per_sample).c_str(),
bits_per_sample, is_gray, add_alpha);
void TestRoundTrip(Codec codec, const TestImageParams& params,
ThreadPool* pool) {
if (!params.ShouldTestRoundtrip(codec)) return;
ColorEncoding c_native;
c_native.SetColorSpace(is_gray ? ColorSpace::kGray : ColorSpace::kRGB);
// Note: this must not be wider than c_external, otherwise gamut clipping
// will cause large round-trip errors.
c_native.primaries = Primaries::kP3;
c_native.tf.SetTransferFunction(TransferFunction::kLinear);
JXL_CHECK(c_native.CreateICC());
std::string extension = ExtensionFromCodec(
codec, params.is_gray, params.add_alpha, params.bits_per_sample);
printf("Codec %s %s\n", extension.c_str(), params.DebugString().c_str());
// Generally store same color space to reduce round trip errors..
ColorEncoding c_external = c_native;
// .. unless we have enough precision for some transforms.
if (bits_per_sample >= 16) {
c_external.white_point = WhitePoint::kE;
c_external.primaries = Primaries::k2100;
c_external.tf.SetTransferFunction(TransferFunction::kSRGB);
}
JXL_CHECK(c_external.CreateICC());
PackedPixelFile ppf_in;
CreateTestImage(params, &ppf_in);
const CodecInOut io = CreateTestImage(xsize, ysize, is_gray, add_alpha,
bits_per_sample, c_native);
const ImageBundle& ib1 = io.Main();
EncodedImage encoded;
auto encoder = Encoder::FromExtension(extension);
ASSERT_TRUE(encoder.get());
ASSERT_TRUE(encoder->Encode(ppf_in, &encoded, pool));
ASSERT_EQ(encoded.bitstreams.size(), 1);
PaddedBytes encoded;
JXL_CHECK(Encode(io, codec, c_external, bits_per_sample, &encoded, pool));
PackedPixelFile ppf_out;
ASSERT_TRUE(DecodeBytes(Span<const uint8_t>(encoded.bitstreams[0]),
ColorHints(), SizeConstraints(), &ppf_out));
CodecInOut io2;
ColorHints color_hints;
// Only for PNM because PNG will warn about ignoring them.
if (codec == Codec::kPNM) {
color_hints.Add("color_space", Description(c_external));
}
JXL_CHECK(SetFromBytes(Span<const uint8_t>(encoded), color_hints, &io2, pool,
nullptr));
ImageBundle& ib2 = io2.Main();
EXPECT_EQ(Description(c_external),
Description(io2.metadata.m.color_encoding));
// See c_external above - for low bits_per_sample the encoded space is
// already the same.
if (bits_per_sample < 16) {
EXPECT_EQ(Description(ib1.c_current()), Description(ib2.c_current()));
if (codec != Codec::kPNM && codec != Codec::kPGX) {
EXPECT_EQ(ppf_in.icc, ppf_out.icc);
}
if (add_alpha) {
EXPECT_TRUE(SamePixels(ib1.alpha(), *ib2.alpha()));
}
JXL_CHECK(ib2.TransformTo(ib1.c_current(), GetJxlCms(), pool));
double max_l1, max_rel;
// Round-trip tolerances must be higher than in external_image_test because
// codecs do not support unbounded ranges.
#if JPEGXL_ENABLE_SKCMS
if (bits_per_sample <= 12) {
max_l1 = 0.5;
max_rel = 6E-3;
} else {
max_l1 = 1E-3;
max_rel = 5E-4;
}
#else // JPEGXL_ENABLE_SKCMS
if (bits_per_sample <= 12) {
max_l1 = 0.5;
max_rel = 6E-3;
} else if (bits_per_sample == 16) {
max_l1 = 3E-3;
max_rel = 1E-4;
} else {
#ifdef __ARM_ARCH
// pow() implementation in arm is a bit less precise than in x86 and
// therefore we need a bigger error margin in this case.
max_l1 = 1E-7;
max_rel = 1E-4;
#else
max_l1 = 1E-7;
max_rel = 1E-5;
#endif
}
#endif // JPEGXL_ENABLE_SKCMS
VerifyRelativeError(ib1.color(), *ib2.color(), max_l1, max_rel);
ASSERT_EQ(ppf_out.frames.size(), 1);
VerifySameImage(ppf_in.frames[0].color, ppf_in.info.bits_per_sample,
ppf_out.frames[0].color, ppf_out.info.bits_per_sample);
}
#if 0
TEST(CodecTest, TestRoundTrip) {
ThreadPoolInternal pool(12);
const size_t xsize = 7;
const size_t ysize = 4;
TestImageParams params;
params.xsize = 7;
params.ysize = 4;
for (Codec codec : Values<Codec>()) {
for (int bits_per_sample : {8, 10, 12, 16, 32}) {
for (Codec codec : AvailableCodecs()) {
for (int bits_per_sample : {4, 8, 10, 12, 16, 32}) {
for (bool is_gray : {false, true}) {
for (bool add_alpha : {false, true}) {
TestRoundTrip(codec, xsize, ysize, is_gray, add_alpha,
static_cast<size_t>(bits_per_sample), &pool);
for (bool big_endian : {false, true}) {
params.bits_per_sample = static_cast<size_t>(bits_per_sample);
params.is_gray = is_gray;
params.add_alpha = add_alpha;
params.big_endian = big_endian;
TestRoundTrip(codec, params, &pool);
}
}
}
}
}
}
#endif
CodecInOut DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
const ColorHints& color_hints = ColorHints()) {
@ -217,7 +276,7 @@ CodecInOut DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
const ImageBundle& ib1 = io.Main();
// Encode/Decode again to make sure Encode carries through all metadata.
PaddedBytes encoded;
std::vector<uint8_t> encoded;
JXL_CHECK(Encode(io, Codec::kPNG, io.metadata.m.color_encoding,
io.metadata.m.bit_depth.bits_per_sample, &encoded, pool));
@ -256,11 +315,11 @@ CodecInOut DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
TEST(CodecTest, TestMetadataSRGB) {
ThreadPoolInternal pool(12);
const char* paths[] = {"third_party/raw.pixls/DJI-FC6310-16bit_srgb8_v4_krita.png",
"third_party/raw.pixls/Google-Pixel2XL-16bit_srgb8_v4_krita.png",
"third_party/raw.pixls/HUAWEI-EVA-L09-16bit_srgb8_dt.png",
"third_party/raw.pixls/Nikon-D300-12bit_srgb8_dt.png",
"third_party/raw.pixls/Sony-DSC-RX1RM2-14bit_srgb8_v4_krita.png"};
const char* paths[] = {"external/raw.pixls/DJI-FC6310-16bit_srgb8_v4_krita.png",
"external/raw.pixls/Google-Pixel2XL-16bit_srgb8_v4_krita.png",
"external/raw.pixls/HUAWEI-EVA-L09-16bit_srgb8_dt.png",
"external/raw.pixls/Nikon-D300-12bit_srgb8_dt.png",
"external/raw.pixls/Sony-DSC-RX1RM2-14bit_srgb8_v4_krita.png"};
for (const char* relative_pathname : paths) {
const CodecInOut io =
DecodeRoundtrip(relative_pathname, Codec::kPNG, &pool);
@ -285,9 +344,9 @@ TEST(CodecTest, TestMetadataLinear) {
ThreadPoolInternal pool(12);
const char* paths[3] = {
"third_party/raw.pixls/Google-Pixel2XL-16bit_acescg_g1_v4_krita.png",
"third_party/raw.pixls/HUAWEI-EVA-L09-16bit_709_g1_dt.png",
"third_party/raw.pixls/Nikon-D300-12bit_2020_g1_dt.png",
"external/raw.pixls/Google-Pixel2XL-16bit_acescg_g1_v4_krita.png",
"external/raw.pixls/HUAWEI-EVA-L09-16bit_709_g1_dt.png",
"external/raw.pixls/Nikon-D300-12bit_2020_g1_dt.png",
};
const WhitePoint white_points[3] = {WhitePoint::kCustom, WhitePoint::kD65,
WhitePoint::kD65};
@ -317,8 +376,8 @@ TEST(CodecTest, TestMetadataICC) {
ThreadPoolInternal pool(12);
const char* paths[] = {
"third_party/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png",
"third_party/raw.pixls/Sony-DSC-RX1RM2-14bit_709_v4_krita.png",
"external/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png",
"external/raw.pixls/Sony-DSC-RX1RM2-14bit_709_v4_krita.png",
};
for (const char* relative_pathname : paths) {
const CodecInOut io =
@ -340,28 +399,28 @@ TEST(CodecTest, TestMetadataICC) {
}
}
TEST(CodecTest, Testthird_party/pngsuite) {
TEST(CodecTest, Testexternal/pngsuite) {
ThreadPoolInternal pool(12);
// Ensure we can load PNG with text, japanese UTF-8, compressed text.
(void)DecodeRoundtrip("third_party/pngsuite/ct1n0g04.png", Codec::kPNG, &pool);
(void)DecodeRoundtrip("third_party/pngsuite/ctjn0g04.png", Codec::kPNG, &pool);
(void)DecodeRoundtrip("third_party/pngsuite/ctzn0g04.png", Codec::kPNG, &pool);
(void)DecodeRoundtrip("external/pngsuite/ct1n0g04.png", Codec::kPNG, &pool);
(void)DecodeRoundtrip("external/pngsuite/ctjn0g04.png", Codec::kPNG, &pool);
(void)DecodeRoundtrip("external/pngsuite/ctzn0g04.png", Codec::kPNG, &pool);
// Extract gAMA
const CodecInOut b1 =
DecodeRoundtrip("third_party/pngsuite/g10n3p04.png", Codec::kPNG, &pool);
DecodeRoundtrip("external/pngsuite/g10n3p04.png", Codec::kPNG, &pool);
EXPECT_TRUE(b1.metadata.color_encoding.tf.IsLinear());
// Extract cHRM
const CodecInOut b_p =
DecodeRoundtrip("third_party/pngsuite/ccwn2c08.png", Codec::kPNG, &pool);
DecodeRoundtrip("external/pngsuite/ccwn2c08.png", Codec::kPNG, &pool);
EXPECT_EQ(Primaries::kSRGB, b_p.metadata.color_encoding.primaries);
EXPECT_EQ(WhitePoint::kD65, b_p.metadata.color_encoding.white_point);
// Extract EXIF from (new-style) dedicated chunk
const CodecInOut b_exif =
DecodeRoundtrip("third_party/pngsuite/exif2c08.png", Codec::kPNG, &pool);
DecodeRoundtrip("external/pngsuite/exif2c08.png", Codec::kPNG, &pool);
EXPECT_EQ(978, b_exif.blobs.exif.size());
}
#endif
@ -384,18 +443,88 @@ void VerifyWideGamutMetadata(const std::string& relative_pathname,
TEST(CodecTest, TestWideGamut) {
ThreadPoolInternal pool(12);
// VerifyWideGamutMetadata("third_party/wide-gamut-tests/P3-sRGB-color-bars.png",
// VerifyWideGamutMetadata("external/wide-gamut-tests/P3-sRGB-color-bars.png",
// Primaries::kP3, &pool);
VerifyWideGamutMetadata("third_party/wide-gamut-tests/P3-sRGB-color-ring.png",
VerifyWideGamutMetadata("external/wide-gamut-tests/P3-sRGB-color-ring.png",
Primaries::kP3, &pool);
// VerifyWideGamutMetadata("third_party/wide-gamut-tests/R2020-sRGB-color-bars.png",
// VerifyWideGamutMetadata("external/wide-gamut-tests/R2020-sRGB-color-bars.png",
// Primaries::k2100, &pool);
// VerifyWideGamutMetadata("third_party/wide-gamut-tests/R2020-sRGB-color-ring.png",
// VerifyWideGamutMetadata("external/wide-gamut-tests/R2020-sRGB-color-ring.png",
// Primaries::k2100, &pool);
}
TEST(CodecTest, TestPNM) { TestCodecPNM(); }
TEST(CodecTest, FormatNegotiation) {
const std::vector<JxlPixelFormat> accepted_formats = {
{/*num_channels=*/4,
/*data_type=*/JXL_TYPE_UINT16,
/*endianness=*/JXL_NATIVE_ENDIAN,
/*align=*/0},
{/*num_channels=*/3,
/*data_type=*/JXL_TYPE_UINT8,
/*endianness=*/JXL_NATIVE_ENDIAN,
/*align=*/0},
{/*num_channels=*/3,
/*data_type=*/JXL_TYPE_UINT16,
/*endianness=*/JXL_NATIVE_ENDIAN,
/*align=*/0},
{/*num_channels=*/1,
/*data_type=*/JXL_TYPE_UINT8,
/*endianness=*/JXL_NATIVE_ENDIAN,
/*align=*/0},
};
JxlBasicInfo info;
JxlEncoderInitBasicInfo(&info);
info.bits_per_sample = 12;
info.num_color_channels = 2;
JxlPixelFormat format;
EXPECT_FALSE(SelectFormat(accepted_formats, info, &format));
info.num_color_channels = 3;
ASSERT_TRUE(SelectFormat(accepted_formats, info, &format));
EXPECT_EQ(format.num_channels, info.num_color_channels);
// 16 is the smallest accepted format that can accommodate the 12-bit data.
EXPECT_EQ(format.data_type, JXL_TYPE_UINT16);
}
TEST(CodecTest, EncodeToPNG) {
ThreadPool* const pool = nullptr;
std::unique_ptr<Encoder> png_encoder = Encoder::FromExtension(".png");
ASSERT_THAT(png_encoder, NotNull());
const PaddedBytes original_png =
ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
PackedPixelFile ppf;
ASSERT_TRUE(extras::DecodeBytes(Span<const uint8_t>(original_png),
ColorHints(), SizeConstraints(), &ppf));
const JxlPixelFormat& format = ppf.frames.front().color.format;
ASSERT_THAT(
png_encoder->AcceptedFormats(),
Contains(AllOf(Field(&JxlPixelFormat::num_channels, format.num_channels),
Field(&JxlPixelFormat::data_type, format.data_type),
Field(&JxlPixelFormat::endianness, format.endianness))));
EncodedImage encoded_png;
ASSERT_TRUE(png_encoder->Encode(ppf, &encoded_png, pool));
EXPECT_THAT(encoded_png.icc, IsEmpty());
ASSERT_THAT(encoded_png.bitstreams, SizeIs(1));
PackedPixelFile decoded_ppf;
ASSERT_TRUE(
extras::DecodeBytes(Span<const uint8_t>(encoded_png.bitstreams.front()),
ColorHints(), SizeConstraints(), &decoded_ppf));
ASSERT_EQ(decoded_ppf.info.bits_per_sample, ppf.info.bits_per_sample);
ASSERT_EQ(decoded_ppf.frames.size(), 1);
VerifySameImage(ppf.frames[0].color, ppf.info.bits_per_sample,
decoded_ppf.frames[0].color,
decoded_ppf.info.bits_per_sample);
}
} // namespace
} // namespace extras
} // namespace jxl

View file

@ -31,6 +31,25 @@ constexpr size_t kMinBytes = 9;
} // namespace
std::vector<Codec> AvailableCodecs() {
std::vector<Codec> out;
#if JPEGXL_ENABLE_APNG
out.push_back(Codec::kPNG);
#endif
#if JPEGXL_ENABLE_EXR
out.push_back(Codec::kEXR);
#endif
#if JPEGXL_ENABLE_GIF
out.push_back(Codec::kGIF);
#endif
#if JPEGXL_ENABLE_JPEG
out.push_back(Codec::kJPG);
#endif
out.push_back(Codec::kPGX);
out.push_back(Codec::kPNM);
return out;
}
Codec CodecFromExtension(std::string extension,
size_t* JXL_RESTRICT bits_per_sample) {
std::transform(

View file

@ -12,15 +12,12 @@
#include <stdint.h>
#include <string>
#include <vector>
#include "lib/extras/dec/color_hints.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/codec_in_out.h"
#include "lib/jxl/field_encodings.h" // MakeBit
namespace jxl {
namespace extras {
@ -36,27 +33,14 @@ enum class Codec : uint32_t {
kEXR
};
static inline constexpr uint64_t EnumBits(Codec /*unused*/) {
// Return only fully-supported codecs (kGIF is decode-only).
return MakeBit(Codec::kPNM)
#if JPEGXL_ENABLE_APNG
| MakeBit(Codec::kPNG)
#endif
#if JPEGXL_ENABLE_JPEG
| MakeBit(Codec::kJPG)
#endif
#if JPEGXL_ENABLE_EXR
| MakeBit(Codec::kEXR)
#endif
;
}
std::vector<Codec> AvailableCodecs();
// If and only if extension is ".pfm", *bits_per_sample is updated to 32 so
// that Encode() would encode to PFM instead of PPM.
Codec CodecFromExtension(std::string extension,
size_t* JXL_RESTRICT bits_per_sample = nullptr);
// Decodes "bytes" and sets io->metadata.m.
// Decodes "bytes" info *ppf.
// color_space_hint may specify the color space, otherwise, defaults to sRGB.
Status DecodeBytes(Span<const uint8_t> bytes, const ColorHints& color_hints,
const SizeConstraints& constraints,

View file

@ -0,0 +1,480 @@
// Copyright (c) the JPEG XL Project 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 "jxl/decode.h"
#include "jxl/decode_cxx.h"
#include "jxl/types.h"
#include "lib/extras/dec/color_description.h"
#include "lib/extras/enc/encode.h"
#include "lib/jxl/base/printf_macros.h"
namespace jxl {
namespace extras {
namespace {
struct BoxProcessor {
BoxProcessor(JxlDecoder* dec) : dec_(dec) { Reset(); }
void InitializeOutput(std::vector<uint8_t>* out) {
box_data_ = out;
AddMoreOutput();
}
bool AddMoreOutput() {
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<uint8_t>* 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;
}
};
} // namespace
bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
const JXLDecompressParams& dparams, size_t* decoded_bytes,
PackedPixelFile* ppf, std::vector<uint8_t>* jpeg_bytes) {
auto decoder = JxlDecoderMake(/*memory_manager=*/nullptr);
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;
std::vector<JxlPixelFormat> accepted_formats = dparams.accepted_formats;
if (accepted_formats.empty()) {
for (const uint32_t num_channels : {1, 2, 3, 4}) {
accepted_formats.push_back(
{num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0});
}
}
JxlColorEncoding color_encoding;
size_t num_color_channels = 0;
if (!dparams.color_space.empty()) {
if (!jxl::ParseDescription(dparams.color_space, &color_encoding)) {
fprintf(stderr, "Failed to parse color space %s.\n",
dparams.color_space.c_str());
return false;
}
num_color_channels =
color_encoding.color_space == JXL_COLOR_SPACE_GRAY ? 1 : 3;
}
bool can_reconstruct_jpeg = false;
std::vector<uint8_t> jpeg_data_chunk;
if (jpeg_bytes != nullptr) {
jpeg_data_chunk.resize(16384);
jpeg_bytes->resize(0);
}
int events = (JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE);
bool max_passes_defined =
(dparams.max_passes < std::numeric_limits<uint32_t>::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 (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec, events)) {
fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
return false;
}
if (jpeg_bytes == nullptr) {
if (JXL_DEC_SUCCESS !=
JxlDecoderSetRenderSpotcolors(dec, dparams.render_spotcolors)) {
fprintf(stderr, "JxlDecoderSetRenderSpotColors failed\n");
return false;
}
if (JXL_DEC_SUCCESS !=
JxlDecoderSetKeepOrientation(dec, dparams.keep_orientation)) {
fprintf(stderr, "JxlDecoderSetKeepOrientation failed\n");
return false;
}
if (JXL_DEC_SUCCESS !=
JxlDecoderSetUnpremultiplyAlpha(dec, dparams.unpremultiply_alpha)) {
fprintf(stderr, "JxlDecoderSetUnpremultiplyAlpha failed\n");
return false;
}
if (dparams.display_nits > 0 &&
JXL_DEC_SUCCESS !=
JxlDecoderSetDesiredIntensityTarget(dec, dparams.display_nits)) {
fprintf(stderr, "Decoder failed to set desired intensity target\n");
return false;
}
if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec, JXL_TRUE)) {
fprintf(stderr, "JxlDecoderSetDecompressBoxes failed\n");
return false;
}
}
if (JXL_DEC_SUCCESS != JxlDecoderSetInput(dec, bytes, bytes_size)) {
fprintf(stderr, "Decoder failed to set input\n");
return false;
}
uint32_t progression_index = 0;
bool codestream_done = false;
BoxProcessor boxes(dec);
for (;;) {
JxlDecoderStatus status = JxlDecoderProcessInput(dec);
if (status == JXL_DEC_ERROR) {
fprintf(stderr, "Failed to decode image\n");
return false;
} else if (status == JXL_DEC_NEED_MORE_INPUT) {
if (codestream_done) {
break;
}
if (dparams.allow_partial_input) {
if (JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec)) {
fprintf(stderr,
"Input file is truncated and there is no preview "
"available yet.\n");
return false;
}
break;
}
fprintf(stderr,
"Input file is truncated and allow_partial_input was disabled.");
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<uint8_t>* 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, "xml ", 4) == 0) {
box_data = &ppf->metadata.xmp;
}
if (box_data) {
boxes.InitializeOutput(box_data);
}
} else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
boxes.AddMoreOutput();
} 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) {
// 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 (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;
}
// Select format according to accepted formats.
if (!jxl::extras::SelectFormat(accepted_formats, ppf->info, &format)) {
fprintf(stderr, "SelectFormat failed\n");
return false;
}
bool have_alpha = (format.num_channels == 2 || format.num_channels == 4);
if (!have_alpha) {
// Mark in the basic info that alpha channel was dropped.
ppf->info.alpha_bits = 0;
} else if (dparams.unpremultiply_alpha) {
// Mark in the basic info that alpha was unpremultiplied.
ppf->info.alpha_premultiplied = false;
}
bool alpha_found = false;
for (uint32_t i = 0; i < ppf->info.num_extra_channels; ++i) {
JxlExtraChannelInfo eci;
if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec, i, &eci)) {
fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n");
return false;
}
if (eci.type == JXL_CHANNEL_ALPHA && have_alpha && !alpha_found) {
// Skip the first alpha channels because it is already present in the
// interleaved image.
alpha_found = true;
continue;
}
std::string name(eci.name_length + 1, 0);
if (JXL_DEC_SUCCESS !=
JxlDecoderGetExtraChannelName(dec, i, &name[0], name.size())) {
fprintf(stderr, "JxlDecoderGetExtraChannelName failed\n");
return false;
}
name.resize(eci.name_length);
ppf->extra_channels_info.push_back({eci, i, name});
}
} else if (status == JXL_DEC_COLOR_ENCODING) {
if (!dparams.color_space.empty()) {
if (ppf->info.uses_original_profile) {
fprintf(stderr,
"Warning: --color_space ignored because the image is "
"not XYB encoded.\n");
} else {
if (JXL_DEC_SUCCESS !=
JxlDecoderSetPreferredColorProfile(dec, &color_encoding)) {
fprintf(stderr, "Failed to set color space.\n");
return false;
}
}
}
size_t icc_size = 0;
JxlColorProfileTarget target = JXL_COLOR_PROFILE_TARGET_DATA;
if (JXL_DEC_SUCCESS !=
JxlDecoderGetICCProfileSize(dec, nullptr, target, &icc_size)) {
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
}
if (icc_size != 0) {
ppf->icc.resize(icc_size);
if (JXL_DEC_SUCCESS !=
JxlDecoderGetColorAsICCProfile(dec, nullptr, target,
ppf->icc.data(), icc_size)) {
fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
return false;
}
}
if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsEncodedProfile(
dec, nullptr, target, &ppf->color_encoding)) {
ppf->color_encoding.color_space = JXL_COLOR_SPACE_UNKNOWN;
}
icc_size = 0;
target = JXL_COLOR_PROFILE_TARGET_ORIGINAL;
if (JXL_DEC_SUCCESS !=
JxlDecoderGetICCProfileSize(dec, nullptr, target, &icc_size)) {
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
}
if (icc_size != 0) {
ppf->orig_icc.resize(icc_size);
if (JXL_DEC_SUCCESS !=
JxlDecoderGetColorAsICCProfile(dec, nullptr, target,
ppf->orig_icc.data(), icc_size)) {
fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
return false;
}
}
} else if (status == JXL_DEC_FRAME) {
jxl::extras::PackedFrame frame(ppf->info.xsize, ppf->info.ysize, format);
if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec, &frame.frame_info)) {
fprintf(stderr, "JxlDecoderGetFrameHeader failed\n");
return false;
}
frame.name.resize(frame.frame_info.name_length + 1, 0);
if (JXL_DEC_SUCCESS !=
JxlDecoderGetFrameName(dec, &frame.name[0], frame.name.size())) {
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;
}
ppf->preview_frame = std::unique_ptr<jxl::extras::PackedFrame>(
new jxl::extras::PackedFrame(ppf->info.preview.xsize,
ppf->info.preview.ysize, format));
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<jxl::extras::PackedPixelFile*>(opaque);
jxl::extras::PackedImage& color = ppf->frames.back().color;
uint8_t* pixels_buffer = reinterpret_cast<uint8_t*>(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;
}
}
JxlPixelFormat ec_format = format;
ec_format.num_channels = 1;
for (const auto& eci : ppf->extra_channels_info) {
frame.extra_channels.emplace_back(jxl::extras::PackedImage(
ppf->info.xsize, ppf->info.ysize, ec_format));
auto& ec = frame.extra_channels.back();
size_t buffer_size;
if (JXL_DEC_SUCCESS != JxlDecoderExtraChannelBufferSize(
dec, &ec_format, &buffer_size, eci.index)) {
fprintf(stderr, "JxlDecoderExtraChannelBufferSize failed\n");
return false;
}
if (buffer_size != ec.pixels_size) {
fprintf(stderr,
"Invalid extra channel buffer size"
" %" PRIuS " %" PRIuS "\n",
buffer_size, ec.pixels_size);
return false;
}
if (JXL_DEC_SUCCESS !=
JxlDecoderSetExtraChannelBuffer(dec, &ec_format, ec.pixels(),
buffer_size, eci.index)) {
fprintf(stderr, "JxlDecoderSetExtraChannelBuffer failed\n");
return false;
}
}
} 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<int>(status));
return false;
}
}
boxes.FinalizeOutput();
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

View file

@ -0,0 +1,66 @@
// Copyright (c) the JPEG XL Project 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 <stdint.h>
#include <limits>
#include <string>
#include <vector>
#include "jxl/parallel_runner.h"
#include "jxl/types.h"
#include "lib/extras/packed_image.h"
namespace jxl {
namespace extras {
struct JXLDecompressParams {
// If empty, little endian float formats will be accepted.
std::vector<JxlPixelFormat> accepted_formats;
// Requested output color space description.
std::string color_space;
// If set, performs tone mapping to this intensity target luminance.
float display_nits = 0.0;
// Whether spot colors are rendered on the image.
bool render_spotcolors = true;
// Whether to keep or undo the orientation given in the header.
bool keep_orientation = false;
// If runner_opaque is set, the decoder uses this parallel runner.
JxlParallelRunner runner;
void* runner_opaque = nullptr;
// 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<uint32_t>::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;
};
bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
const JXLDecompressParams& dparams, size_t* decoded_bytes,
PackedPixelFile* ppf,
std::vector<uint8_t>* jpeg_bytes = nullptr);
} // namespace extras
} // namespace jxl
#endif // LIB_EXTRAS_DEC_JXL_H_

View file

@ -42,18 +42,9 @@
#include <string>
#include <vector>
#include "jxl/encode.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/printf_macros.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/dec_external_image.h"
#include "lib/jxl/enc_color_management.h"
#include "lib/jxl/enc_image_bundle.h"
#include "lib/jxl/exif.h"
#include "lib/jxl/frame_header.h"
#include "lib/jxl/headers.h"
#include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h"
#include "png.h" /* original (unpatched) libpng is ok */
namespace jxl {
@ -62,14 +53,16 @@ namespace extras {
namespace {
static void PngWrite(png_structp png_ptr, png_bytep data, png_size_t length) {
PaddedBytes* bytes = static_cast<PaddedBytes*>(png_get_io_ptr(png_ptr));
bytes->append(data, data + length);
std::vector<uint8_t>* bytes =
static_cast<std::vector<uint8_t>*>(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 Blobs& blobs, std::vector<std::string>* strings) {
static Status Encode(const PackedMetadata& blobs,
std::vector<std::string>* 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
@ -122,26 +115,136 @@ class BlobsWriterPNG {
}
};
} // namespace
void MaybeAddCICP(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<png_byte>(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<png_byte>(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_PLTE;
memcpy(cicp_chunk.name, "cICP", 5);
png_set_keep_unknown_chunks(png_ptr, 3,
reinterpret_cast<const png_byte*>("cICP"), 1);
png_set_unknown_chunks(png_ptr, info_ptr, &cicp_chunk, 1);
}
Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
size_t bits_per_sample, ThreadPool* pool,
PaddedBytes* bytes) {
if (bits_per_sample > 8) {
bits_per_sample = 16;
} else if (bits_per_sample < 8) {
// PNG can also do 4, 2, and 1 bits per sample, but it isn't implemented
bits_per_sample = 8;
Status EncodePackedPixelFileToAPNG(const PackedPixelFile& ppf, ThreadPool* pool,
std::vector<uint8_t>* bytes) {
size_t xsize = ppf.info.xsize;
size_t ysize = ppf.info.ysize;
bool has_alpha = ppf.info.alpha_bits != 0;
bool is_gray = ppf.info.num_color_channels == 1;
size_t color_channels = ppf.info.num_color_channels;
size_t num_channels = color_channels + (has_alpha ? 1 : 0);
size_t num_samples = num_channels * xsize * ysize;
if (xsize == 0 || ysize == 0 || ppf.frames.empty()) {
return JXL_FAILURE("Empty image");
}
if (has_alpha && ppf.info.alpha_bits != ppf.info.bits_per_sample) {
return JXL_FAILURE("Alpha bit depth does not match image bit depth");
}
if (color_channels != 1 && color_channels != 3) {
return JXL_FAILURE("Invalid number of color channels");
}
if (!ppf.info.have_animation && ppf.frames.size() != 1) {
return JXL_FAILURE("Invalid number of frames");
}
if (ppf.info.orientation != JXL_ORIENT_IDENTITY) {
return JXL_FAILURE("Orientation must be identity");
}
size_t count = 0;
bool have_anim = io->metadata.m.have_animation;
size_t anim_chunks = 0;
int W = 0, H = 0;
for (size_t i = 0; i < io->frames.size(); i++) {
auto& frame = io->frames[i];
if (!have_anim && i + 1 < io->frames.size()) continue;
for (const auto& frame : ppf.frames) {
const PackedImage& color = frame.color;
const JxlPixelFormat format = color.format;
const uint8_t* in = reinterpret_cast<const uint8_t*>(color.pixels());
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;
if (in == nullptr || color.pixels_size < bytes_per_sample * num_samples) {
return JXL_FAILURE("Invalid frame");
}
if (color.xsize != xsize || color.ysize != ysize ||
format.num_channels != num_channels) {
return JXL_FAILURE("Frame size does not match image size");
}
if (ppf.info.bits_per_sample > data_bits_per_sample) {
return JXL_FAILURE("Bit depth does not fit pixel data type");
}
if (color.flipped_y) {
return JXL_FAILURE("Flipped y channel not supported");
}
std::vector<uint8_t> out(out_size);
if (format.data_type == JXL_TYPE_UINT8) {
if (ppf.info.bits_per_sample < 8) {
float mul = 255.0 / ((1u << ppf.info.bits_per_sample) - 1);
for (size_t i = 0; i < num_samples; ++i) {
out[i] = static_cast<uint8_t>(in[i] * mul + 0.5);
}
} else {
memcpy(&out[0], in, out_size);
}
} else if (format.data_type == JXL_TYPE_UINT16) {
if (ppf.info.bits_per_sample < 16 ||
format.endianness != JXL_BIG_ENDIAN) {
float mul = 65535.0 / ((1u << ppf.info.bits_per_sample) - 1);
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<uint32_t>(val * mul + 0.5), p_out);
}
} else {
memcpy(&out[0], in, out_size);
}
} else if (format.data_type == JXL_TYPE_FLOAT) {
float mul = 65535.0;
const uint8_t* p_in = in;
uint8_t* p_out = out.data();
for (size_t i = 0; i < num_samples; ++i, p_in += 4, p_out += 2) {
uint32_t val = (format.endianness == JXL_BIG_ENDIAN ? LoadBE32(p_in)
: LoadLE32(p_in));
float fval;
memcpy(&fval, &val, 4);
StoreBE16(static_cast<uint32_t>(fval * mul + 0.5), p_out);
}
} else {
return JXL_FAILURE("Unsupported pixel data type");
}
png_structp png_ptr;
png_infop info_ptr;
@ -155,46 +258,24 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
png_set_write_fn(png_ptr, bytes, PngWrite, NULL);
png_set_flush(png_ptr, 0);
ImageBundle ib = frame.Copy();
const size_t alpha_bits = ib.HasAlpha() ? bits_per_sample : 0;
ImageMetadata metadata = io->metadata.m;
ImageBundle store(&metadata);
const ImageBundle* transformed;
JXL_RETURN_IF_ERROR(TransformIfNeeded(ib, c_desired, GetJxlCms(), pool,
&store, &transformed));
size_t stride = ib.oriented_xsize() *
DivCeil(c_desired.Channels() * bits_per_sample + alpha_bits,
kBitsPerByte);
std::vector<uint8_t> raw_bytes(stride * ib.oriented_ysize());
JXL_RETURN_IF_ERROR(ConvertToExternal(
*transformed, bits_per_sample, /*float_out=*/false,
c_desired.Channels() + (ib.HasAlpha() ? 1 : 0), JXL_BIG_ENDIAN, stride,
pool, raw_bytes.data(), raw_bytes.size(),
/*out_callback=*/{}, metadata.GetOrientation()));
int width = xsize;
int height = ysize;
int width = ib.oriented_xsize();
int height = ib.oriented_ysize();
png_byte color_type =
(c_desired.Channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_GRAY);
if (ib.HasAlpha()) color_type |= PNG_COLOR_MASK_ALPHA;
png_byte bit_depth = bits_per_sample;
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) {
W = width;
H = height;
// TODO(jon): instead of always setting an iCCP, could try to avoid that
// have to avoid warnings on the ICC profile becoming fatal
png_set_benign_errors(png_ptr, 1);
png_set_iCCP(png_ptr, info_ptr, "1", 0, c_desired.ICC().data(),
c_desired.ICC().size());
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());
}
std::vector<std::string> textstrings;
JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(io->blobs, &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<png_charp>(textstrings[kk].c_str());
@ -211,27 +292,24 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
bytes->resize(pos);
}
if (have_anim) {
if (ppf.info.have_animation) {
if (count == 0) {
png_byte adata[8];
png_save_uint_32(adata, io->frames.size());
png_save_uint_32(adata + 4, io->metadata.m.animation.num_loops);
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];
JXL_ASSERT(W == width);
JXL_ASSERT(H == height);
// 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.duration * io->metadata.m.animation.tps_denominator);
png_save_uint_16(fdata + 22, io->metadata.m.animation.tps_numerator);
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";
@ -240,7 +318,7 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
std::vector<uint8_t*> rows(height);
for (int y = 0; y < height; ++y) {
rows[y] = raw_bytes.data() + y * stride;
rows[y] = out.data() + y * out_stride;
}
png_write_flush(png_ptr);
@ -268,7 +346,9 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
}
count++;
if (count == io->frames.size() || !have_anim) png_write_end(png_ptr, NULL);
if (count == ppf.frames.size() || !ppf.info.have_animation) {
png_write_end(png_ptr, NULL);
}
png_destroy_write_struct(&png_ptr, &info_ptr);
}
@ -276,5 +356,32 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
return true;
}
class APNGEncoder : public Encoder {
public:
std::vector<JxlPixelFormat> AcceptedFormats() const override {
std::vector<JxlPixelFormat> formats;
for (const uint32_t num_channels : {1, 2, 3, 4}) {
for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) {
formats.push_back(JxlPixelFormat{num_channels, data_type,
JXL_BIG_ENDIAN, /*align=*/0});
}
}
return formats;
}
Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
ThreadPool* pool) const override {
encoded_image->icc.clear();
encoded_image->bitstreams.resize(1);
return EncodePackedPixelFileToAPNG(ppf, pool,
&encoded_image->bitstreams.front());
}
};
} // namespace
std::unique_ptr<Encoder> GetAPNGEncoder() {
return jxl::make_unique<APNGEncoder>();
}
} // namespace extras
} // namespace jxl

View file

@ -8,19 +8,14 @@
// Encodes APNG images in memory.
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/codec_in_out.h"
#include <memory>
#include "lib/extras/enc/encode.h"
namespace jxl {
namespace extras {
// Encodes `io` into `bytes`.
Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
size_t bits_per_sample, ThreadPool* pool,
PaddedBytes* bytes);
std::unique_ptr<Encoder> GetAPNGEncoder();
} // namespace extras
} // namespace jxl

View file

@ -0,0 +1,96 @@
// Copyright (c) the JPEG XL Project 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 <locale>
#if JPEGXL_ENABLE_APNG
#include "lib/extras/enc/apng.h"
#endif
#if JPEGXL_ENABLE_EXR
#include "lib/extras/enc/exr.h"
#endif
#if JPEGXL_ENABLE_JPEG
#include "lib/extras/enc/jpg.h"
#endif
#include "lib/extras/enc/npy.h"
#include "lib/extras/enc/pgx.h"
#include "lib/extras/enc/pnm.h"
#include "lib/jxl/base/printf_macros.h"
namespace jxl {
namespace extras {
Status SelectFormat(const std::vector<JxlPixelFormat>& accepted_formats,
const JxlBasicInfo& basic_info, JxlPixelFormat* format) {
const size_t original_bit_depth = basic_info.bits_per_sample;
size_t current_bit_depth = 0;
size_t num_alpha_channels = (basic_info.alpha_bits != 0 ? 1 : 0);
size_t num_channels = basic_info.num_color_channels + num_alpha_channels;
for (;;) {
for (const JxlPixelFormat& candidate : accepted_formats) {
if (candidate.num_channels != num_channels) continue;
const size_t candidate_bit_depth =
PackedImage::BitsPerChannel(candidate.data_type);
if (
// Candidate bit depth is less than what we have and still enough
(original_bit_depth <= candidate_bit_depth &&
candidate_bit_depth < current_bit_depth) ||
// Or larger than the too-small bit depth we currently have
(current_bit_depth < candidate_bit_depth &&
current_bit_depth < original_bit_depth)) {
*format = candidate;
current_bit_depth = candidate_bit_depth;
}
}
if (current_bit_depth == 0) {
if (num_channels > basic_info.num_color_channels) {
// Try dropping the alpha channel.
--num_channels;
continue;
}
return JXL_FAILURE("no appropriate format found");
}
break;
}
if (current_bit_depth < original_bit_depth) {
JXL_WARNING("encoding %" PRIuS "-bit original to %" PRIuS " bits",
original_bit_depth, current_bit_depth);
}
return true;
}
std::unique_ptr<Encoder> Encoder::FromExtension(std::string extension) {
std::transform(
extension.begin(), extension.end(), extension.begin(),
[](char c) { return std::tolower(c, std::locale::classic()); });
#if JPEGXL_ENABLE_APNG
if (extension == ".png" || extension == ".apng") return GetAPNGEncoder();
#endif
#if JPEGXL_ENABLE_JPEG
if (extension == ".jpg") return GetJPEGEncoder();
if (extension == ".jpeg") return GetJPEGEncoder();
#endif
if (extension == ".npy") return GetNumPyEncoder();
if (extension == ".pgx") return GetPGXEncoder();
if (extension == ".pam") return GetPAMEncoder();
if (extension == ".pgm") return GetPGMEncoder();
if (extension == ".ppm") return GetPPMEncoder();
if (extension == ".pfm") return GetPFMEncoder();
#if JPEGXL_ENABLE_EXR
if (extension == ".exr") return GetEXREncoder();
#endif
return nullptr;
}
} // namespace extras
} // namespace jxl

View file

@ -0,0 +1,72 @@
// Copyright (c) the JPEG XL Project 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 <string>
#include <unordered_map>
#include "lib/extras/dec/decode.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 sequential bitstreams.
std::vector<std::vector<uint8_t>> bitstreams;
// For each extra channel one or more sequential bitstreams.
std::vector<std::vector<std::vector<uint8_t>>> extra_channel_bitstreams;
std::vector<uint8_t> 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<uint8_t> icc;
// Additional output for conformance testing, only filled in by NumPyEncoder.
std::vector<uint8_t> metadata;
};
class Encoder {
public:
static std::unique_ptr<Encoder> FromExtension(std::string extension);
virtual ~Encoder() = default;
virtual std::vector<JxlPixelFormat> AcceptedFormats() const = 0;
// Any existing data in encoded_image is discarded.
virtual Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
ThreadPool* pool = nullptr) const = 0;
void SetOption(std::string name, std::string value) {
options_[std::move(name)] = std::move(value);
}
protected:
const std::unordered_map<std::string, std::string>& options() const {
return options_;
}
private:
std::unordered_map<std::string, std::string> options_;
};
// TODO(sboukortt): consider exposing this as part of the C API.
Status SelectFormat(const std::vector<JxlPixelFormat>& accepted_formats,
const JxlBasicInfo& basic_info, JxlPixelFormat* format);
} // namespace extras
} // namespace jxl
#endif // LIB_EXTRAS_ENC_ENCODE_H_

View file

@ -12,6 +12,7 @@
#include <vector>
#include "lib/extras/packed_image_convert.h"
#include "lib/jxl/alpha.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/color_management.h"
@ -47,7 +48,7 @@ size_t GetNumThreads(ThreadPool* pool) {
class InMemoryOStream : public OpenEXR::OStream {
public:
// `bytes` must outlive the InMemoryOStream.
explicit InMemoryOStream(PaddedBytes* const bytes)
explicit InMemoryOStream(std::vector<uint8_t>* const bytes)
: OStream(/*fileName=*/""), bytes_(*bytes) {}
void write(const char c[], const int n) override {
@ -67,14 +68,12 @@ class InMemoryOStream : public OpenEXR::OStream {
}
private:
PaddedBytes& bytes_;
std::vector<uint8_t>& bytes_;
size_t pos_ = 0;
};
} // namespace
Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
ThreadPool* pool, PaddedBytes* bytes) {
Status EncodeImageEXR(const ImageBundle& ib, const ColorEncoding& c_desired,
ThreadPool* pool, std::vector<uint8_t>* bytes) {
// As in `DecodeImageEXR`, `pool` is only used for pixel conversion, not for
// actual OpenEXR I/O.
OpenEXR::setGlobalThreadCount(GetNumThreads(pool));
@ -82,16 +81,16 @@ Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
ColorEncoding c_linear = c_desired;
c_linear.tf.SetTransferFunction(TransferFunction::kLinear);
JXL_RETURN_IF_ERROR(c_linear.CreateICC());
ImageMetadata metadata = io->metadata.m;
ImageMetadata metadata = *ib.metadata();
ImageBundle store(&metadata);
const ImageBundle* linear;
JXL_RETURN_IF_ERROR(TransformIfNeeded(io->Main(), c_linear, GetJxlCms(), pool,
&store, &linear));
JXL_RETURN_IF_ERROR(
TransformIfNeeded(ib, c_linear, GetJxlCms(), pool, &store, &linear));
const bool has_alpha = io->Main().HasAlpha();
const bool alpha_is_premultiplied = io->Main().AlphaIsPremultiplied();
const bool has_alpha = ib.HasAlpha();
const bool alpha_is_premultiplied = ib.AlphaIsPremultiplied();
OpenEXR::Header header(io->xsize(), io->ysize());
OpenEXR::Header header(ib.xsize(), ib.ysize());
const PrimariesCIExy& primaries = c_linear.HasPrimaries()
? c_linear.GetPrimaries()
: ColorEncoding::SRGB().GetPrimaries();
@ -102,7 +101,7 @@ Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
chromaticities.white =
Imath::V2f(c_linear.GetWhitePoint().x, c_linear.GetWhitePoint().y);
OpenEXR::addChromaticities(header, chromaticities);
OpenEXR::addWhiteLuminance(header, io->metadata.m.IntensityTarget());
OpenEXR::addWhiteLuminance(header, ib.metadata()->IntensityTarget());
// Ensure that the destructor of RgbaOutputFile has run before we look at the
// size of `bytes`.
@ -112,15 +111,14 @@ Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
os, header, has_alpha ? OpenEXR::WRITE_RGBA : OpenEXR::WRITE_RGB);
// How many rows to write at once. Again, the OpenEXR documentation
// recommends writing the whole image in one call.
const int y_chunk_size = io->ysize();
std::vector<OpenEXR::Rgba> output_rows(io->xsize() * y_chunk_size);
const int y_chunk_size = ib.ysize();
std::vector<OpenEXR::Rgba> output_rows(ib.xsize() * y_chunk_size);
for (size_t start_y = 0; start_y < io->ysize(); start_y += y_chunk_size) {
for (size_t start_y = 0; start_y < ib.ysize(); start_y += y_chunk_size) {
// Inclusive.
const size_t end_y =
std::min(start_y + y_chunk_size - 1, io->ysize() - 1);
output.setFrameBuffer(output_rows.data() - start_y * io->xsize(),
/*xStride=*/1, /*yStride=*/io->xsize());
const size_t end_y = std::min(start_y + y_chunk_size - 1, ib.ysize() - 1);
output.setFrameBuffer(output_rows.data() - start_y * ib.xsize(),
/*xStride=*/1, /*yStride=*/ib.xsize());
JXL_RETURN_IF_ERROR(RunOnPool(
pool, start_y, end_y + 1, ThreadPool::NoInit,
[&](const uint32_t y, size_t /* thread */) {
@ -130,18 +128,18 @@ Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
linear->color().ConstPlaneRow(2, y),
};
OpenEXR::Rgba* const JXL_RESTRICT row_data =
&output_rows[(y - start_y) * io->xsize()];
&output_rows[(y - start_y) * ib.xsize()];
if (has_alpha) {
const float* const JXL_RESTRICT alpha_row =
io->Main().alpha().ConstRow(y);
ib.alpha().ConstRow(y);
if (alpha_is_premultiplied) {
for (size_t x = 0; x < io->xsize(); ++x) {
for (size_t x = 0; x < ib.xsize(); ++x) {
row_data[x] =
OpenEXR::Rgba(input_rows[0][x], input_rows[1][x],
input_rows[2][x], alpha_row[x]);
}
} else {
for (size_t x = 0; x < io->xsize(); ++x) {
for (size_t x = 0; x < ib.xsize(); ++x) {
row_data[x] = OpenEXR::Rgba(alpha_row[x] * input_rows[0][x],
alpha_row[x] * input_rows[1][x],
alpha_row[x] * input_rows[2][x],
@ -149,7 +147,7 @@ Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
}
}
} else {
for (size_t x = 0; x < io->xsize(); ++x) {
for (size_t x = 0; x < ib.xsize(); ++x) {
row_data[x] = OpenEXR::Rgba(input_rows[0][x], input_rows[1][x],
input_rows[2][x], 1.f);
}
@ -163,5 +161,47 @@ Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
return true;
}
class EXREncoder : public Encoder {
std::vector<JxlPixelFormat> AcceptedFormats() const override {
std::vector<JxlPixelFormat> formats;
for (const uint32_t num_channels : {1, 2, 3, 4}) {
for (const JxlDataType data_type : {JXL_TYPE_FLOAT, JXL_TYPE_FLOAT16}) {
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 = nullptr) const override {
encoded_image->icc.clear();
CodecInOut io;
JXL_RETURN_IF_ERROR(ConvertPackedPixelFileToCodecInOut(ppf, pool, &io));
encoded_image->bitstreams.clear();
encoded_image->bitstreams.reserve(io.frames.size());
for (const ImageBundle& ib : io.frames) {
encoded_image->bitstreams.emplace_back();
JXL_RETURN_IF_ERROR(EncodeImageEXR(ib, io.metadata.m.color_encoding, pool,
&encoded_image->bitstreams.back()));
}
return true;
}
};
} // namespace
std::unique_ptr<Encoder> GetEXREncoder() {
return jxl::make_unique<EXREncoder>();
}
Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
ThreadPool* pool, std::vector<uint8_t>* bytes) {
return EncodeImageEXR(io->Main(), c_desired, pool, bytes);
}
} // namespace extras
} // namespace jxl

View file

@ -8,6 +8,7 @@
// Encodes OpenEXR images in memory.
#include "lib/extras/enc/encode.h"
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/padded_bytes.h"
@ -19,10 +20,12 @@
namespace jxl {
namespace extras {
std::unique_ptr<Encoder> GetEXREncoder();
// Transforms from io->c_current to `c_desired` (with the transfer function set
// to linear as that is the OpenEXR convention) and encodes into `bytes`.
Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
ThreadPool* pool, PaddedBytes* bytes);
ThreadPool* pool, std::vector<uint8_t>* bytes);
} // namespace extras
} // namespace jxl

View file

@ -12,9 +12,11 @@
#include <algorithm>
#include <iterator>
#include <numeric>
#include <sstream>
#include <utility>
#include <vector>
#include "lib/extras/packed_image_convert.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/color_encoding_internal.h"
@ -93,12 +95,10 @@ Status SetChromaSubsampling(const YCbCrChromaSubsampling& chroma_subsampling,
return true;
}
} // namespace
Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
Status EncodeWithLibJpeg(const ImageBundle& ib, std::vector<uint8_t> exif,
size_t quality,
const YCbCrChromaSubsampling& chroma_subsampling,
PaddedBytes* bytes) {
std::vector<uint8_t>* bytes) {
jpeg_compress_struct cinfo;
// cinfo is initialized by libjpeg, which we are not instrumenting with
// msan.
@ -109,9 +109,9 @@ Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
unsigned char* buffer = nullptr;
unsigned long size = 0;
jpeg_mem_dest(&cinfo, &buffer, &size);
cinfo.image_width = ib->oriented_xsize();
cinfo.image_height = ib->oriented_ysize();
if (ib->IsGray()) {
cinfo.image_width = ib.oriented_xsize();
cinfo.image_height = ib.oriented_ysize();
if (ib.IsGray()) {
cinfo.input_components = 1;
cinfo.in_color_space = JCS_GRAYSCALE;
} else {
@ -125,11 +125,10 @@ Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
}
jpeg_set_quality(&cinfo, quality, TRUE);
jpeg_start_compress(&cinfo, TRUE);
if (!ib->IsSRGB()) {
WriteICCProfile(&cinfo, ib->c_current().ICC());
if (!ib.IsSRGB()) {
WriteICCProfile(&cinfo, ib.c_current().ICC());
}
if (!io->blobs.exif.empty()) {
std::vector<uint8_t> exif = io->blobs.exif;
if (!exif.empty()) {
ResetExifOrientation(exif);
WriteExif(&cinfo, exif);
}
@ -137,14 +136,14 @@ Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
return JXL_FAILURE("invalid numbers of components");
size_t stride =
ib->oriented_xsize() * cinfo.input_components * sizeof(JSAMPLE);
PaddedBytes raw_bytes(stride * ib->oriented_ysize());
ib.oriented_xsize() * cinfo.input_components * sizeof(JSAMPLE);
std::vector<uint8_t> raw_bytes(stride * ib.oriented_ysize());
JXL_RETURN_IF_ERROR(ConvertToExternal(
*ib, BITS_IN_JSAMPLE, /*float_out=*/false, cinfo.input_components,
ib, BITS_IN_JSAMPLE, /*float_out=*/false, cinfo.input_components,
JXL_BIG_ENDIAN, stride, nullptr, raw_bytes.data(), raw_bytes.size(),
/*out_callback=*/{}, ib->metadata()->GetOrientation()));
/*out_callback=*/{}, ib.metadata()->GetOrientation()));
for (size_t y = 0; y < ib->oriented_ysize(); ++y) {
for (size_t y = 0; y < ib.oriented_ysize(); ++y) {
JSAMPROW row[] = {raw_bytes.data() + y * stride};
jpeg_write_scanlines(&cinfo, row, 1);
@ -160,19 +159,18 @@ Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
return true;
}
Status EncodeWithSJpeg(const ImageBundle* ib, const CodecInOut* io,
Status EncodeWithSJpeg(const ImageBundle& ib, std::vector<uint8_t> exif,
size_t quality,
const YCbCrChromaSubsampling& chroma_subsampling,
PaddedBytes* bytes) {
std::vector<uint8_t>* bytes) {
#if !JPEGXL_ENABLE_SJPEG
return JXL_FAILURE("JPEG XL was built without sjpeg support");
#else
sjpeg::EncoderParam param(quality);
if (!ib->IsSRGB()) {
param.iccp.assign(ib->metadata()->color_encoding.ICC().begin(),
ib->metadata()->color_encoding.ICC().end());
if (!ib.IsSRGB()) {
param.iccp.assign(ib.metadata()->color_encoding.ICC().begin(),
ib.metadata()->color_encoding.ICC().end());
}
std::vector<uint8_t> exif = io->blobs.exif;
if (!exif.empty()) {
ResetExifOrientation(exif);
param.exif.assign(exif.begin(), exif.end());
@ -184,16 +182,16 @@ Status EncodeWithSJpeg(const ImageBundle* ib, const CodecInOut* io,
} else {
return JXL_FAILURE("sjpeg does not support this chroma subsampling mode");
}
size_t stride = ib->oriented_xsize() * 3;
PaddedBytes rgb(ib->xsize() * ib->ysize() * 3);
size_t stride = ib.oriented_xsize() * 3;
std::vector<uint8_t> rgb(ib.xsize() * ib.ysize() * 3);
JXL_RETURN_IF_ERROR(
ConvertToExternal(*ib, 8, /*float_out=*/false, 3, JXL_BIG_ENDIAN, stride,
ConvertToExternal(ib, 8, /*float_out=*/false, 3, JXL_BIG_ENDIAN, stride,
nullptr, rgb.data(), rgb.size(),
/*out_callback=*/{}, ib->metadata()->GetOrientation()));
/*out_callback=*/{}, ib.metadata()->GetOrientation()));
std::string output;
JXL_RETURN_IF_ERROR(sjpeg::Encode(rgb.data(), ib->oriented_xsize(),
ib->oriented_ysize(), stride, param,
JXL_RETURN_IF_ERROR(sjpeg::Encode(rgb.data(), ib.oriented_xsize(),
ib.oriented_ysize(), stride, param,
&output));
bytes->assign(
reinterpret_cast<const uint8_t*>(output.data()),
@ -202,31 +200,31 @@ Status EncodeWithSJpeg(const ImageBundle* ib, const CodecInOut* io,
#endif
}
Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
Status EncodeImageJPG(const ImageBundle& ib, std::vector<uint8_t> exif,
JpegEncoder encoder, size_t quality,
YCbCrChromaSubsampling chroma_subsampling,
ThreadPool* pool, PaddedBytes* bytes) {
if (io->Main().HasAlpha()) {
ThreadPool* pool, std::vector<uint8_t>* bytes) {
if (ib.HasAlpha()) {
return JXL_FAILURE("alpha is not supported");
}
if (quality > 100) {
return JXL_FAILURE("please specify a 0-100 JPEG quality");
}
const ImageBundle* ib;
ImageMetadata metadata = io->metadata.m;
const ImageBundle* transformed;
ImageMetadata metadata = *ib.metadata();
ImageBundle ib_store(&metadata);
JXL_RETURN_IF_ERROR(TransformIfNeeded(io->Main(),
io->metadata.m.color_encoding,
GetJxlCms(), pool, &ib_store, &ib));
JXL_RETURN_IF_ERROR(TransformIfNeeded(
ib, metadata.color_encoding, GetJxlCms(), pool, &ib_store, &transformed));
switch (encoder) {
case JpegEncoder::kLibJpeg:
JXL_RETURN_IF_ERROR(
EncodeWithLibJpeg(ib, io, quality, chroma_subsampling, bytes));
JXL_RETURN_IF_ERROR(EncodeWithLibJpeg(ib, std::move(exif), quality,
chroma_subsampling, bytes));
break;
case JpegEncoder::kSJpeg:
JXL_RETURN_IF_ERROR(
EncodeWithSJpeg(ib, io, quality, chroma_subsampling, bytes));
JXL_RETURN_IF_ERROR(EncodeWithSJpeg(ib, std::move(exif), quality,
chroma_subsampling, bytes));
break;
default:
return JXL_FAILURE("tried to use an unknown JPEG encoder");
@ -235,5 +233,90 @@ Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
return true;
}
Status ParseChromaSubsampling(const std::string& param,
YCbCrChromaSubsampling* subsampling) {
const std::pair<const char*,
std::pair<std::array<uint8_t, 3>, std::array<uint8_t, 3>>>
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 (param == option.first) {
return subsampling->Set(option.second.first.data(),
option.second.second.data());
}
}
return false;
}
class JPEGEncoder : public Encoder {
std::vector<JxlPixelFormat> AcceptedFormats() const override {
std::vector<JxlPixelFormat> 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_UINT8,
/*endianness=*/endianness,
/*align=*/0});
}
}
return formats;
}
Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
ThreadPool* pool = nullptr) const override {
const auto& options = this->options();
int quality = 100;
auto it_quality = options.find("q");
if (it_quality != options.end()) {
std::istringstream is(it_quality->second);
JXL_RETURN_IF_ERROR(static_cast<bool>(is >> quality));
}
YCbCrChromaSubsampling chroma_subsampling;
auto it_chroma_subsampling = options.find("chroma_subsampling");
if (it_chroma_subsampling != options.end()) {
JXL_RETURN_IF_ERROR(ParseChromaSubsampling(it_chroma_subsampling->second,
&chroma_subsampling));
}
JpegEncoder jpeg_encoder = JpegEncoder::kLibJpeg;
auto it_encoder = options.find("jpeg_encoder");
if (it_encoder != options.end()) {
if (it_encoder->second == "libjpeg") {
jpeg_encoder = JpegEncoder::kLibJpeg;
} else if (it_encoder->second == "sjpeg") {
jpeg_encoder = JpegEncoder::kSJpeg;
} else {
return JXL_FAILURE("unknown jpeg encoder \"%s\"",
it_encoder->second.c_str());
}
}
CodecInOut io;
JXL_RETURN_IF_ERROR(ConvertPackedPixelFileToCodecInOut(ppf, pool, &io));
encoded_image->icc = ppf.icc;
encoded_image->bitstreams.clear();
encoded_image->bitstreams.reserve(io.frames.size());
for (const ImageBundle& ib : io.frames) {
encoded_image->bitstreams.emplace_back();
JXL_RETURN_IF_ERROR(EncodeImageJPG(ib, ppf.metadata.exif, jpeg_encoder,
quality, chroma_subsampling, pool,
&encoded_image->bitstreams.back()));
}
return true;
}
};
} // namespace
std::unique_ptr<Encoder> GetJPEGEncoder() {
return jxl::make_unique<JPEGEncoder>();
}
Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
YCbCrChromaSubsampling chroma_subsampling,
ThreadPool* pool, std::vector<uint8_t>* bytes) {
return EncodeImageJPG(io->Main(), io->blobs.exif, encoder, quality,
chroma_subsampling, pool, bytes);
}
} // namespace extras
} // namespace jxl

View file

@ -10,7 +10,7 @@
#include <stdint.h>
#include "lib/extras/codec.h"
#include "lib/extras/enc/encode.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
@ -25,10 +25,12 @@ enum class JpegEncoder {
kSJpeg,
};
std::unique_ptr<Encoder> GetJPEGEncoder();
// Encodes into `bytes`.
Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
YCbCrChromaSubsampling chroma_subsampling,
ThreadPool* pool, PaddedBytes* bytes);
ThreadPool* pool, std::vector<uint8_t>* bytes);
} // namespace extras
} // namespace jxl

View file

@ -0,0 +1,321 @@
// Copyright (c) the JPEG XL Project 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 <stdio.h>
#include <sstream>
#include <string>
#include <vector>
#include "jxl/types.h"
#include "lib/extras/packed_image.h"
namespace jxl {
namespace extras {
namespace {
// JSON value writing
class JSONField {
public:
virtual ~JSONField() = default;
virtual void Write(std::ostream& o, uint32_t indent) const = 0;
protected:
JSONField() = default;
};
class JSONValue : public JSONField {
public:
template <typename T>
explicit JSONValue(const T& value) : value_(std::to_string(value)) {}
explicit JSONValue(const std::string& value) : value_("\"" + value + "\"") {}
explicit JSONValue(bool value) : value_(value ? "true" : "false") {}
void Write(std::ostream& o, uint32_t indent) const override { o << value_; }
private:
std::string value_;
};
class JSONDict : public JSONField {
public:
JSONDict() = default;
template <typename T>
T* AddEmpty(const std::string& key) {
static_assert(std::is_convertible<T*, JSONField*>::value,
"T must be a JSONField");
T* ret = new T();
values_.emplace_back(
key, std::unique_ptr<JSONField>(static_cast<JSONField*>(ret)));
return ret;
}
template <typename T>
void Add(const std::string& key, const T& value) {
values_.emplace_back(key, std::unique_ptr<JSONField>(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& key_value : values_) {
if (!is_first) {
o << ",";
}
is_first = false;
o << std::endl << indent_str << " \"" << key_value.first << "\": ";
key_value.second->Write(o, indent + 2);
}
if (!values_.empty()) {
o << std::endl << indent_str;
}
o << "}";
}
private:
// Dictionary with order.
std::vector<std::pair<std::string, std::unique_ptr<JSONField>>> values_;
};
class JSONArray : public JSONField {
public:
JSONArray() = default;
template <typename T>
T* AddEmpty() {
static_assert(std::is_convertible<T*, JSONField*>::value,
"T must be a JSONField");
T* ret = new T();
values_.emplace_back(ret);
return ret;
}
template <typename T>
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 << std::endl << indent_str << " ";
value->Write(o, indent + 2);
}
if (!values_.empty()) {
o << std::endl << indent_str;
}
o << "]";
}
private:
std::vector<std::unique_ptr<JSONField>> values_;
};
void GenerateMetadata(const PackedPixelFile& ppf, std::vector<uint8_t>* out) {
JSONDict meta;
// Same order as in 18181-3 CD.
// Frames.
auto* meta_frames = meta.AddEmpty<JSONArray>("frames");
for (size_t i = 0; i < ppf.frames.size(); i++) {
auto* frame_i = meta_frames->AddEmpty<JSONDict>();
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<JSONDict>("preview");
// TODO(veluca): can we have duration/name/timecode here?
}
{
auto ectype = meta.AddEmpty<JSONArray>("extra_channel_type");
auto bps = meta.AddEmpty<JSONArray>("bits_per_sample");
auto ebps = meta.AddEmpty<JSONArray>("exp_bits_per_sample");
bps->Add(ppf.info.bits_per_sample);
ebps->Add(ppf.info.exponent_bits_per_sample);
for (size_t i = 0; i < ppf.extra_channels_info.size(); i++) {
switch (ppf.extra_channels_info[i].ec_info.type) {
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(ppf.extra_channels_info[i].ec_info.bits_per_sample);
ebps->Add(ppf.extra_channels_info[i].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<uint8_t>* 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<uint8_t>* out) {
const uint8_t header[] = "\x93NUMPY\x01\x00";
Append(out, header, 8);
std::stringstream ss;
ss << "{'descr': '<f4', 'fortran_order': False, 'shape': (" << num_frames
<< ", " << ysize << ", " << xsize << ", " << num_channels << "), }\n";
// 16-bit little endian header length.
uint8_t header_len[2] = {static_cast<uint8_t>(ss.str().size() % 256),
static_cast<uint8_t>(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<uint8_t>* 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<uint8_t*>(color.pixels());
JXL_ASSERT(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<uint8_t*>(ec.pixels());
JXL_ASSERT(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<uint8_t>* 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 = nullptr) const override {
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<JxlPixelFormat> AcceptedFormats() const override {
std::vector<JxlPixelFormat> formats;
for (const uint32_t num_channels : {1, 3}) {
formats.push_back(JxlPixelFormat{num_channels, JXL_TYPE_FLOAT,
JXL_LITTLE_ENDIAN, /*align=*/0});
}
return formats;
}
};
} // namespace
std::unique_ptr<Encoder> GetNumPyEncoder() {
return jxl::make_unique<NumPyEncoder>();
}
} // namespace extras
} // namespace jxl

View file

@ -0,0 +1,23 @@
// Copyright (c) the JPEG XL Project 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 <memory>
#include "lib/extras/enc/encode.h"
namespace jxl {
namespace extras {
std::unique_ptr<Encoder> GetNumPyEncoder();
} // namespace extras
} // namespace jxl
#endif // LIB_EXTRAS_ENC_NPY_H_

View file

@ -8,18 +8,10 @@
#include <stdio.h>
#include <string.h>
#include "lib/jxl/base/bits.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/file_io.h"
#include "jxl/codestream_header.h"
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/printf_macros.h"
#include "lib/jxl/color_management.h"
#include "lib/jxl/dec_external_image.h"
#include "lib/jxl/enc_color_management.h"
#include "lib/jxl/enc_external_image.h"
#include "lib/jxl/enc_image_bundle.h"
#include "lib/jxl/fields.h" // AllDefault
#include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h"
namespace jxl {
namespace extras {
@ -27,60 +19,79 @@ namespace {
constexpr size_t kMaxHeaderSize = 200;
Status EncodeHeader(const ImageBundle& ib, const size_t bits_per_sample,
char* header, int* JXL_RESTRICT chars_written) {
if (ib.HasAlpha()) return JXL_FAILURE("PGX: can't store alpha");
if (!ib.IsGray()) return JXL_FAILURE("PGX: must be grayscale");
Status EncodeHeader(const JxlBasicInfo& info, char* header,
int* chars_written) {
if (info.xsize == 0 || info.ysize == 0) {
return JXL_FAILURE("Empty image");
}
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");
}
if (info.orientation != JXL_ORIENT_IDENTITY) {
return JXL_FAILURE("Orientation must be identity");
}
// 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 (bits_per_sample != 8 && bits_per_sample != 16) {
if (info.bits_per_sample != 8 && info.bits_per_sample != 16) {
return JXL_FAILURE("PGX: bits other than 8 or 16 not yet supported");
}
// Use ML (Big Endian), LM may not be well supported by all decoders.
*chars_written = snprintf(header, kMaxHeaderSize,
"PG ML + %" PRIuS " %" PRIuS " %" PRIuS "\n",
bits_per_sample, ib.xsize(), ib.ysize());
*chars_written = snprintf(header, kMaxHeaderSize, "PG ML + %u %u %u\n",
info.bits_per_sample, info.xsize, info.ysize);
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
kMaxHeaderSize);
return true;
}
} // namespace
Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
size_t bits_per_sample, ThreadPool* pool,
PaddedBytes* bytes) {
if (!Bundle::AllDefault(io->metadata.m)) {
JXL_WARNING("PGX encoder ignoring metadata - use a different codec");
}
if (!c_desired.IsSRGB()) {
JXL_WARNING(
"PGX encoder cannot store custom ICC profile; decoder\n"
"will need hint key=color_space to get the same values");
}
ImageBundle ib = io->Main().Copy();
ImageMetadata metadata = io->metadata.m;
ImageBundle store(&metadata);
const ImageBundle* transformed;
JXL_RETURN_IF_ERROR(TransformIfNeeded(ib, c_desired, GetJxlCms(), pool,
&store, &transformed));
PaddedBytes pixels(ib.xsize() * ib.ysize() *
(bits_per_sample / kBitsPerByte));
size_t stride = ib.xsize() * (bits_per_sample / kBitsPerByte);
JXL_RETURN_IF_ERROR(
ConvertToExternal(*transformed, bits_per_sample,
/*float_out=*/false,
/*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool,
pixels.data(), pixels.size(),
/*out_callback=*/{}, metadata.GetOrientation()));
Status EncodeImagePGX(const PackedFrame& frame, const JxlBasicInfo& info,
std::vector<uint8_t>* bytes) {
char header[kMaxHeaderSize];
int header_size = 0;
JXL_RETURN_IF_ERROR(EncodeHeader(ib, bits_per_sample, header, &header_size));
JXL_RETURN_IF_ERROR(EncodeHeader(info, header, &header_size));
const PackedImage& color = frame.color;
const JxlPixelFormat format = color.format;
const uint8_t* in = reinterpret_cast<const uint8_t*>(color.pixels());
size_t data_bits_per_sample = PackedImage::BitsPerChannel(format.data_type);
size_t bytes_per_sample = data_bits_per_sample / kBitsPerByte;
size_t num_samples = info.xsize * info.ysize;
if (in == nullptr || color.pixels_size != bytes_per_sample * num_samples) {
return JXL_FAILURE("Invalid frame");
}
if (color.xsize != info.xsize || color.ysize != info.ysize ||
format.num_channels != 1) {
return JXL_FAILURE("Frame size does not match image size");
}
if (info.bits_per_sample != data_bits_per_sample) {
return JXL_FAILURE("Bit depth does not match pixel data type");
}
if (color.flipped_y) {
return JXL_FAILURE("Flipped y channel not supported");
}
std::vector<uint8_t> pixels(num_samples * bytes_per_sample);
if (format.data_type == JXL_TYPE_UINT8) {
memcpy(&pixels[0], 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[0], in, num_samples * bytes_per_sample);
}
} else {
return JXL_FAILURE("Unsupported pixel data type");
}
bytes->resize(static_cast<size_t>(header_size) + pixels.size());
memcpy(bytes->data(), header, static_cast<size_t>(header_size));
@ -89,5 +100,39 @@ Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
return true;
}
class PGXEncoder : public Encoder {
public:
std::vector<JxlPixelFormat> AcceptedFormats() const override {
std::vector<JxlPixelFormat> 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 {
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) {
encoded_image->bitstreams.emplace_back();
JXL_RETURN_IF_ERROR(
EncodeImagePGX(frame, ppf.info, &encoded_image->bitstreams.back()));
}
return true;
}
};
} // namespace
std::unique_ptr<Encoder> GetPGXEncoder() {
return jxl::make_unique<PGXEncoder>();
}
} // namespace extras
} // namespace jxl

View file

@ -11,21 +11,12 @@
#include <stddef.h>
#include <stdint.h>
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/codec_in_out.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/extras/enc/encode.h"
namespace jxl {
namespace extras {
// Transforms from io->c_current to `c_desired` and encodes into `bytes`.
Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
size_t bits_per_sample, ThreadPool* pool,
PaddedBytes* bytes);
std::unique_ptr<Encoder> GetPGXEncoder();
} // namespace extras
} // namespace jxl

View file

@ -32,15 +32,112 @@ namespace {
constexpr size_t kMaxHeaderSize = 200;
Status EncodeHeader(const PackedPixelFile& ppf, const size_t bits_per_sample,
const bool little_endian, char* header,
int* JXL_RESTRICT chars_written) {
bool is_gray = ppf.info.num_color_channels <= 2;
size_t oriented_xsize =
ppf.info.orientation <= 4 ? ppf.info.xsize : ppf.info.ysize;
size_t oriented_ysize =
ppf.info.orientation <= 4 ? ppf.info.ysize : ppf.info.xsize;
if (ppf.info.alpha_bits > 0) { // PAM
class PNMEncoder : public Encoder {
public:
Status Encode(const PackedPixelFile& ppf, EncodedImage* encoded_image,
ThreadPool* pool = nullptr) const override {
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) {
encoded_image->bitstreams.emplace_back();
JXL_RETURN_IF_ERROR(EncodeImagePNM(frame.color, ppf.info.orientation,
ppf.info.bits_per_sample,
&encoded_image->bitstreams.back()));
}
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();
for (const auto& frame : ppf.frames) {
ec_bitstreams.emplace_back();
JXL_RETURN_IF_ERROR(
EncodeImagePNM(frame.extra_channels[i], ppf.info.orientation,
ppf.info.bits_per_sample, &ec_bitstreams.back()));
}
}
return true;
}
};
class PPMEncoder : public PNMEncoder {
public:
std::vector<JxlPixelFormat> AcceptedFormats() const override {
std::vector<JxlPixelFormat> formats;
for (const uint32_t num_channels : {1, 2, 3, 4}) {
for (const JxlDataType data_type : {JXL_TYPE_UINT8, JXL_TYPE_UINT16}) {
for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
/*data_type=*/data_type,
/*endianness=*/endianness,
/*align=*/0});
}
}
}
return formats;
}
};
class PFMEncoder : public PNMEncoder {
public:
std::vector<JxlPixelFormat> AcceptedFormats() const override {
std::vector<JxlPixelFormat> formats;
for (const uint32_t num_channels : {1, 3}) {
for (const JxlDataType data_type : {JXL_TYPE_FLOAT16, JXL_TYPE_FLOAT}) {
for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
/*data_type=*/data_type,
/*endianness=*/endianness,
/*align=*/0});
}
}
}
return formats;
}
};
class PGMEncoder : public PPMEncoder {
public:
std::vector<JxlPixelFormat> AcceptedFormats() const override {
std::vector<JxlPixelFormat> formats = PPMEncoder::AcceptedFormats();
for (auto it = formats.begin(); it != formats.end();) {
if (it->num_channels > 2) {
it = formats.erase(it);
} else {
++it;
}
}
return formats;
}
};
class PAMEncoder : public PPMEncoder {
public:
std::vector<JxlPixelFormat> AcceptedFormats() const override {
std::vector<JxlPixelFormat> formats = PPMEncoder::AcceptedFormats();
for (auto it = formats.begin(); it != formats.end();) {
if (it->num_channels != 2 && it->num_channels != 4) {
it = formats.erase(it);
} else {
++it;
}
}
return formats;
}
};
Status EncodeHeader(const PackedImage& image, JxlOrientation orientation,
size_t bits_per_sample, bool little_endian, char* header,
int* chars_written) {
size_t num_channels = image.format.num_channels;
bool is_gray = num_channels <= 2;
bool has_alpha = num_channels == 2 || num_channels == 4;
size_t oriented_xsize = orientation <= 4 ? image.xsize : image.ysize;
size_t oriented_ysize = orientation <= 4 ? image.ysize : image.xsize;
if (has_alpha) { // PAM
if (bits_per_sample > 16) return JXL_FAILURE("PNM cannot have > 16 bits");
const uint32_t max_val = (1U << bits_per_sample) - 1;
*chars_written =
@ -60,8 +157,8 @@ Status EncodeHeader(const PackedPixelFile& ppf, const size_t bits_per_sample,
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
kMaxHeaderSize);
} else { // PGM/PPM
if (bits_per_sample > 16) return JXL_FAILURE("PNM cannot have > 16 bits");
const uint32_t max_val = (1U << bits_per_sample) - 1;
if (max_val >= 65536) return JXL_FAILURE("PNM cannot have > 16 bits");
const char type = is_gray ? '5' : '6';
*chars_written =
snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%u\n",
@ -96,34 +193,36 @@ void VerticallyFlipImage(float* const float_image, const size_t xsize,
} // namespace
Status EncodeImagePNM(const PackedPixelFile& ppf, size_t bits_per_sample,
ThreadPool* pool, size_t frame_index,
std::vector<uint8_t>* bytes) {
const bool floating_point = bits_per_sample > 16;
// Choose native for PFM; PGM/PPM require big-endian
const JxlEndianness endianness =
floating_point ? JXL_NATIVE_ENDIAN : JXL_BIG_ENDIAN;
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");
}
std::unique_ptr<Encoder> GetPPMEncoder() {
return jxl::make_unique<PPMEncoder>();
}
std::unique_ptr<Encoder> GetPFMEncoder() {
return jxl::make_unique<PFMEncoder>();
}
std::unique_ptr<Encoder> GetPGMEncoder() {
return jxl::make_unique<PGMEncoder>();
}
std::unique_ptr<Encoder> GetPAMEncoder() {
return jxl::make_unique<PAMEncoder>();
}
Status EncodeImagePNM(const PackedImage& image, JxlOrientation orientation,
size_t bits_per_sample, std::vector<uint8_t>* bytes) {
// Choose native for PFM; PGM/PPM require big-endian
bool is_little_endian = bits_per_sample > 16 && IsLittleEndian();
char header[kMaxHeaderSize];
int header_size = 0;
bool is_little_endian = endianness == JXL_LITTLE_ENDIAN ||
(endianness == JXL_NATIVE_ENDIAN && IsLittleEndian());
JXL_RETURN_IF_ERROR(EncodeHeader(ppf, bits_per_sample, is_little_endian,
header, &header_size));
bytes->resize(static_cast<size_t>(header_size) +
ppf.frames[frame_index].color.pixels_size);
JXL_RETURN_IF_ERROR(EncodeHeader(image, orientation, bits_per_sample,
is_little_endian, header, &header_size));
bytes->resize(static_cast<size_t>(header_size) + image.pixels_size);
memcpy(bytes->data(), header, static_cast<size_t>(header_size));
memcpy(bytes->data() + header_size, ppf.frames[frame_index].color.pixels(),
ppf.frames[frame_index].color.pixels_size);
if (floating_point) {
memcpy(bytes->data() + header_size, image.pixels(), image.pixels_size);
if (bits_per_sample > 16) {
VerticallyFlipImage(reinterpret_cast<float*>(bytes->data() + header_size),
ppf.frames[frame_index].color.xsize,
ppf.frames[frame_index].color.ysize,
ppf.info.num_color_channels);
image.xsize, image.ysize, image.format.num_channels);
}
return true;

View file

@ -11,20 +11,19 @@
// TODO(janwas): workaround for incorrect Win64 codegen (cause unknown)
#include <hwy/highway.h>
#include "lib/extras/enc/encode.h"
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/codec_in_out.h"
#include "lib/jxl/color_encoding_internal.h"
namespace jxl {
namespace extras {
// Transforms from io->c_current to `c_desired` and encodes into `bytes`.
Status EncodeImagePNM(const PackedPixelFile& ppf, size_t bits_per_sample,
ThreadPool* pool, size_t frame_index,
std::vector<uint8_t>* bytes);
std::unique_ptr<Encoder> GetPAMEncoder();
std::unique_ptr<Encoder> GetPGMEncoder();
std::unique_ptr<Encoder> GetPPMEncoder();
std::unique_ptr<Encoder> GetPFMEncoder();
Status EncodeImagePNM(const PackedImage& image, JxlOrientation orientation,
size_t bits_per_sample, std::vector<uint8_t>* bytes);
} // namespace extras
} // namespace jxl

View file

@ -22,7 +22,6 @@
#include "jxl/codestream_header.h"
#include "jxl/encode.h"
#include "jxl/types.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/common.h"
namespace jxl {
@ -77,6 +76,11 @@ class PackedImage {
JxlPixelFormat format;
size_t pixels_size;
size_t pixel_stride() const {
return (BitsPerChannel(format.data_type) * format.num_channels /
jxl::kBitsPerByte);
}
static size_t BitsPerChannel(JxlDataType data_type) {
switch (data_type) {
case JXL_TYPE_UINT8:
@ -140,20 +144,20 @@ class PackedPixelFile {
// The extra channel metadata information.
struct PackedExtraChannel {
PackedExtraChannel(const JxlExtraChannelInfo& ec_info,
const std::string& name)
: ec_info(ec_info), name(name) {}
JxlExtraChannelInfo ec_info;
size_t index;
std::string name;
};
std::vector<PackedExtraChannel> extra_channels_info;
// Color information. If the icc is empty, the JxlColorEncoding should be used
// instead.
// Color information of the decoded pixels.
// If the icc is empty, the JxlColorEncoding should be used instead.
std::vector<uint8_t> icc;
JxlColorEncoding color_encoding = {};
// The icc profile of the original image.
std::vector<uint8_t> orig_icc;
std::unique_ptr<PackedFrame> preview_frame;
std::vector<PackedFrame> frames;
PackedMetadata metadata;

View file

@ -20,6 +20,62 @@
namespace jxl {
namespace extras {
Status ConvertPackedFrameToImageBundle(const JxlBasicInfo& info,
const PackedFrame& frame,
const CodecInOut& io, ThreadPool* pool,
ImageBundle* bundle) {
JXL_ASSERT(frame.color.pixels() != nullptr);
size_t frame_bits_per_sample =
(frame.color.bitdepth_from_format
? frame.color.BitsPerChannel(frame.color.format.data_type)
: info.bits_per_sample);
JXL_ASSERT(frame_bits_per_sample != 0);
// It is ok for the frame.color.format.num_channels to not match the
// number of channels on the image.
JXL_ASSERT(1 <= frame.color.format.num_channels &&
frame.color.format.num_channels <= 4);
const Span<const uint8_t> span(
static_cast<const uint8_t*>(frame.color.pixels()),
frame.color.pixels_size);
JXL_ASSERT(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_ASSERT(io.metadata.m.color_encoding.IsGray() ==
(frame.color.format.num_channels <= 2));
const bool float_in = frame.color.format.data_type == JXL_TYPE_FLOAT16 ||
frame.color.format.data_type == JXL_TYPE_FLOAT;
JXL_RETURN_IF_ERROR(ConvertFromExternal(
span, frame.color.xsize, frame.color.ysize, io.metadata.m.color_encoding,
frame.color.format.num_channels,
/*alpha_is_premultiplied=*/info.alpha_premultiplied,
frame_bits_per_sample, frame.color.format.endianness,
/*flipped_y=*/frame.color.flipped_y, pool, bundle,
/*float_in=*/float_in, /*align=*/0));
bundle->extra_channels().resize(io.metadata.m.extra_channel_info.size());
for (size_t i = 0; i < frame.extra_channels.size(); i++) {
const auto& ppf_ec = frame.extra_channels[i];
bundle->extra_channels()[i] = ImageF(ppf_ec.xsize, ppf_ec.ysize);
JXL_CHECK(BufferToImageF(ppf_ec.format, ppf_ec.xsize, ppf_ec.ysize,
ppf_ec.pixels(), ppf_ec.pixels_size, pool,
&bundle->extra_channels()[i]));
}
return true;
}
Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
ThreadPool* pool, CodecInOut* io) {
const bool has_alpha = ppf.info.alpha_bits != 0;
@ -63,7 +119,7 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
PaddedBytes icc;
icc.append(ppf.icc);
if (!io->metadata.m.color_encoding.SetICC(std::move(icc))) {
fprintf(stderr, "Warning: error setting ICC profile, assuming SRGB");
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.IsGray() != is_gray) {
@ -105,61 +161,24 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
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.preview_frame, *io, pool, &io->preview_frame));
}
// Convert the pixels
io->dec_pixels = 0;
io->frames.clear();
for (const auto& frame : ppf.frames) {
JXL_ASSERT(frame.color.pixels() != nullptr);
size_t frame_bits_per_sample =
(frame.color.bitdepth_from_format
? frame.color.BitsPerChannel(frame.color.format.data_type)
: ppf.info.bits_per_sample);
JXL_ASSERT(frame_bits_per_sample != 0);
// It is ok for the frame.color.format.num_channels to not match the
// number of channels on the image.
JXL_ASSERT(1 <= frame.color.format.num_channels &&
frame.color.format.num_channels <= 4);
const Span<const uint8_t> span(
static_cast<const uint8_t*>(frame.color.pixels()),
frame.color.pixels_size);
Rect frame_rect = Rect(frame.frame_info.layer_info.crop_x0,
frame.frame_info.layer_info.crop_y0,
frame.frame_info.layer_info.xsize,
frame.frame_info.layer_info.ysize);
JXL_ASSERT(frame_rect.IsInside(Rect(0, 0, ppf.info.xsize, ppf.info.ysize)));
ImageBundle bundle(&io->metadata.m);
if (ppf.info.have_animation) {
bundle.duration = frame.frame_info.duration;
bundle.blend = frame.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_ASSERT(io->metadata.m.color_encoding.IsGray() ==
(frame.color.format.num_channels <= 2));
const bool float_in = frame.color.format.data_type == JXL_TYPE_FLOAT16 ||
frame.color.format.data_type == JXL_TYPE_FLOAT;
JXL_RETURN_IF_ERROR(ConvertFromExternal(
span, frame.color.xsize, frame.color.ysize,
io->metadata.m.color_encoding, frame.color.format.num_channels,
/*alpha_is_premultiplied=*/ppf.info.alpha_premultiplied,
frame_bits_per_sample, frame.color.format.endianness,
/*flipped_y=*/frame.color.flipped_y, pool, &bundle,
/*float_in=*/float_in, /*align=*/0));
bundle.extra_channels().resize(io->metadata.m.extra_channel_info.size());
for (size_t i = 0; i < frame.extra_channels.size(); i++) {
const auto& ppf_ec = frame.extra_channels[i];
bundle.extra_channels()[i] = ImageF(ppf_ec.xsize, ppf_ec.ysize);
JXL_CHECK(BufferToImageF(ppf_ec.format, ppf_ec.xsize, ppf_ec.ysize,
ppf_ec.pixels(), ppf_ec.pixels_size, pool,
&bundle.extra_channels()[i]));
}
JXL_RETURN_IF_ERROR(
ConvertPackedFrameToImageBundle(ppf.info, frame, *io, pool, &bundle));
io->frames.push_back(std::move(bundle));
io->dec_pixels += frame.color.xsize * frame.color.ysize;
}
@ -236,8 +255,7 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
// Convert the pixels
ppf->frames.clear();
for (const auto& frame : io.frames) {
size_t frame_bits_per_sample = frame.metadata()->bit_depth.bits_per_sample;
JXL_ASSERT(frame_bits_per_sample != 0);
JXL_ASSERT(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 num_channels =
@ -251,9 +269,8 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
format);
packed_frame.color.bitdepth_from_format = float_out;
const size_t bits_per_sample =
packed_frame.color.bitdepth_from_format
? packed_frame.color.BitsPerChannel(pixel_format.data_type)
: ppf->info.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

View file

@ -0,0 +1,60 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/extras/render_hdr.h"
#include "lib/extras/hlg.h"
#include "lib/extras/tone_mapping.h"
#include "lib/jxl/enc_color_management.h"
namespace jxl {
Status RenderHDR(CodecInOut* io, float display_nits, ThreadPool* pool) {
const ColorEncoding& original_color_encoding = io->metadata.m.color_encoding;
if (!(original_color_encoding.tf.IsPQ() ||
original_color_encoding.tf.IsHLG())) {
// Nothing to do.
return true;
}
if (original_color_encoding.tf.IsPQ()) {
JXL_RETURN_IF_ERROR(ToneMapTo({0, display_nits}, io, pool));
JXL_RETURN_IF_ERROR(GamutMap(io, /*preserve_saturation=*/0.1, pool));
} else {
const float intensity_target = io->metadata.m.IntensityTarget();
const float gamma_hlg_to_display = GetHlgGamma(display_nits);
// If the image is already in display space, we need to account for the
// already-applied OOTF.
const float gamma_display_to_display =
gamma_hlg_to_display / GetHlgGamma(intensity_target);
// Ensures that conversions to linear in HlgOOTF below will not themselves
// include the OOTF.
io->metadata.m.SetIntensityTarget(300);
bool need_gamut_mapping = false;
for (ImageBundle& ib : io->frames) {
const float gamma = ib.c_current().tf.IsHLG() ? gamma_hlg_to_display
: gamma_display_to_display;
if (gamma < 1) need_gamut_mapping = true;
JXL_RETURN_IF_ERROR(HlgOOTF(&ib, gamma, pool));
}
io->metadata.m.SetIntensityTarget(display_nits);
if (need_gamut_mapping) {
JXL_RETURN_IF_ERROR(GamutMap(io, /*preserve_saturation=*/0.1, pool));
}
}
ColorEncoding rec2020_pq;
rec2020_pq.SetColorSpace(ColorSpace::kRGB);
rec2020_pq.white_point = WhitePoint::kD65;
rec2020_pq.primaries = Primaries::k2100;
rec2020_pq.tf.SetTransferFunction(TransferFunction::kPQ);
JXL_RETURN_IF_ERROR(rec2020_pq.CreateICC());
io->metadata.m.color_encoding = rec2020_pq;
return io->TransformTo(rec2020_pq, GetJxlCms(), pool);
}
} // namespace jxl

View file

@ -0,0 +1,27 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#ifndef LIB_EXTRAS_RENDER_HDR_H_
#define LIB_EXTRAS_RENDER_HDR_H_
#include "lib/jxl/codec_in_out.h"
namespace jxl {
// If `io` has an original color space using PQ or HLG, this renders it
// appropriately for a display with a peak luminance of `display_nits` and
// converts the result to a Rec. 2020 / PQ image. Otherwise, leaves the image as
// is.
// PQ images are tone-mapped using the method described in Rep. ITU-R BT.2408-5
// annex 5, while HLG images are rendered using the HLG OOTF with a gamma
// appropriate for the given target luminance.
// With a sufficiently bright SDR display, converting the output of this
// function to an SDR colorspace may look decent.
Status RenderHDR(CodecInOut* io, float display_nits,
ThreadPool* pool = nullptr);
} // namespace jxl
#endif // LIB_EXTRAS_RENDER_HDR_H_

View file

@ -10,13 +10,15 @@
#include <hwy/foreach_target.h>
#include <hwy/highway.h>
#include "lib/jxl/dec_tone_mapping-inl.h"
#include "lib/jxl/enc_color_management.h"
#include "lib/jxl/transfer_functions-inl.h"
HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
static constexpr float rec2020_luminances[3] = {0.2627f, 0.6780f, 0.0593f};
Status ToneMapFrame(const std::pair<float, float> display_nits,
ImageBundle* const ib, ThreadPool* const pool) {
// Perform tone mapping as described in Report ITU-R BT.2390-8, section 5.4
@ -34,42 +36,12 @@ Status ToneMapFrame(const std::pair<float, float> display_nits,
JXL_RETURN_IF_ERROR(linear_rec2020.CreateICC());
JXL_RETURN_IF_ERROR(ib->TransformTo(linear_rec2020, GetJxlCms(), pool));
const auto eotf_inv = [&df](const V luminance) -> V {
return TF_PQ().EncodedFromDisplay(df, luminance * Set(df, 1. / 10000));
};
Rec2408ToneMapper<decltype(df)> tone_mapper(
{ib->metadata()->tone_mapping.min_nits,
ib->metadata()->IntensityTarget()},
display_nits, rec2020_luminances);
const V pq_mastering_min =
eotf_inv(Set(df, ib->metadata()->tone_mapping.min_nits));
const V pq_mastering_max =
eotf_inv(Set(df, ib->metadata()->tone_mapping.intensity_target));
const V pq_mastering_range = pq_mastering_max - pq_mastering_min;
const V inv_pq_mastering_range =
Set(df, 1) / (pq_mastering_max - pq_mastering_min);
const V min_lum = (eotf_inv(Set(df, display_nits.first)) - pq_mastering_min) *
inv_pq_mastering_range;
const V max_lum =
(eotf_inv(Set(df, display_nits.second)) - pq_mastering_min) *
inv_pq_mastering_range;
const V ks = MulAdd(Set(df, 1.5f), max_lum, Set(df, -0.5f));
const V b = min_lum;
const V inv_one_minus_ks = Set(df, 1) / Max(Set(df, 1e-6f), Set(df, 1) - ks);
const auto T = [ks, inv_one_minus_ks](const V a) {
return (a - ks) * inv_one_minus_ks;
};
const auto P = [&T, &df, ks, max_lum](const V b) {
const V t_b = T(b);
const V t_b_2 = t_b * t_b;
const V t_b_3 = t_b_2 * t_b;
return MulAdd(
MulAdd(Set(df, 2), t_b_3, MulAdd(Set(df, -3), t_b_2, Set(df, 1))), ks,
MulAdd(t_b_3 + MulAdd(Set(df, -2), t_b_2, t_b), Set(df, 1) - ks,
MulAdd(Set(df, -2), t_b_3, Set(df, 3) * t_b_2) * max_lum));
};
const V inv_max_display_nits = Set(df, 1 / display_nits.second);
JXL_RETURN_IF_ERROR(RunOnPool(
return RunOnPool(
pool, 0, ib->ysize(), ThreadPool::NoInit,
[&](const uint32_t y, size_t /* thread */) {
float* const JXL_RESTRICT row_r = ib->color()->PlaneRow(0, y);
@ -79,43 +51,13 @@ Status ToneMapFrame(const std::pair<float, float> display_nits,
V red = Load(df, row_r + x);
V green = Load(df, row_g + x);
V blue = Load(df, row_b + x);
const V luminance = Set(df, ib->metadata()->IntensityTarget()) *
(MulAdd(Set(df, 0.2627f), red,
MulAdd(Set(df, 0.6780f), green,
Set(df, 0.0593f) * blue)));
const V normalized_pq =
Min(Set(df, 1.f), (eotf_inv(luminance) - pq_mastering_min) *
inv_pq_mastering_range);
const V e2 =
IfThenElse(normalized_pq < ks, normalized_pq, P(normalized_pq));
const V one_minus_e2 = Set(df, 1) - e2;
const V one_minus_e2_2 = one_minus_e2 * one_minus_e2;
const V one_minus_e2_4 = one_minus_e2_2 * one_minus_e2_2;
const V e3 = MulAdd(b, one_minus_e2_4, e2);
const V e4 = MulAdd(e3, pq_mastering_range, pq_mastering_min);
const V new_luminance =
Min(Set(df, display_nits.second),
ZeroIfNegative(Set(df, 10000) *
TF_PQ().DisplayFromEncoded(df, e4)));
const V ratio = new_luminance / luminance;
const V normalizer =
Set(df, ib->metadata()->IntensityTarget()) * inv_max_display_nits;
for (V* const val : {&red, &green, &blue}) {
*val = IfThenElse(luminance <= Set(df, 1e-6f), new_luminance,
*val * ratio) *
normalizer;
}
tone_mapper.ToneMap(&red, &green, &blue);
Store(red, df, row_r + x);
Store(green, df, row_g + x);
Store(blue, df, row_b + x);
}
},
"ToneMap"));
return true;
"ToneMap");
}
Status GamutMapFrame(ImageBundle* const ib, float preserve_saturation,
@ -141,44 +83,8 @@ Status GamutMapFrame(ImageBundle* const ib, float preserve_saturation,
V red = Load(df, row_r + x);
V green = Load(df, row_g + x);
V blue = Load(df, row_b + x);
const V luminance =
MulAdd(Set(df, 0.2627f), red,
MulAdd(Set(df, 0.6780f), green, Set(df, 0.0593f) * blue));
// Desaturate out-of-gamut pixels. This is done by mixing each pixel
// with just enough gray of the target luminance to make all
// components non-negative.
// - For saturation preservation, if a component is still larger than
// 1 then the pixel is normalized to have a maximum component of 1.
// That will reduce its luminance.
// - For luminance preservation, getting all components below 1 is
// done by mixing in yet more gray. That will desaturate it further.
V gray_mix_saturation = Zero(df);
V gray_mix_luminance = Zero(df);
for (const V val : {red, green, blue}) {
const V inv_val_minus_gray = Set(df, 1) / (val - luminance);
gray_mix_saturation =
IfThenElse(val >= luminance, gray_mix_saturation,
Max(gray_mix_saturation, val * inv_val_minus_gray));
gray_mix_luminance =
Max(gray_mix_luminance,
IfThenElse(val <= luminance, gray_mix_saturation,
(val - Set(df, 1)) * inv_val_minus_gray));
}
const V gray_mix =
Clamp(Set(df, preserve_saturation) *
(gray_mix_saturation - gray_mix_luminance) +
gray_mix_luminance,
Zero(df), Set(df, 1));
for (V* const val : {&red, &green, &blue}) {
*val = MulAdd(gray_mix, luminance - *val, *val);
}
const V normalizer =
Set(df, 1) / Max(Set(df, 1), Max(red, Max(green, blue)));
for (V* const val : {&red, &green, &blue}) {
*val = *val * normalizer;
}
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);

View file

@ -13,8 +13,7 @@ namespace jxl {
static void BM_ToneMapping(benchmark::State& state) {
CodecInOut image;
const PaddedBytes image_bytes =
ReadTestData("third_party/imagecompression.info/flower_foveon.png");
const PaddedBytes image_bytes = ReadTestData("jxl/flower/flower.png");
JXL_CHECK(SetFromBytes(Span<const uint8_t>(image_bytes), &image));
// Convert to linear Rec. 2020 so that `ToneMapTo` doesn't have to and we

View file

@ -19,7 +19,7 @@
#define JXL_CMS_INTERFACE_H_
#include "jxl/color_encoding.h"
#include "jxl/memory_manager.h"
#include "jxl/types.h"
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {

View file

@ -18,7 +18,6 @@
#include <stddef.h>
#include <stdint.h>
#include "jxl/color_encoding.h"
#include "jxl/types.h"
#if defined(__cplusplus) || defined(c_plusplus)
@ -175,10 +174,14 @@ typedef struct {
* (linear if outputting to floating point, nonlinear with standard sRGB
* transfer function if outputting to unsigned integers) but will not convert
* it to to the original color profile. The decoder also does not convert to
* the target display color profile, but instead will always indicate which
* color profile the returned pixel data is encoded in when using @see
* JXL_COLOR_PROFILE_TARGET_DATA so that a CMS can be used to convert the
* data.
* the target display color profile. To convert the pixel data produced by
* the decoder to the original color profile, one of the JxlDecoderGetColor*
* functions needs to be called with @ref JXL_COLOR_PROFILE_TARGET_DATA to get
* the color profile of the decoder output, and then an external CMS can be
* used for conversion.
* Note that for lossy compression, this should be set to false for most use
* cases, and if needed, the image should be converted to the original color
* profile after decoding, as described above.
*/
JXL_BOOL uses_original_profile;

View file

@ -16,8 +16,6 @@
#include <stdint.h>
#include "jxl/types.h"
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
@ -90,7 +88,7 @@ typedef enum {
JXL_TRANSFER_FUNCTION_LINEAR = 8,
/** As specified in IEC 61966-2-1 sRGB */
JXL_TRANSFER_FUNCTION_SRGB = 13,
/** As specified in SMPTE ST 428-1 */
/** As specified in SMPTE ST 2084 */
JXL_TRANSFER_FUNCTION_PQ = 16,
/** As specified in SMPTE ST 428-1 */
JXL_TRANSFER_FUNCTION_DCI = 17,

File diff suppressed because it is too large Load diff

View file

@ -13,8 +13,8 @@
#ifndef JXL_ENCODE_H_
#define JXL_ENCODE_H_
#include "jxl/cms_interface.h"
#include "jxl/codestream_header.h"
#include "jxl/decode.h"
#include "jxl/jxl_export.h"
#include "jxl/memory_manager.h"
#include "jxl/parallel_runner.h"
@ -73,15 +73,60 @@ typedef enum {
/** DEPRECATED: the encoder does not return this status and there is no need
* to handle or expect it.
* Instead, JXL_ENC_ERROR is returned with error condition
* JXL_ENC_ERR_NOT_SUPPORTED.
*/
JXL_ENC_NOT_SUPPORTED = 3,
} JxlEncoderStatus;
/**
* Id of encoder options for a frame. This includes options such as the
* image quality and compression speed for this frame. This does not include
* non-frame related encoder options such as for boxes.
* 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
@ -236,9 +281,12 @@ typedef enum {
*/
JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM = 24,
/** Color space for modular encoding: -1=default, 0-35=reverse color transform
/** Reversible color transform for modular encoding: -1=default, 0-41=RCT
* index, e.g. index 0 = none, index 6 = YCoCg.
* The default behavior is to try several, depending on the speed setting.
* 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,
@ -354,6 +402,15 @@ 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 JxlEncoderError that caused the (last) JXL_ENC_ERROR to be
* returned.
*/
JXL_EXPORT JxlEncoderError JxlEncoderGetError(JxlEncoder* enc);
/**
* Encodes JPEG XL file using the available bytes. @p *avail_out indicates how
* many output bytes are available, and @p *next_out points to the input bytes.
@ -519,12 +576,12 @@ JxlEncoderAddJPEGFrame(const JxlEncoderFrameSettings* frame_settings,
* If the image has alpha, and alpha is not passed here, it will implicitly be
* set to all-opaque (an alpha value of 1.0 everywhere).
*
* The color profile of the pixels depends on the value of uses_original_profile
* in the JxlBasicInfo. If true, the pixels are assumed to be encoded in the
* original profile that is set with JxlEncoderSetColorEncoding or
* JxlEncoderSetICCProfile. If false, the pixels are assumed to be nonlinear
* sRGB for integer data types (JXL_TYPE_UINT8, JXL_TYPE_UINT16), and linear
* sRGB for floating point data types (JXL_TYPE_FLOAT16, JXL_TYPE_FLOAT).
* The pixels are assumed to be encoded in the original profile that is set with
* JxlEncoderSetColorEncoding or JxlEncoderSetICCProfile. If none of these
* functions were used, the pixels are assumed to be nonlinear sRGB for integer
* data types (JXL_TYPE_UINT8, JXL_TYPE_UINT16), and linear sRGB for floating
* point data types (JXL_TYPE_FLOAT16, JXL_TYPE_FLOAT).
*
* 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
@ -715,6 +772,7 @@ JXL_EXPORT void JxlEncoderCloseInput(JxlEncoder* enc);
* is an alternative to JxlEncoderSetICCProfile and only one of these two must
* be used. This one sets the color encoding as a @ref JxlColorEncoding, while
* the other sets it as ICC binary data.
* Must be called after JxlEncoderSetBasicInfo.
*
* @param enc encoder object.
* @param color color encoding. Object owned by the caller and its contents are
@ -730,6 +788,7 @@ JxlEncoderSetColorEncoding(JxlEncoder* enc, const JxlColorEncoding* color);
* ICC color profile. This is an alternative to JxlEncoderSetColorEncoding and
* only one of these two must be used. This one sets the color encoding as ICC
* binary data, while the other defines it as a @ref JxlColorEncoding.
* Must be called after JxlEncoderSetBasicInfo.
*
* @param enc encoder object.
* @param icc_profile bytes of the original ICC profile
@ -852,7 +911,25 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelName(JxlEncoder* enc,
*/
JXL_EXPORT JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option,
int32_t value);
int64_t value);
/**
* Sets a frame-specific option of float type to the encoder options.
* The JxlEncoderFrameSettingId argument determines which option is set.
*
* @param frame_settings set of options and metadata for this frame. Also
* includes reference to the encoder object.
* @param option ID of the option to set.
* @param value Float value to set for this option.
* @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR in
* case of an error, such as invalid or unknown option id, or invalid integer
* value for the given option. If an error is returned, the state of the
* JxlEncoderFrameSettings object is still valid and is the same as before this
* function was called.
*/
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.
@ -893,8 +970,8 @@ 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. Keeping the default value of 5 is recommended for compatibility with all
* decoders.
* 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
@ -910,16 +987,19 @@ JxlEncoderStoreJPEGMetadata(JxlEncoder* enc, JXL_BOOL store_jpeg_metadata);
* 5 limitations, allows CMYK color and up to 32 bits per color channel, but
* may be less widely supported.
*
* The default value is 5. To use level 10 features, the setting must be
* explicitly set to 10, the encoder will not automatically enable it. If
* incompatible parameters such as too high image resolution for the current
* level are set, the encoder will return an error. For internal coding tools,
* the encoder will only use those compatible with the level setting.
* 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 5 or 10.
* @param level the level value to set, must be -1, 5, or 10.
* @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR
* otherwise.
*/

View file

@ -115,6 +115,33 @@ typedef struct {
*/
typedef char JxlBoxType[4];
/** Types of progressive detail.
* Setting a progressive detail with value N implies all progressive details
* with smaller or equal value. Currently only the following level of
* progressive detail is implemented:
* - kDC (which implies kFrames)
* - kLastPasses (which implies kDC and kFrames)
* - kPasses (which implies kLastPasses, kDC and kFrames)
*/
typedef enum {
// after completed kRegularFrames
kFrames = 0,
// after completed DC (1:8)
kDC = 1,
// after completed AC passes that are the last pass for their resolution
// target.
kLastPasses = 2,
// after completed AC passes that are not the last pass for their resolution
// target.
kPasses = 3,
// during DC frame when lower resolution are completed (1:32, 1:16)
kDCProgressive = 4,
// after completed groups
kDCGroups = 5,
// after completed groups
kGroups = 6,
} JxlProgressiveDetail;
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif

View file

@ -62,8 +62,12 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/compressed_dc.cc
jxl/compressed_dc.h
jxl/convolve-inl.h
jxl/convolve.cc
jxl/convolve.h
jxl/convolve_separable5.cc
jxl/convolve_separable7.cc
jxl/convolve_slow.cc
jxl/convolve_symmetric3.cc
jxl/convolve_symmetric5.cc
jxl/dct-inl.h
jxl/dct_block-inl.h
jxl/dct_scales.cc
@ -90,9 +94,9 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/dec_modular.h
jxl/dec_noise.cc
jxl/dec_noise.h
jxl/dec_params.h
jxl/dec_patch_dictionary.cc
jxl/dec_patch_dictionary.h
jxl/dec_tone_mapping-inl.h
jxl/dec_transforms-inl.h
jxl/dec_xyb-inl.h
jxl/dec_xyb.cc
@ -198,6 +202,8 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/render_pipeline/stage_chroma_upsampling.h
jxl/render_pipeline/stage_epf.cc
jxl/render_pipeline/stage_epf.h
jxl/render_pipeline/stage_from_linear.cc
jxl/render_pipeline/stage_from_linear.h
jxl/render_pipeline/stage_gaborish.cc
jxl/render_pipeline/stage_gaborish.h
jxl/render_pipeline/stage_noise.cc
@ -208,6 +214,10 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/render_pipeline/stage_splines.h
jxl/render_pipeline/stage_spot.cc
jxl/render_pipeline/stage_spot.h
jxl/render_pipeline/stage_to_linear.cc
jxl/render_pipeline/stage_to_linear.h
jxl/render_pipeline/stage_tone_mapping.cc
jxl/render_pipeline/stage_tone_mapping.h
jxl/render_pipeline/stage_upsampling.cc
jxl/render_pipeline/stage_upsampling.h
jxl/render_pipeline/stage_write.cc
@ -235,8 +245,6 @@ set(JPEGXL_INTERNAL_SOURCES_ENC
jxl/butteraugli/butteraugli.cc
jxl/butteraugli/butteraugli.h
jxl/butteraugli_wrapper.cc
jxl/dec_file.cc
jxl/dec_file.h
jxl/enc_ac_strategy.cc
jxl/enc_ac_strategy.h
jxl/enc_adaptive_quantization.cc
@ -272,7 +280,6 @@ set(JPEGXL_INTERNAL_SOURCES_ENC
jxl/enc_entropy_coder.h
jxl/enc_external_image.cc
jxl/enc_external_image.h
jxl/enc_fast_heuristics.cc
jxl/enc_file.cc
jxl/enc_file.h
jxl/enc_frame.cc
@ -400,10 +407,10 @@ target_compile_options(jxl_dec-obj PRIVATE ${JPEGXL_INTERNAL_FLAGS})
target_compile_options(jxl_dec-obj PUBLIC ${JPEGXL_COVERAGE_FLAGS})
set_property(TARGET jxl_dec-obj PROPERTY POSITION_INDEPENDENT_CODE ON)
target_include_directories(jxl_dec-obj PUBLIC
${PROJECT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
$<TARGET_PROPERTY:hwy,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:brotlicommon-static,INTERFACE_INCLUDE_DIRECTORIES>
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:$<TARGET_PROPERTY:hwy,INTERFACE_INCLUDE_DIRECTORIES>>"
"$<BUILD_INTERFACE:$<TARGET_PROPERTY:brotlicommon-static,INTERFACE_INCLUDE_DIRECTORIES>>"
)
target_compile_definitions(jxl_dec-obj PUBLIC
${OBJ_COMPILE_DEFINITIONS}
@ -470,9 +477,9 @@ add_library(jxl_dec-static STATIC
target_link_libraries(jxl_dec-static
PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_DEC_INTERNAL_LIBS})
target_include_directories(jxl_dec-static PUBLIC
"${PROJECT_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_BINARY_DIR}/include")
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>")
# The list of objects in the static and shared libraries.
set(JPEGXL_INTERNAL_OBJECTS
@ -491,9 +498,9 @@ add_library(jxl-static STATIC ${JPEGXL_INTERNAL_OBJECTS})
target_link_libraries(jxl-static
PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_INTERNAL_LIBS})
target_include_directories(jxl-static PUBLIC
"${PROJECT_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_BINARY_DIR}/include")
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>")
# JXL_EXPORT is defined to "__declspec(dllimport)" automatically by CMake
# in Windows builds when including headers from the C API and compiling from
@ -506,7 +513,7 @@ target_compile_definitions(jxl_dec-static INTERFACE -DJXL_EXPORT=)
# TODO(deymo): Move TCMalloc linkage to the tools/ directory since the library
# shouldn't do any allocs anyway.
if(${JPEGXL_ENABLE_TCMALLOC})
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
@ -526,15 +533,14 @@ endif() # JPEGXL_ENABLE_TCMALLOC
# Install the static library too, but as jxl.a file without the -static except
# in Windows.
if (NOT WIN32)
if (NOT WIN32 OR MINGW)
set_target_properties(jxl-static PROPERTIES OUTPUT_NAME "jxl")
set_target_properties(jxl_dec-static PROPERTIES OUTPUT_NAME "jxl_dec")
endif()
install(TARGETS jxl-static DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(TARGETS jxl_dec-static DESTINATION ${CMAKE_INSTALL_LIBDIR})
if (((NOT DEFINED "${TARGET_SUPPORTS_SHARED_LIBS}") OR
TARGET_SUPPORTS_SHARED_LIBS) AND NOT JPEGXL_STATIC AND BUILD_SHARED_LIBS)
if (BUILD_SHARED_LIBS)
# Public shared library.
add_library(jxl SHARED ${JPEGXL_INTERNAL_OBJECTS})
@ -543,8 +549,8 @@ target_link_libraries(jxl PUBLIC ${JPEGXL_COVERAGE_FLAGS})
target_link_libraries(jxl PRIVATE ${JPEGXL_INTERNAL_SHARED_LIBS})
# Shared library include path contains only the "include/" paths.
target_include_directories(jxl PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_BINARY_DIR}/include")
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>")
set_target_properties(jxl PROPERTIES
VERSION ${JPEGXL_LIBRARY_VERSION}
SOVERSION ${JPEGXL_LIBRARY_SOVERSION}
@ -591,7 +597,7 @@ foreach(target IN ITEMS jxl jxl_dec)
# 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})
if(LINKER_SUPPORT_EXCLUDE_LIBS)
set_property(TARGET ${target} APPEND_STRING PROPERTY
LINK_FLAGS " ${LINKER_EXCLUDE_LIBS_FLAG}")
endif()
@ -607,8 +613,7 @@ install(TARGETS jxl
else()
add_library(jxl ALIAS jxl-static)
add_library(jxl_dec ALIAS jxl_dec-static)
endif() # TARGET_SUPPORTS_SHARED_LIBS AND NOT JPEGXL_STATIC AND
# BUILD_SHARED_LIBS
endif() # BUILD_SHARED_LIBS
# Add a pkg-config file for libjxl.
set(JPEGXL_LIBRARY_REQUIRES
@ -616,6 +621,20 @@ set(JPEGXL_LIBRARY_REQUIRES
if(NOT JPEGXL_ENABLE_SKCMS)
set(JPEGXL_LIBRARY_REQUIRES "${JPEGXL_LIBRARY_REQUIRES} lcms2")
endif()
# Allow adding prefix if CMAKE_INSTALL_INCLUDEDIR not absolute.
if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
set(PKGCONFIG_TARGET_INCLUDES "${CMAKE_INSTALL_INCLUDEDIR}")
else()
set(PKGCONFIG_TARGET_INCLUDES "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
endif()
# Allow adding prefix if CMAKE_INSTALL_LIBDIR not absolute.
if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
set(PKGCONFIG_TARGET_LIBS "${CMAKE_INSTALL_LIBDIR}")
else()
set(PKGCONFIG_TARGET_LIBS "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
endif()
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/jxl/libjxl.pc.in"
"libjxl.pc" @ONLY)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libjxl.pc"

View file

@ -108,4 +108,13 @@ void UnpremultiplyAlpha(float* JXL_RESTRICT r, float* JXL_RESTRICT g,
}
}
void UnpremultiplyAlpha(float* JXL_RESTRICT rgba, size_t num_pixels) {
for (size_t x = 0, ix = 0; x < num_pixels; ++x, ix += 4) {
const float multiplier = 1.f / std::max(kSmallAlpha, rgba[ix + 3]);
rgba[ix] *= multiplier;
rgba[ix + 1] *= multiplier;
rgba[ix + 2] *= multiplier;
}
}
} // namespace jxl

View file

@ -60,6 +60,7 @@ void PremultiplyAlpha(float* JXL_RESTRICT r, float* JXL_RESTRICT g,
void UnpremultiplyAlpha(float* JXL_RESTRICT r, float* JXL_RESTRICT g,
float* JXL_RESTRICT b, const float* JXL_RESTRICT a,
size_t num_pixels);
void UnpremultiplyAlpha(float* JXL_RESTRICT rgba, size_t num_pixels);
} // namespace jxl

View file

@ -5,8 +5,7 @@
#include "lib/jxl/alpha.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "lib/jxl/test_utils.h"
namespace jxl {
namespace {

View file

@ -12,11 +12,11 @@
namespace jxl {
std::vector<int> CreateFlatHistogram(int length, int total_count) {
std::vector<int32_t> CreateFlatHistogram(int length, int total_count) {
JXL_ASSERT(length > 0);
JXL_ASSERT(length <= total_count);
const int count = total_count / length;
std::vector<int> result(length, count);
std::vector<int32_t> result(length, count);
const int rem_counts = total_count % length;
for (int i = 0; i < rem_counts; ++i) {
++result[i];
@ -48,7 +48,7 @@ std::vector<int> CreateFlatHistogram(int length, int total_count) {
// underfull nor overfull, and represents exactly two symbols. The overfull
// entry might be either overfull or underfull, and is pushed into the
// corresponding stack.
void InitAliasTable(std::vector<int> distribution, uint32_t range,
void InitAliasTable(std::vector<int32_t> distribution, uint32_t range,
size_t log_alpha_size, AliasTable::Entry* JXL_RESTRICT a) {
while (!distribution.empty() && distribution.back() == 0) {
distribution.pop_back();

View file

@ -32,7 +32,7 @@ static JXL_INLINE uint32_t GetPopulationCountPrecision(uint32_t logcount,
// Returns a histogram where the counts are positive, differ by at most 1,
// and add up to total_count. The bigger counts (if any) are at the beginning
// of the histogram.
std::vector<int> CreateFlatHistogram(int length, int total_count);
std::vector<int32_t> CreateFlatHistogram(int length, int total_count);
// An alias table implements a mapping from the [0, ANS_TAB_SIZE) range into
// the [0, ANS_MAX_ALPHABET_SIZE) range, satisfying the following conditions:
@ -135,7 +135,7 @@ struct AliasTable {
};
// Computes an alias table for a given distribution.
void InitAliasTable(std::vector<int> distribution, uint32_t range,
void InitAliasTable(std::vector<int32_t> distribution, uint32_t range,
size_t log_alpha_size, AliasTable::Entry* JXL_RESTRICT a);
} // namespace jxl

View file

@ -112,7 +112,7 @@ void RoundtripRandomUnbalancedStream(int alphabet_size) {
constexpr int kPrecision = 1 << 10;
Rng rng(0);
for (size_t i = 0; i < kReps; i++) {
std::vector<int> distributions[kNumHistograms];
std::vector<int> distributions[kNumHistograms] = {};
for (int j = 0; j < kNumHistograms; j++) {
distributions[j].resize(kPrecision);
int symbol = 0;

View file

@ -25,6 +25,12 @@ void AuxOut::Print(size_t num_inputs) const {
printf("Average butteraugli iters: %10.2f\n",
num_butteraugli_iters * 1.0 / num_inputs);
if (min_quant_rescale != 1.0 || max_quant_rescale != 1.0) {
printf("quant rescale range: %f .. %f\n", min_quant_rescale,
max_quant_rescale);
printf("bitrate error range: %.3f%% .. %.3f%%\n",
100.0f * min_bitrate_error, 100.0f * max_bitrate_error);
}
for (size_t i = 0; i < layers.size(); ++i) {
if (layers[i].total_bits != 0) {

View file

@ -37,72 +37,54 @@ namespace jxl {
enum {
kLayerHeader = 0,
kLayerTOC,
kLayerDictionary,
kLayerSplines,
kLayerNoise,
kLayerQuant,
kLayerDequantTables,
kLayerOrder,
kLayerModularTree,
kLayerModularGlobal,
kLayerDC,
kLayerModularDcGroup,
kLayerControlFields,
kLayerOrder,
kLayerAC,
kLayerACTokens,
kLayerDictionary,
kLayerDots,
kLayerSplines,
kLayerLossless,
kLayerModularGlobal,
kLayerModularDcGroup,
kLayerModularAcGroup,
kLayerModularTree,
kLayerAlpha,
kLayerDepth,
kLayerExtraChannels,
kNumImageLayers
};
static inline const char* LayerName(size_t layer) {
switch (layer) {
case kLayerHeader:
return "headers";
return "Headers";
case kLayerTOC:
return "TOC";
case kLayerDictionary:
return "Patches";
case kLayerSplines:
return "Splines";
case kLayerNoise:
return "noise";
return "Noise";
case kLayerQuant:
return "quantizer";
case kLayerDequantTables:
return "quant tables";
case kLayerOrder:
return "order";
return "Quantizer";
case kLayerModularTree:
return "ModularTree";
case kLayerModularGlobal:
return "ModularGlobal";
case kLayerDC:
return "DC";
case kLayerModularDcGroup:
return "ModularDcGroup";
case kLayerControlFields:
return "ControlFields";
case kLayerOrder:
return "CoeffOrder";
case kLayerAC:
return "AC";
return "ACHistograms";
case kLayerACTokens:
return "ACTokens";
case kLayerDictionary:
return "dictionary";
case kLayerDots:
return "dots";
case kLayerSplines:
return "splines";
case kLayerLossless:
return "lossless";
case kLayerModularGlobal:
return "modularGlobal";
case kLayerModularDcGroup:
return "modularDcGroup";
case kLayerModularAcGroup:
return "modularAcGroup";
case kLayerModularTree:
return "modularTree";
case kLayerAlpha:
return "alpha";
case kLayerDepth:
return "depth";
case kLayerExtraChannels:
return "extra channels";
return "ModularAcGroup";
default:
JXL_ABORT("Invalid layer %d\n", static_cast<int>(layer));
}
@ -167,10 +149,22 @@ struct AuxOut {
dc_pred_usage[i] += victim.dc_pred_usage[i];
dc_pred_usage_xb[i] += victim.dc_pred_usage_xb[i];
}
max_quant_rescale = std::max(max_quant_rescale, victim.max_quant_rescale);
min_quant_rescale = std::min(min_quant_rescale, victim.min_quant_rescale);
max_bitrate_error = std::max(max_bitrate_error, victim.max_bitrate_error);
min_bitrate_error = std::min(min_bitrate_error, victim.min_bitrate_error);
}
void Print(size_t num_inputs) const;
size_t TotalBits() const {
size_t total = 0;
for (const auto& layer : layers) {
total += layer.total_bits;
}
return total;
}
template <typename T>
void DumpImage(const char* label, const Image3<T>& image) const {
if (!dump_image) return;
@ -285,6 +279,11 @@ struct AuxOut {
int num_butteraugli_iters = 0;
float max_quant_rescale = 1.0f;
float min_quant_rescale = 1.0f;
float min_bitrate_error = 0.0f;
float max_bitrate_error = 0.0f;
// If not empty, additional debugging information (e.g. debug images) is
// saved in files with this prefix.
std::string debug_prefix;

View file

@ -71,8 +71,9 @@ void* CacheAligned::Allocate(const size_t payload_size, size_t offset) {
// To avoid wasting space, the header resides at the end of `unused`,
// which therefore cannot be empty (offset == 0).
if (offset == 0) {
offset = kAlignment; // = round_up(sizeof(AllocationHeader), kAlignment)
static_assert(sizeof(AllocationHeader) <= kAlignment, "Else: round up");
// SVE/RVV vectors can be large, so we cannot rely on them (including the
// padding at the end of AllocationHeader) to fit in kAlignment.
offset = hwy::RoundUpTo(sizeof(AllocationHeader), kAlignment);
}
#if JXL_USE_MMAP

View file

@ -10,6 +10,8 @@
#include <stdint.h>
#include "lib/jxl/base/sanitizer_definitions.h"
// #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.
@ -75,6 +77,16 @@
#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

View file

@ -31,6 +31,9 @@ class ThreadPool {
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.

View file

@ -77,7 +77,8 @@ template <typename ContainerType>
static inline Status ReadFile(const std::string& pathname,
ContainerType* JXL_RESTRICT bytes) {
FileWrapper f(pathname, "rb");
if (f == nullptr) return JXL_FAILURE("Failed to open file for reading");
if (f == nullptr)
return JXL_FAILURE("Failed to open file for reading: %s", pathname.c_str());
// Get size of file in bytes
const int64_t size = f.size();

View file

@ -20,7 +20,8 @@
namespace jxl {
struct Rng {
explicit Rng(size_t seed)
: s{0x94D049BB133111EBull, 0xBF58476D1CE4E5B9ull + seed} {}
: s{static_cast<uint64_t>(0x94D049BB133111EBull),
static_cast<uint64_t>(0xBF58476D1CE4E5B9ull) + seed} {}
// Xorshift128+ adapted from xorshift128+-inl.h
uint64_t operator()() {

View file

@ -3,11 +3,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "lib/extras/codec.h"
#include "lib/jxl/dec_file.h"
#include "lib/jxl/image_test_utils.h"
#include "lib/jxl/test_utils.h"
#include "lib/jxl/testdata.h"
namespace jxl {
@ -20,9 +18,8 @@ TEST(BlendingTest, Crops) {
const PaddedBytes compressed =
ReadTestData("jxl/blending/cropped_traffic_light.jxl");
DecompressParams dparams;
CodecInOut decoded;
ASSERT_TRUE(DecodeFile(dparams, compressed, &decoded, pool));
ASSERT_TRUE(test::DecodeFile({}, compressed, &decoded, pool));
ASSERT_THAT(decoded.frames, SizeIs(4));
int i = 0;

View file

@ -13,6 +13,7 @@
#include <utility>
#include <vector>
#include "lib/jxl/alpha.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/common.h"
#include "lib/jxl/frame_header.h"
@ -133,18 +134,57 @@ class CodecInOut {
}
return true;
}
// Calls PremultiplyAlpha for each ImageBundle (preview/frames).
void PremultiplyAlpha() {
// Performs "PremultiplyAlpha" for each ImageBundle (preview/frames).
bool PremultiplyAlpha() {
const auto doPremultiplyAlpha = [](ImageBundle& bundle) {
if (!bundle.HasAlpha()) return;
if (!bundle.HasColor()) return;
auto* color = bundle.color();
const auto* alpha = bundle.alpha();
JXL_CHECK(color->ysize() == alpha->ysize());
JXL_CHECK(color->xsize() == alpha->xsize());
for (size_t y = 0; y < color->ysize(); y++) {
::jxl::PremultiplyAlpha(color->PlaneRow(0, y), color->PlaneRow(1, y),
color->PlaneRow(2, y), alpha->Row(y),
color->xsize());
}
};
ExtraChannelInfo* eci = metadata.m.Find(ExtraChannel::kAlpha);
if (eci == nullptr || eci->alpha_associated) return; // nothing to do
if (eci == nullptr || eci->alpha_associated) return false;
if (metadata.m.have_preview) {
preview_frame.PremultiplyAlpha();
doPremultiplyAlpha(preview_frame);
}
for (ImageBundle& ib : frames) {
ib.PremultiplyAlpha();
doPremultiplyAlpha(ib);
}
eci->alpha_associated = true;
return;
return true;
}
bool UnpremultiplyAlpha() {
const auto doUnpremultiplyAlpha = [](ImageBundle& bundle) {
if (!bundle.HasAlpha()) return;
if (!bundle.HasColor()) return;
auto* color = bundle.color();
const auto* alpha = bundle.alpha();
JXL_CHECK(color->ysize() == alpha->ysize());
JXL_CHECK(color->xsize() == alpha->xsize());
for (size_t y = 0; y < color->ysize(); y++) {
::jxl::UnpremultiplyAlpha(color->PlaneRow(0, y), color->PlaneRow(1, y),
color->PlaneRow(2, y), alpha->Row(y),
color->xsize());
}
};
ExtraChannelInfo* eci = metadata.m.Find(ExtraChannel::kAlpha);
if (eci == nullptr || !eci->alpha_associated) return false;
if (metadata.m.have_preview) {
doUnpremultiplyAlpha(preview_frame);
}
for (ImageBundle& ib : frames) {
doUnpremultiplyAlpha(ib);
}
eci->alpha_associated = false;
return true;
}
// -- DECODER INPUT:

View file

@ -88,7 +88,7 @@ static inline constexpr uint64_t EnumBits(Primaries /*unused*/) {
}
// Values from CICP TransferCharacteristics
enum TransferFunction : uint32_t {
enum class TransferFunction : uint32_t {
k709 = 1,
kUnknown = 2,
kLinear = 8,

View file

@ -468,7 +468,8 @@ Status MaybeCreateProfile(const ColorEncoding& c,
CreateICCCurvParaTag({2.6, 1.0, 0.0, 1.0, 0.0}, 3, &tags));
break;
default:
JXL_ABORT("Unknown TF %d", c.tf.GetTransferFunction());
JXL_ABORT("Unknown TF %u",
static_cast<unsigned int>(c.tf.GetTransferFunction()));
}
}
FinalizeICCTag(&tags, &tag_offset, &tag_size);

View file

@ -13,8 +13,6 @@
#include <string>
#include <utility>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/file_io.h"

View file

@ -12,7 +12,10 @@
#include <hwy/highway.h>
#include "lib/jxl/base/profiler.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/image_ops.h"
HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
@ -110,6 +113,180 @@ class Neighbors {
}
};
#if HWY_TARGET != HWY_SCALAR
// Returns indices for SetTableIndices such that TableLookupLanes on the
// rightmost unaligned vector (rightmost sample in its most-significant lane)
// returns the mirrored values, with the mirror outside the last valid sample.
static inline const int32_t* MirrorLanes(const size_t mod) {
const HWY_CAPPED(float, 16) d;
constexpr size_t kN = MaxLanes(d);
// For mod = `image width mod 16` 0..15:
// last full vec mirrored (mem order) loadedVec mirrorVec idxVec
// 0123456789abcdef| fedcba9876543210 fed..210 012..def 012..def
// 0123456789abcdef|0 0fedcba98765432 0fe..321 234..f00 123..eff
// 0123456789abcdef|01 10fedcba987654 10f..432 456..110 234..ffe
// 0123456789abcdef|012 210fedcba9876 210..543 67..2210 34..ffed
// 0123456789abcdef|0123 3210fedcba98 321..654 8..33210 4..ffedc
// 0123456789abcdef|01234 43210fedcba
// 0123456789abcdef|012345 543210fedc
// 0123456789abcdef|0123456 6543210fe
// 0123456789abcdef|01234567 76543210
// 0123456789abcdef|012345678 8765432
// 0123456789abcdef|0123456789 987654
// 0123456789abcdef|0123456789A A9876
// 0123456789abcdef|0123456789AB BA98
// 0123456789abcdef|0123456789ABC CBA
// 0123456789abcdef|0123456789ABCD DC
// 0123456789abcdef|0123456789ABCDE E EDC..10f EED..210 ffe..321
#if HWY_CAP_GE512
HWY_ALIGN static constexpr int32_t idx_lanes[2 * kN - 1] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, //
14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
#elif HWY_CAP_GE256
HWY_ALIGN static constexpr int32_t idx_lanes[2 * kN - 1] = {
1, 2, 3, 4, 5, 6, 7, 7, //
6, 5, 4, 3, 2, 1, 0};
#else // 128-bit
HWY_ALIGN static constexpr int32_t idx_lanes[2 * kN - 1] = {1, 2, 3, 3, //
2, 1, 0};
#endif
return idx_lanes + kN - 1 - mod;
}
#endif // HWY_TARGET != HWY_SCALAR
// Single entry point for convolution.
// "Strategy" (Direct*/Separable*) decides kernel size and how to evaluate it.
template <class Strategy>
class ConvolveT {
static constexpr int64_t kRadius = Strategy::kRadius;
using Simd = HWY_CAPPED(float, 16);
public:
static size_t MinWidth() {
#if HWY_TARGET == HWY_SCALAR
// First/Last use mirrored loads of up to +/- kRadius.
return 2 * kRadius;
#else
return Lanes(Simd()) + kRadius;
#endif
}
// "Image" is ImageF or Image3F.
template <class Image, class Weights>
static void Run(const Image& in, const Rect& rect, const Weights& weights,
ThreadPool* pool, Image* out) {
PROFILER_ZONE("ConvolveT::Run");
JXL_CHECK(SameSize(rect, *out));
JXL_CHECK(rect.xsize() >= MinWidth());
static_assert(int64_t(kRadius) <= 3,
"Must handle [0, kRadius) and >= kRadius");
switch (rect.xsize() % Lanes(Simd())) {
case 0:
return RunRows<0>(in, rect, weights, pool, out);
case 1:
return RunRows<1>(in, rect, weights, pool, out);
case 2:
return RunRows<2>(in, rect, weights, pool, out);
default:
return RunRows<3>(in, rect, weights, pool, out);
}
}
private:
template <size_t kSizeModN, class WrapRow, class Weights>
static JXL_INLINE void RunRow(const float* JXL_RESTRICT in,
const size_t xsize, const int64_t stride,
const WrapRow& wrap_row, const Weights& weights,
float* JXL_RESTRICT out) {
Strategy::template ConvolveRow<kSizeModN>(in, xsize, stride, wrap_row,
weights, out);
}
template <size_t kSizeModN, class Weights>
static JXL_INLINE void RunBorderRows(const ImageF& in, const Rect& rect,
const int64_t ybegin, const int64_t yend,
const Weights& weights, ImageF* out) {
const int64_t stride = in.PixelsPerRow();
const WrapRowMirror wrap_row(in, rect.ysize());
for (int64_t y = ybegin; y < yend; ++y) {
RunRow<kSizeModN>(rect.ConstRow(in, y), rect.xsize(), stride, wrap_row,
weights, out->Row(y));
}
}
// Image3F.
template <size_t kSizeModN, class Weights>
static JXL_INLINE void RunBorderRows(const Image3F& in, const Rect& rect,
const int64_t ybegin, const int64_t yend,
const Weights& weights, Image3F* out) {
const int64_t stride = in.PixelsPerRow();
for (int64_t y = ybegin; y < yend; ++y) {
for (size_t c = 0; c < 3; ++c) {
const WrapRowMirror wrap_row(in.Plane(c), rect.ysize());
RunRow<kSizeModN>(rect.ConstPlaneRow(in, c, y), rect.xsize(), stride,
wrap_row, weights, out->PlaneRow(c, y));
}
}
}
template <size_t kSizeModN, class Weights>
static JXL_INLINE void RunInteriorRows(const ImageF& in, const Rect& rect,
const int64_t ybegin,
const int64_t yend,
const Weights& weights,
ThreadPool* pool, ImageF* out) {
const int64_t stride = in.PixelsPerRow();
JXL_CHECK(RunOnPool(
pool, ybegin, yend, ThreadPool::NoInit,
[&](const uint32_t y, size_t /*thread*/) HWY_ATTR {
RunRow<kSizeModN>(rect.ConstRow(in, y), rect.xsize(), stride,
WrapRowUnchanged(), weights, out->Row(y));
},
"Convolve"));
}
// Image3F.
template <size_t kSizeModN, class Weights>
static JXL_INLINE void RunInteriorRows(const Image3F& in, const Rect& rect,
const int64_t ybegin,
const int64_t yend,
const Weights& weights,
ThreadPool* pool, Image3F* out) {
const int64_t stride = in.PixelsPerRow();
JXL_CHECK(RunOnPool(
pool, ybegin, yend, ThreadPool::NoInit,
[&](const uint32_t y, size_t /*thread*/) HWY_ATTR {
for (size_t c = 0; c < 3; ++c) {
RunRow<kSizeModN>(rect.ConstPlaneRow(in, c, y), rect.xsize(),
stride, WrapRowUnchanged(), weights,
out->PlaneRow(c, y));
}
},
"Convolve3"));
}
template <size_t kSizeModN, class Image, class Weights>
static JXL_INLINE void RunRows(const Image& in, const Rect& rect,
const Weights& weights, ThreadPool* pool,
Image* out) {
const int64_t ysize = rect.ysize();
RunBorderRows<kSizeModN>(in, rect, 0, std::min(int64_t(kRadius), ysize),
weights, out);
if (ysize > 2 * int64_t(kRadius)) {
RunInteriorRows<kSizeModN>(in, rect, int64_t(kRadius),
ysize - int64_t(kRadius), weights, pool, out);
}
if (ysize > int64_t(kRadius)) {
RunBorderRows<kSizeModN>(in, rect, ysize - int64_t(kRadius), ysize,
weights, out);
}
}
};
} // namespace
// NOLINTNEXTLINE(google-readability-namespace-comments)
} // namespace HWY_NAMESPACE

File diff suppressed because it is too large Load diff

View file

@ -75,28 +75,14 @@ const WeightsSymmetric5& WeightsSymmetric5Lowpass();
void SlowSymmetric3(const ImageF& in, const Rect& rect,
const WeightsSymmetric3& weights, ThreadPool* pool,
ImageF* JXL_RESTRICT out);
void SlowSymmetric3(const Image3F& in, const Rect& rect,
const WeightsSymmetric3& weights, ThreadPool* pool,
Image3F* JXL_RESTRICT out);
void SlowSeparable5(const ImageF& in, const Rect& rect,
const WeightsSeparable5& weights, ThreadPool* pool,
ImageF* out);
void SlowSeparable5(const Image3F& in, const Rect& rect,
const WeightsSeparable5& weights, ThreadPool* pool,
Image3F* out);
void SlowSeparable7(const ImageF& in, const Rect& rect,
const WeightsSeparable7& weights, ThreadPool* pool,
ImageF* out);
void SlowSeparable7(const Image3F& in, const Rect& rect,
const WeightsSeparable7& weights, ThreadPool* pool,
Image3F* out);
void SlowLaplacian5(const ImageF& in, const Rect& rect, ThreadPool* pool,
ImageF* out);
void SlowLaplacian5(const Image3F& in, const Rect& rect, ThreadPool* pool,
Image3F* out);
void Symmetric3(const ImageF& in, const Rect& rect,
const WeightsSymmetric3& weights, ThreadPool* pool,
@ -106,26 +92,14 @@ void Symmetric5(const ImageF& in, const Rect& rect,
const WeightsSymmetric5& weights, ThreadPool* pool,
ImageF* JXL_RESTRICT out);
void Symmetric5_3(const Image3F& in, const Rect& rect,
const WeightsSymmetric5& weights, ThreadPool* pool,
Image3F* JXL_RESTRICT out);
void Separable5(const ImageF& in, const Rect& rect,
const WeightsSeparable5& weights, ThreadPool* pool,
ImageF* out);
void Separable5_3(const Image3F& in, const Rect& rect,
const WeightsSeparable5& weights, ThreadPool* pool,
Image3F* out);
void Separable7(const ImageF& in, const Rect& rect,
const WeightsSeparable7& weights, ThreadPool* pool,
ImageF* out);
void Separable7_3(const Image3F& in, const Rect& rect,
const WeightsSeparable7& weights, ThreadPool* pool,
Image3F* out);
} // namespace jxl
#endif // LIB_JXL_CONVOLVE_H_

View file

@ -0,0 +1,257 @@
// Copyright (c) the JPEG XL Project 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/convolve.h"
#undef HWY_TARGET_INCLUDE
#define HWY_TARGET_INCLUDE "lib/jxl/convolve_separable5.cc"
#include <hwy/foreach_target.h>
#include <hwy/highway.h>
#include "lib/jxl/convolve-inl.h"
HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
// These templates are not found via ADL.
using hwy::HWY_NAMESPACE::Vec;
// 5x5 convolution by separable kernel with a single scan through the input.
// This is more cache-efficient than separate horizontal/vertical passes, and
// possibly faster (given enough registers) than tiling and/or transposing.
//
// Overview: imagine a 5x5 window around a central pixel. First convolve the
// rows by multiplying the pixels with the corresponding weights from
// WeightsSeparable5.horz[abs(x_offset) * 4]. Then multiply each of these
// intermediate results by the corresponding vertical weight, i.e.
// vert[abs(y_offset) * 4]. Finally, store the sum of these values as the
// convolution result at the position of the central pixel in the output.
//
// Each of these operations uses SIMD vectors. The central pixel and most
// importantly the output are aligned, so neighnoring pixels (e.g. x_offset=1)
// require unaligned loads. Because weights are supplied in identical groups of
// 4, we can use LoadDup128 to load them (slightly faster).
//
// Uses mirrored boundary handling. Until x >= kRadius, the horizontal
// convolution uses Neighbors class to shuffle vectors as if each of its lanes
// had been loaded from the mirrored offset. Similarly, the last full vector to
// write uses mirroring. In the case of scalar vectors, Neighbors is not usable
// and the value is loaded directly. Otherwise, the number of valid pixels
// modulo the vector size enables a small optimization: for smaller offsets,
// a non-mirrored load is sufficient.
class Separable5Strategy {
using D = HWY_CAPPED(float, 16);
using V = Vec<D>;
public:
static constexpr int64_t kRadius = 2;
template <size_t kSizeModN, class WrapRow>
static JXL_MAYBE_INLINE void ConvolveRow(
const float* const JXL_RESTRICT row_m, const size_t xsize,
const int64_t stride, const WrapRow& wrap_row,
const WeightsSeparable5& weights, float* const JXL_RESTRICT row_out) {
const D d;
const int64_t neg_stride = -stride; // allows LEA addressing.
const float* const JXL_RESTRICT row_t2 =
wrap_row(row_m + 2 * neg_stride, stride);
const float* const JXL_RESTRICT row_t1 =
wrap_row(row_m + 1 * neg_stride, stride);
const float* const JXL_RESTRICT row_b1 =
wrap_row(row_m + 1 * stride, stride);
const float* const JXL_RESTRICT row_b2 =
wrap_row(row_m + 2 * stride, stride);
const V wh0 = LoadDup128(d, weights.horz + 0 * 4);
const V wh1 = LoadDup128(d, weights.horz + 1 * 4);
const V wh2 = LoadDup128(d, weights.horz + 2 * 4);
const V wv0 = LoadDup128(d, weights.vert + 0 * 4);
const V wv1 = LoadDup128(d, weights.vert + 1 * 4);
const V wv2 = LoadDup128(d, weights.vert + 2 * 4);
size_t x = 0;
// More than one iteration for scalars.
for (; x < kRadius; x += Lanes(d)) {
const V conv0 = HorzConvolveFirst(row_m, x, xsize, wh0, wh1, wh2) * wv0;
const V conv1t = HorzConvolveFirst(row_t1, x, xsize, wh0, wh1, wh2);
const V conv1b = HorzConvolveFirst(row_b1, x, xsize, wh0, wh1, wh2);
const V conv1 = MulAdd(conv1t + conv1b, wv1, conv0);
const V conv2t = HorzConvolveFirst(row_t2, x, xsize, wh0, wh1, wh2);
const V conv2b = HorzConvolveFirst(row_b2, x, xsize, wh0, wh1, wh2);
const V conv2 = MulAdd(conv2t + conv2b, wv2, conv1);
Store(conv2, d, row_out + x);
}
// Main loop: load inputs without padding
for (; x + Lanes(d) + kRadius <= xsize; x += Lanes(d)) {
const V conv0 = HorzConvolve(row_m + x, wh0, wh1, wh2) * wv0;
const V conv1t = HorzConvolve(row_t1 + x, wh0, wh1, wh2);
const V conv1b = HorzConvolve(row_b1 + x, wh0, wh1, wh2);
const V conv1 = MulAdd(conv1t + conv1b, wv1, conv0);
const V conv2t = HorzConvolve(row_t2 + x, wh0, wh1, wh2);
const V conv2b = HorzConvolve(row_b2 + x, wh0, wh1, wh2);
const V conv2 = MulAdd(conv2t + conv2b, wv2, conv1);
Store(conv2, d, row_out + x);
}
// Last full vector to write (the above loop handled mod >= kRadius)
#if HWY_TARGET == HWY_SCALAR
while (x < xsize) {
#else
if (kSizeModN < kRadius) {
#endif
const V conv0 =
HorzConvolveLast<kSizeModN>(row_m, x, xsize, wh0, wh1, wh2) * wv0;
const V conv1t =
HorzConvolveLast<kSizeModN>(row_t1, x, xsize, wh0, wh1, wh2);
const V conv1b =
HorzConvolveLast<kSizeModN>(row_b1, x, xsize, wh0, wh1, wh2);
const V conv1 = MulAdd(conv1t + conv1b, wv1, conv0);
const V conv2t =
HorzConvolveLast<kSizeModN>(row_t2, x, xsize, wh0, wh1, wh2);
const V conv2b =
HorzConvolveLast<kSizeModN>(row_b2, x, xsize, wh0, wh1, wh2);
const V conv2 = MulAdd(conv2t + conv2b, wv2, conv1);
Store(conv2, d, row_out + x);
x += Lanes(d);
}
// If mod = 0, the above vector was the last.
if (kSizeModN != 0) {
for (; x < xsize; ++x) {
float mul = 0.0f;
for (int64_t dy = -kRadius; dy <= kRadius; ++dy) {
const float wy = weights.vert[std::abs(dy) * 4];
const float* clamped_row = wrap_row(row_m + dy * stride, stride);
for (int64_t dx = -kRadius; dx <= kRadius; ++dx) {
const float wx = weights.horz[std::abs(dx) * 4];
const int64_t clamped_x = Mirror(x + dx, xsize);
mul += clamped_row[clamped_x] * wx * wy;
}
}
row_out[x] = mul;
}
}
}
private:
// Same as HorzConvolve for the first/last vector in a row.
static JXL_MAYBE_INLINE V HorzConvolveFirst(
const float* const JXL_RESTRICT row, const int64_t x, const int64_t xsize,
const V wh0, const V wh1, const V wh2) {
const D d;
const V c = LoadU(d, row + x);
const V mul0 = c * wh0;
#if HWY_TARGET == HWY_SCALAR
const V l1 = LoadU(d, row + Mirror(x - 1, xsize));
const V l2 = LoadU(d, row + Mirror(x - 2, xsize));
#else
(void)xsize;
const V l1 = Neighbors::FirstL1(c);
const V l2 = Neighbors::FirstL2(c);
#endif
const V r1 = LoadU(d, row + x + 1);
const V r2 = LoadU(d, row + x + 2);
const V mul1 = MulAdd(l1 + r1, wh1, mul0);
const V mul2 = MulAdd(l2 + r2, wh2, mul1);
return mul2;
}
template <size_t kSizeModN>
static JXL_MAYBE_INLINE V
HorzConvolveLast(const float* const JXL_RESTRICT row, const int64_t x,
const int64_t xsize, const V wh0, const V wh1, const V wh2) {
const D d;
const V c = LoadU(d, row + x);
const V mul0 = c * wh0;
const V l1 = LoadU(d, row + x - 1);
const V l2 = LoadU(d, row + x - 2);
V r1, r2;
#if HWY_TARGET == HWY_SCALAR
r1 = LoadU(d, row + Mirror(x + 1, xsize));
r2 = LoadU(d, row + Mirror(x + 2, xsize));
#else
const size_t N = Lanes(d);
if (kSizeModN == 0) {
r2 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 2)));
r1 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 1)));
} else { // == 1
const auto last = LoadU(d, row + xsize - N);
r2 = TableLookupLanes(last, SetTableIndices(d, MirrorLanes(N - 1)));
r1 = last;
}
#endif
// Sum of pixels with Manhattan distance i, multiplied by weights[i].
const V sum1 = l1 + r1;
const V mul1 = MulAdd(sum1, wh1, mul0);
const V sum2 = l2 + r2;
const V mul2 = MulAdd(sum2, wh2, mul1);
return mul2;
}
// Requires kRadius valid pixels before/after pos.
static JXL_MAYBE_INLINE V HorzConvolve(const float* const JXL_RESTRICT pos,
const V wh0, const V wh1,
const V wh2) {
const D d;
const V c = LoadU(d, pos);
const V mul0 = c * wh0;
// Loading anew is faster than combining vectors.
const V l1 = LoadU(d, pos - 1);
const V r1 = LoadU(d, pos + 1);
const V l2 = LoadU(d, pos - 2);
const V r2 = LoadU(d, pos + 2);
// Sum of pixels with Manhattan distance i, multiplied by weights[i].
const V sum1 = l1 + r1;
const V mul1 = MulAdd(sum1, wh1, mul0);
const V sum2 = l2 + r2;
const V mul2 = MulAdd(sum2, wh2, mul1);
return mul2;
}
};
void Separable5(const ImageF& in, const Rect& rect,
const WeightsSeparable5& weights, ThreadPool* pool,
ImageF* out) {
using Conv = ConvolveT<Separable5Strategy>;
if (rect.xsize() >= Conv::MinWidth()) {
return Conv::Run(in, rect, weights, pool, out);
}
return SlowSeparable5(in, rect, weights, pool, out);
}
// NOLINTNEXTLINE(google-readability-namespace-comments)
} // namespace HWY_NAMESPACE
} // namespace jxl
HWY_AFTER_NAMESPACE();
#if HWY_ONCE
namespace jxl {
HWY_EXPORT(Separable5);
void Separable5(const ImageF& in, const Rect& rect,
const WeightsSeparable5& weights, ThreadPool* pool,
ImageF* out) {
return HWY_DYNAMIC_DISPATCH(Separable5)(in, rect, weights, pool, out);
}
} // namespace jxl
#endif // HWY_ONCE

View file

@ -0,0 +1,282 @@
// Copyright (c) the JPEG XL Project 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/convolve.h"
#undef HWY_TARGET_INCLUDE
#define HWY_TARGET_INCLUDE "lib/jxl/convolve_separable7.cc"
#include <hwy/foreach_target.h>
#include <hwy/highway.h>
#include "lib/jxl/convolve-inl.h"
HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
// These templates are not found via ADL.
using hwy::HWY_NAMESPACE::Vec;
// 7x7 convolution by separable kernel with a single scan through the input.
// Extended version of Separable5, see documentation there.
class Separable7Strategy {
using D = HWY_CAPPED(float, 16);
using V = Vec<D>;
public:
static constexpr int64_t kRadius = 3;
template <size_t kSizeModN, class WrapRow>
static JXL_MAYBE_INLINE void ConvolveRow(
const float* const JXL_RESTRICT row_m, const size_t xsize,
const int64_t stride, const WrapRow& wrap_row,
const WeightsSeparable7& weights, float* const JXL_RESTRICT row_out) {
const D d;
const int64_t neg_stride = -stride; // allows LEA addressing.
const float* const JXL_RESTRICT row_t3 =
wrap_row(row_m + 3 * neg_stride, stride);
const float* const JXL_RESTRICT row_t2 =
wrap_row(row_m + 2 * neg_stride, stride);
const float* const JXL_RESTRICT row_t1 =
wrap_row(row_m + 1 * neg_stride, stride);
const float* const JXL_RESTRICT row_b1 =
wrap_row(row_m + 1 * stride, stride);
const float* const JXL_RESTRICT row_b2 =
wrap_row(row_m + 2 * stride, stride);
const float* const JXL_RESTRICT row_b3 =
wrap_row(row_m + 3 * stride, stride);
const V wh0 = LoadDup128(d, weights.horz + 0 * 4);
const V wh1 = LoadDup128(d, weights.horz + 1 * 4);
const V wh2 = LoadDup128(d, weights.horz + 2 * 4);
const V wh3 = LoadDup128(d, weights.horz + 3 * 4);
const V wv0 = LoadDup128(d, weights.vert + 0 * 4);
const V wv1 = LoadDup128(d, weights.vert + 1 * 4);
const V wv2 = LoadDup128(d, weights.vert + 2 * 4);
const V wv3 = LoadDup128(d, weights.vert + 3 * 4);
size_t x = 0;
// More than one iteration for scalars.
for (; x < kRadius; x += Lanes(d)) {
const V conv0 =
HorzConvolveFirst(row_m, x, xsize, wh0, wh1, wh2, wh3) * wv0;
const V conv1t = HorzConvolveFirst(row_t1, x, xsize, wh0, wh1, wh2, wh3);
const V conv1b = HorzConvolveFirst(row_b1, x, xsize, wh0, wh1, wh2, wh3);
const V conv1 = MulAdd(conv1t + conv1b, wv1, conv0);
const V conv2t = HorzConvolveFirst(row_t2, x, xsize, wh0, wh1, wh2, wh3);
const V conv2b = HorzConvolveFirst(row_b2, x, xsize, wh0, wh1, wh2, wh3);
const V conv2 = MulAdd(conv2t + conv2b, wv2, conv1);
const V conv3t = HorzConvolveFirst(row_t3, x, xsize, wh0, wh1, wh2, wh3);
const V conv3b = HorzConvolveFirst(row_b3, x, xsize, wh0, wh1, wh2, wh3);
const V conv3 = MulAdd(conv3t + conv3b, wv3, conv2);
Store(conv3, d, row_out + x);
}
// Main loop: load inputs without padding
for (; x + Lanes(d) + kRadius <= xsize; x += Lanes(d)) {
const V conv0 = HorzConvolve(row_m + x, wh0, wh1, wh2, wh3) * wv0;
const V conv1t = HorzConvolve(row_t1 + x, wh0, wh1, wh2, wh3);
const V conv1b = HorzConvolve(row_b1 + x, wh0, wh1, wh2, wh3);
const V conv1 = MulAdd(conv1t + conv1b, wv1, conv0);
const V conv2t = HorzConvolve(row_t2 + x, wh0, wh1, wh2, wh3);
const V conv2b = HorzConvolve(row_b2 + x, wh0, wh1, wh2, wh3);
const V conv2 = MulAdd(conv2t + conv2b, wv2, conv1);
const V conv3t = HorzConvolve(row_t3 + x, wh0, wh1, wh2, wh3);
const V conv3b = HorzConvolve(row_b3 + x, wh0, wh1, wh2, wh3);
const V conv3 = MulAdd(conv3t + conv3b, wv3, conv2);
Store(conv3, d, row_out + x);
}
// Last full vector to write (the above loop handled mod >= kRadius)
#if HWY_TARGET == HWY_SCALAR
while (x < xsize) {
#else
if (kSizeModN < kRadius) {
#endif
const V conv0 =
HorzConvolveLast<kSizeModN>(row_m, x, xsize, wh0, wh1, wh2, wh3) *
wv0;
const V conv1t =
HorzConvolveLast<kSizeModN>(row_t1, x, xsize, wh0, wh1, wh2, wh3);
const V conv1b =
HorzConvolveLast<kSizeModN>(row_b1, x, xsize, wh0, wh1, wh2, wh3);
const V conv1 = MulAdd(conv1t + conv1b, wv1, conv0);
const V conv2t =
HorzConvolveLast<kSizeModN>(row_t2, x, xsize, wh0, wh1, wh2, wh3);
const V conv2b =
HorzConvolveLast<kSizeModN>(row_b2, x, xsize, wh0, wh1, wh2, wh3);
const V conv2 = MulAdd(conv2t + conv2b, wv2, conv1);
const V conv3t =
HorzConvolveLast<kSizeModN>(row_t3, x, xsize, wh0, wh1, wh2, wh3);
const V conv3b =
HorzConvolveLast<kSizeModN>(row_b3, x, xsize, wh0, wh1, wh2, wh3);
const V conv3 = MulAdd(conv3t + conv3b, wv3, conv2);
Store(conv3, d, row_out + x);
x += Lanes(d);
}
// If mod = 0, the above vector was the last.
if (kSizeModN != 0) {
for (; x < xsize; ++x) {
float mul = 0.0f;
for (int64_t dy = -kRadius; dy <= kRadius; ++dy) {
const float wy = weights.vert[std::abs(dy) * 4];
const float* clamped_row = wrap_row(row_m + dy * stride, stride);
for (int64_t dx = -kRadius; dx <= kRadius; ++dx) {
const float wx = weights.horz[std::abs(dx) * 4];
const int64_t clamped_x = Mirror(x + dx, xsize);
mul += clamped_row[clamped_x] * wx * wy;
}
}
row_out[x] = mul;
}
}
}
private:
// Same as HorzConvolve for the first/last vector in a row.
static JXL_MAYBE_INLINE V HorzConvolveFirst(
const float* const JXL_RESTRICT row, const int64_t x, const int64_t xsize,
const V wh0, const V wh1, const V wh2, const V wh3) {
const D d;
const V c = LoadU(d, row + x);
const V mul0 = c * wh0;
#if HWY_TARGET == HWY_SCALAR
const V l1 = LoadU(d, row + Mirror(x - 1, xsize));
const V l2 = LoadU(d, row + Mirror(x - 2, xsize));
const V l3 = LoadU(d, row + Mirror(x - 3, xsize));
#else
(void)xsize;
const V l1 = Neighbors::FirstL1(c);
const V l2 = Neighbors::FirstL2(c);
const V l3 = Neighbors::FirstL3(c);
#endif
const V r1 = LoadU(d, row + x + 1);
const V r2 = LoadU(d, row + x + 2);
const V r3 = LoadU(d, row + x + 3);
const V mul1 = MulAdd(l1 + r1, wh1, mul0);
const V mul2 = MulAdd(l2 + r2, wh2, mul1);
const V mul3 = MulAdd(l3 + r3, wh3, mul2);
return mul3;
}
template <size_t kSizeModN>
static JXL_MAYBE_INLINE V HorzConvolveLast(
const float* const JXL_RESTRICT row, const int64_t x, const int64_t xsize,
const V wh0, const V wh1, const V wh2, const V wh3) {
const D d;
const V c = LoadU(d, row + x);
const V mul0 = c * wh0;
const V l1 = LoadU(d, row + x - 1);
const V l2 = LoadU(d, row + x - 2);
const V l3 = LoadU(d, row + x - 3);
V r1, r2, r3;
#if HWY_TARGET == HWY_SCALAR
r1 = LoadU(d, row + Mirror(x + 1, xsize));
r2 = LoadU(d, row + Mirror(x + 2, xsize));
r3 = LoadU(d, row + Mirror(x + 3, xsize));
#else
const size_t N = Lanes(d);
if (kSizeModN == 0) {
r3 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 3)));
r2 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 2)));
r1 = TableLookupLanes(c, SetTableIndices(d, MirrorLanes(N - 1)));
} else if (kSizeModN == 1) {
const auto last = LoadU(d, row + xsize - N);
r3 = TableLookupLanes(last, SetTableIndices(d, MirrorLanes(N - 2)));
r2 = TableLookupLanes(last, SetTableIndices(d, MirrorLanes(N - 1)));
r1 = last;
} else /* kSizeModN >= 2 */ {
const auto last = LoadU(d, row + xsize - N);
r3 = TableLookupLanes(last, SetTableIndices(d, MirrorLanes(N - 1)));
r2 = last;
r1 = LoadU(d, row + x + 1);
}
#endif
// Sum of pixels with Manhattan distance i, multiplied by weights[i].
const V sum1 = l1 + r1;
const V mul1 = MulAdd(sum1, wh1, mul0);
const V sum2 = l2 + r2;
const V mul2 = MulAdd(sum2, wh2, mul1);
const V sum3 = l3 + r3;
const V mul3 = MulAdd(sum3, wh3, mul2);
return mul3;
}
// Returns one vector of horizontal convolution results; lane i is the result
// for pixel pos + i. This is the fast path for interior pixels, i.e. kRadius
// valid pixels before/after pos.
static JXL_MAYBE_INLINE V HorzConvolve(const float* const JXL_RESTRICT pos,
const V wh0, const V wh1, const V wh2,
const V wh3) {
const D d;
const V c = LoadU(d, pos);
const V mul0 = c * wh0;
// TODO(janwas): better to Combine
const V l1 = LoadU(d, pos - 1);
const V r1 = LoadU(d, pos + 1);
const V l2 = LoadU(d, pos - 2);
const V r2 = LoadU(d, pos + 2);
const V l3 = LoadU(d, pos - 3);
const V r3 = LoadU(d, pos + 3);
// Sum of pixels with Manhattan distance i, multiplied by weights[i].
const V sum1 = l1 + r1;
const V mul1 = MulAdd(sum1, wh1, mul0);
const V sum2 = l2 + r2;
const V mul2 = MulAdd(sum2, wh2, mul1);
const V sum3 = l3 + r3;
const V mul3 = MulAdd(sum3, wh3, mul2);
return mul3;
}
};
void Separable7(const ImageF& in, const Rect& rect,
const WeightsSeparable7& weights, ThreadPool* pool,
ImageF* out) {
using Conv = ConvolveT<Separable7Strategy>;
if (rect.xsize() >= Conv::MinWidth()) {
return Conv::Run(in, rect, weights, pool, out);
}
return SlowSeparable7(in, rect, weights, pool, out);
}
// NOLINTNEXTLINE(google-readability-namespace-comments)
} // namespace HWY_NAMESPACE
} // namespace jxl
HWY_AFTER_NAMESPACE();
#if HWY_ONCE
namespace jxl {
HWY_EXPORT(Separable7);
void Separable7(const ImageF& in, const Rect& rect,
const WeightsSeparable7& weights, ThreadPool* pool,
ImageF* out) {
return HWY_DYNAMIC_DISPATCH(Separable7)(in, rect, weights, pool, out);
}
} // namespace jxl
#endif // HWY_ONCE

View file

@ -0,0 +1,212 @@
// Copyright (c) the JPEG XL Project 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/convolve.h"
#include "lib/jxl/convolve-inl.h"
namespace jxl {
//------------------------------------------------------------------------------
// Kernels
// 4 instances of a given literal value, useful as input to LoadDup128.
#define JXL_REP4(literal) literal, literal, literal, literal
// Concentrates energy in low-frequency components (e.g. for antialiasing).
const WeightsSymmetric3& WeightsSymmetric3Lowpass() {
// Computed by research/convolve_weights.py's cubic spline approximations of
// prolate spheroidal wave functions.
constexpr float w0 = 0.36208932f;
constexpr float w1 = 0.12820096f;
constexpr float w2 = 0.03127668f;
static constexpr WeightsSymmetric3 weights = {
{JXL_REP4(w0)}, {JXL_REP4(w1)}, {JXL_REP4(w2)}};
return weights;
}
const WeightsSeparable5& WeightsSeparable5Lowpass() {
constexpr float w0 = 0.41714928f;
constexpr float w1 = 0.25539268f;
constexpr float w2 = 0.03603267f;
static constexpr WeightsSeparable5 weights = {
{JXL_REP4(w0), JXL_REP4(w1), JXL_REP4(w2)},
{JXL_REP4(w0), JXL_REP4(w1), JXL_REP4(w2)}};
return weights;
}
const WeightsSymmetric5& WeightsSymmetric5Lowpass() {
static constexpr WeightsSymmetric5 weights = {
{JXL_REP4(0.1740135f)}, {JXL_REP4(0.1065369f)}, {JXL_REP4(0.0150310f)},
{JXL_REP4(0.0652254f)}, {JXL_REP4(0.0012984f)}, {JXL_REP4(0.0092025f)}};
return weights;
}
const WeightsSeparable5& WeightsSeparable5Gaussian1() {
constexpr float w0 = 0.38774f;
constexpr float w1 = 0.24477f;
constexpr float w2 = 0.06136f;
static constexpr WeightsSeparable5 weights = {
{JXL_REP4(w0), JXL_REP4(w1), JXL_REP4(w2)},
{JXL_REP4(w0), JXL_REP4(w1), JXL_REP4(w2)}};
return weights;
}
const WeightsSeparable5& WeightsSeparable5Gaussian2() {
constexpr float w0 = 0.250301f;
constexpr float w1 = 0.221461f;
constexpr float w2 = 0.153388f;
static constexpr WeightsSeparable5 weights = {
{JXL_REP4(w0), JXL_REP4(w1), JXL_REP4(w2)},
{JXL_REP4(w0), JXL_REP4(w1), JXL_REP4(w2)}};
return weights;
}
#undef JXL_REP4
//------------------------------------------------------------------------------
// Slow
namespace {
template <class WrapX, class WrapY>
float SlowSymmetric3Pixel(const ImageF& in, const int64_t ix, const int64_t iy,
const int64_t xsize, const int64_t ysize,
const WeightsSymmetric3& weights) {
float sum = 0.0f;
// ix: image; kx: kernel
for (int64_t ky = -1; ky <= 1; ky++) {
const int64_t y = WrapY()(iy + ky, ysize);
const float* JXL_RESTRICT row_in = in.ConstRow(static_cast<size_t>(y));
const float wc = ky == 0 ? weights.c[0] : weights.r[0];
const float wlr = ky == 0 ? weights.r[0] : weights.d[0];
const int64_t xm1 = WrapX()(ix - 1, xsize);
const int64_t xp1 = WrapX()(ix + 1, xsize);
sum += row_in[ix] * wc + (row_in[xm1] + row_in[xp1]) * wlr;
}
return sum;
}
template <class WrapY>
void SlowSymmetric3Row(const ImageF& in, const int64_t iy, const int64_t xsize,
const int64_t ysize, const WeightsSymmetric3& weights,
float* JXL_RESTRICT row_out) {
row_out[0] =
SlowSymmetric3Pixel<WrapMirror, WrapY>(in, 0, iy, xsize, ysize, weights);
for (int64_t ix = 1; ix < xsize - 1; ix++) {
row_out[ix] = SlowSymmetric3Pixel<WrapUnchanged, WrapY>(in, ix, iy, xsize,
ysize, weights);
}
{
const int64_t ix = xsize - 1;
row_out[ix] = SlowSymmetric3Pixel<WrapMirror, WrapY>(in, ix, iy, xsize,
ysize, weights);
}
}
} // namespace
void SlowSymmetric3(const ImageF& in, const Rect& rect,
const WeightsSymmetric3& weights, ThreadPool* pool,
ImageF* JXL_RESTRICT out) {
PROFILER_FUNC;
const int64_t xsize = static_cast<int64_t>(rect.xsize());
const int64_t ysize = static_cast<int64_t>(rect.ysize());
const int64_t kRadius = 1;
JXL_CHECK(RunOnPool(
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
[&](const uint32_t task, size_t /*thread*/) {
const int64_t iy = task;
float* JXL_RESTRICT out_row = out->Row(static_cast<size_t>(iy));
if (iy < kRadius || iy >= ysize - kRadius) {
SlowSymmetric3Row<WrapMirror>(in, iy, xsize, ysize, weights, out_row);
} else {
SlowSymmetric3Row<WrapUnchanged>(in, iy, xsize, ysize, weights,
out_row);
}
},
"SlowSymmetric3"));
}
namespace {
// Separable kernels, any radius.
float SlowSeparablePixel(const ImageF& in, const Rect& rect, const int64_t x,
const int64_t y, const int64_t radius,
const float* JXL_RESTRICT horz_weights,
const float* JXL_RESTRICT vert_weights) {
const size_t xsize = rect.xsize();
const size_t ysize = rect.ysize();
const WrapMirror wrap;
float mul = 0.0f;
for (int dy = -radius; dy <= radius; ++dy) {
const float wy = vert_weights[std::abs(dy) * 4];
const size_t sy = wrap(y + dy, ysize);
JXL_CHECK(sy < ysize);
const float* const JXL_RESTRICT row = rect.ConstRow(in, sy);
for (int dx = -radius; dx <= radius; ++dx) {
const float wx = horz_weights[std::abs(dx) * 4];
const size_t sx = wrap(x + dx, xsize);
JXL_CHECK(sx < xsize);
mul += row[sx] * wx * wy;
}
}
return mul;
}
} // namespace
void SlowSeparable5(const ImageF& in, const Rect& rect,
const WeightsSeparable5& weights, ThreadPool* pool,
ImageF* out) {
PROFILER_FUNC;
const float* horz_weights = &weights.horz[0];
const float* vert_weights = &weights.vert[0];
const size_t ysize = rect.ysize();
JXL_CHECK(RunOnPool(
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
[&](const uint32_t task, size_t /*thread*/) {
const int64_t y = task;
float* const JXL_RESTRICT row_out = out->Row(y);
for (size_t x = 0; x < rect.xsize(); ++x) {
row_out[x] = SlowSeparablePixel(in, rect, x, y, /*radius=*/2,
horz_weights, vert_weights);
}
},
"SlowSeparable5"));
}
void SlowSeparable7(const ImageF& in, const Rect& rect,
const WeightsSeparable7& weights, ThreadPool* pool,
ImageF* out) {
PROFILER_FUNC;
const float* horz_weights = &weights.horz[0];
const float* vert_weights = &weights.vert[0];
const size_t ysize = rect.ysize();
JXL_CHECK(RunOnPool(
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
[&](const uint32_t task, size_t /*thread*/) {
const int64_t y = task;
float* const JXL_RESTRICT row_out = out->Row(y);
for (size_t x = 0; x < rect.xsize(); ++x) {
row_out[x] = SlowSeparablePixel(in, rect, x, y, /*radius=*/3,
horz_weights, vert_weights);
}
},
"SlowSeparable7"));
}
} // namespace jxl

View file

@ -0,0 +1,191 @@
// Copyright (c) the JPEG XL Project 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/convolve.h"
#undef HWY_TARGET_INCLUDE
#define HWY_TARGET_INCLUDE "lib/jxl/convolve_symmetric3.cc"
#include <hwy/foreach_target.h>
#include <hwy/highway.h>
#include "lib/jxl/convolve-inl.h"
HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
// These templates are not found via ADL.
using hwy::HWY_NAMESPACE::Vec;
template <class WrapY, class V>
static V WeightedSum(const ImageF& in, const WrapY wrap_y, const size_t ix,
const int64_t iy, const size_t ysize, const V wx0,
const V wx1, const V wx2) {
const HWY_FULL(float) d;
const float* JXL_RESTRICT center = in.ConstRow(wrap_y(iy, ysize)) + ix;
const auto in_m2 = LoadU(d, center - 2);
const auto in_p2 = LoadU(d, center + 2);
const auto in_m1 = LoadU(d, center - 1);
const auto in_p1 = LoadU(d, center + 1);
const auto in_00 = Load(d, center);
const auto sum_2 = wx2 * (in_m2 + in_p2);
const auto sum_1 = wx1 * (in_m1 + in_p1);
const auto sum_0 = wx0 * in_00;
return sum_2 + sum_1 + sum_0;
}
// 3x3 convolution by symmetric kernel with a single scan through the input.
class Symmetric3Strategy {
using D = HWY_CAPPED(float, 16);
using V = Vec<D>;
public:
static constexpr int64_t kRadius = 1;
// Only accesses pixels in [0, xsize).
template <size_t kSizeModN, class WrapRow>
static JXL_MAYBE_INLINE void ConvolveRow(
const float* const JXL_RESTRICT row_m, const size_t xsize,
const int64_t stride, const WrapRow& wrap_row,
const WeightsSymmetric3& weights, float* const JXL_RESTRICT row_out) {
const D d;
// t, m, b = top, middle, bottom row;
const float* const JXL_RESTRICT row_t = wrap_row(row_m - stride, stride);
const float* const JXL_RESTRICT row_b = wrap_row(row_m + stride, stride);
// Must load in advance - compiler doesn't understand LoadDup128 and
// schedules them too late.
const V w0 = LoadDup128(d, weights.c);
const V w1 = LoadDup128(d, weights.r);
const V w2 = LoadDup128(d, weights.d);
// l, c, r = left, center, right. Leftmost vector: need FirstL1.
{
const V tc = LoadU(d, row_t + 0);
const V mc = LoadU(d, row_m + 0);
const V bc = LoadU(d, row_b + 0);
const V tl = Neighbors::FirstL1(tc);
const V tr = LoadU(d, row_t + 0 + 1);
const V ml = Neighbors::FirstL1(mc);
const V mr = LoadU(d, row_m + 0 + 1);
const V bl = Neighbors::FirstL1(bc);
const V br = LoadU(d, row_b + 0 + 1);
const V conv =
WeightedSum(tl, tc, tr, ml, mc, mr, bl, bc, br, w0, w1, w2);
Store(conv, d, row_out + 0);
}
// Loop as long as we can load enough new values:
const size_t N = Lanes(d);
size_t x = N;
for (; x + N + kRadius <= xsize; x += N) {
const auto conv = ConvolveValid(row_t, row_m, row_b, x, w0, w1, w2);
Store(conv, d, row_out + x);
}
// For final (partial) vector:
const V tc = LoadU(d, row_t + x);
const V mc = LoadU(d, row_m + x);
const V bc = LoadU(d, row_b + x);
V tr, mr, br;
#if HWY_TARGET == HWY_SCALAR
tr = tc; // Single-lane => mirrored right neighbor = center value.
mr = mc;
br = bc;
#else
if (kSizeModN == 0) {
// The above loop didn't handle the last vector because it needs an
// additional right neighbor (generated via mirroring).
auto mirror = SetTableIndices(d, MirrorLanes(N - 1));
tr = TableLookupLanes(tc, mirror);
mr = TableLookupLanes(mc, mirror);
br = TableLookupLanes(bc, mirror);
} else {
auto mirror = SetTableIndices(d, MirrorLanes((xsize % N) - 1));
// Loads last valid value into uppermost lane and mirrors.
tr = TableLookupLanes(LoadU(d, row_t + xsize - N), mirror);
mr = TableLookupLanes(LoadU(d, row_m + xsize - N), mirror);
br = TableLookupLanes(LoadU(d, row_b + xsize - N), mirror);
}
#endif
const V tl = LoadU(d, row_t + x - 1);
const V ml = LoadU(d, row_m + x - 1);
const V bl = LoadU(d, row_b + x - 1);
const V conv = WeightedSum(tl, tc, tr, ml, mc, mr, bl, bc, br, w0, w1, w2);
Store(conv, d, row_out + x);
}
private:
// Returns sum{x_i * w_i}.
template <class V>
static JXL_MAYBE_INLINE V WeightedSum(const V tl, const V tc, const V tr,
const V ml, const V mc, const V mr,
const V bl, const V bc, const V br,
const V w0, const V w1, const V w2) {
const V sum_tb = tc + bc;
// Faster than 5 mul + 4 FMA.
const V mul0 = mc * w0;
const V sum_lr = ml + mr;
const V x1 = sum_tb + sum_lr;
const V mul1 = MulAdd(x1, w1, mul0);
const V sum_t2 = tl + tr;
const V sum_b2 = bl + br;
const V x2 = sum_t2 + sum_b2;
const V mul2 = MulAdd(x2, w2, mul1);
return mul2;
}
static JXL_MAYBE_INLINE V ConvolveValid(const float* JXL_RESTRICT row_t,
const float* JXL_RESTRICT row_m,
const float* JXL_RESTRICT row_b,
const int64_t x, const V w0,
const V w1, const V w2) {
const D d;
const V tc = LoadU(d, row_t + x);
const V mc = LoadU(d, row_m + x);
const V bc = LoadU(d, row_b + x);
const V tl = LoadU(d, row_t + x - 1);
const V tr = LoadU(d, row_t + x + 1);
const V ml = LoadU(d, row_m + x - 1);
const V mr = LoadU(d, row_m + x + 1);
const V bl = LoadU(d, row_b + x - 1);
const V br = LoadU(d, row_b + x + 1);
return WeightedSum(tl, tc, tr, ml, mc, mr, bl, bc, br, w0, w1, w2);
}
};
void Symmetric3(const ImageF& in, const Rect& rect,
const WeightsSymmetric3& weights, ThreadPool* pool,
ImageF* out) {
using Conv = ConvolveT<Symmetric3Strategy>;
if (rect.xsize() >= Conv::MinWidth()) {
return Conv::Run(in, rect, weights, pool, out);
}
return SlowSymmetric3(in, rect, weights, pool, out);
}
// NOLINTNEXTLINE(google-readability-namespace-comments)
} // namespace HWY_NAMESPACE
} // namespace jxl
HWY_AFTER_NAMESPACE();
#if HWY_ONCE
namespace jxl {
HWY_EXPORT(Symmetric3);
void Symmetric3(const ImageF& in, const Rect& rect,
const WeightsSymmetric3& weights, ThreadPool* pool,
ImageF* out) {
return HWY_DYNAMIC_DISPATCH(Symmetric3)(in, rect, weights, pool, out);
}
} // namespace jxl
#endif // HWY_ONCE

View file

@ -0,0 +1,183 @@
// Copyright (c) the JPEG XL Project 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/convolve.h"
#undef HWY_TARGET_INCLUDE
#define HWY_TARGET_INCLUDE "lib/jxl/convolve_symmetric5.cc"
#include <hwy/foreach_target.h>
#include <hwy/highway.h>
#include "lib/jxl/common.h" // RoundUpTo
#include "lib/jxl/convolve-inl.h"
HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
// These templates are not found via ADL.
using hwy::HWY_NAMESPACE::Vec;
// Weighted sum of 1x5 pixels around ix, iy with [wx2 wx1 wx0 wx1 wx2].
template <class WrapY>
static float WeightedSumBorder(const ImageF& in, const WrapY wrap_y,
const int64_t ix, const int64_t iy,
const size_t xsize, const size_t ysize,
const float wx0, const float wx1,
const float wx2) {
const WrapMirror wrap_x;
const float* JXL_RESTRICT row = in.ConstRow(wrap_y(iy, ysize));
const float in_m2 = row[wrap_x(ix - 2, xsize)];
const float in_p2 = row[wrap_x(ix + 2, xsize)];
const float in_m1 = row[wrap_x(ix - 1, xsize)];
const float in_p1 = row[wrap_x(ix + 1, xsize)];
const float in_00 = row[ix];
const float sum_2 = wx2 * (in_m2 + in_p2);
const float sum_1 = wx1 * (in_m1 + in_p1);
const float sum_0 = wx0 * in_00;
return sum_2 + sum_1 + sum_0;
}
template <class WrapY, class V>
static V WeightedSum(const ImageF& in, const WrapY wrap_y, const size_t ix,
const int64_t iy, const size_t ysize, const V wx0,
const V wx1, const V wx2) {
const HWY_FULL(float) d;
const float* JXL_RESTRICT center = in.ConstRow(wrap_y(iy, ysize)) + ix;
const auto in_m2 = LoadU(d, center - 2);
const auto in_p2 = LoadU(d, center + 2);
const auto in_m1 = LoadU(d, center - 1);
const auto in_p1 = LoadU(d, center + 1);
const auto in_00 = Load(d, center);
const auto sum_2 = wx2 * (in_m2 + in_p2);
const auto sum_1 = wx1 * (in_m1 + in_p1);
const auto sum_0 = wx0 * in_00;
return sum_2 + sum_1 + sum_0;
}
// Produces result for one pixel
template <class WrapY>
float Symmetric5Border(const ImageF& in, const Rect& rect, const int64_t ix,
const int64_t iy, const WeightsSymmetric5& weights) {
const float w0 = weights.c[0];
const float w1 = weights.r[0];
const float w2 = weights.R[0];
const float w4 = weights.d[0];
const float w5 = weights.L[0];
const float w8 = weights.D[0];
const size_t xsize = rect.xsize();
const size_t ysize = rect.ysize();
const WrapY wrap_y;
// Unrolled loop over all 5 rows of the kernel.
float sum0 = WeightedSumBorder(in, wrap_y, ix, iy, xsize, ysize, w0, w1, w2);
sum0 += WeightedSumBorder(in, wrap_y, ix, iy - 2, xsize, ysize, w2, w5, w8);
float sum1 =
WeightedSumBorder(in, wrap_y, ix, iy + 2, xsize, ysize, w2, w5, w8);
sum0 += WeightedSumBorder(in, wrap_y, ix, iy - 1, xsize, ysize, w1, w4, w5);
sum1 += WeightedSumBorder(in, wrap_y, ix, iy + 1, xsize, ysize, w1, w4, w5);
return sum0 + sum1;
}
// Produces result for one vector's worth of pixels
template <class WrapY>
static void Symmetric5Interior(const ImageF& in, const Rect& rect,
const int64_t ix, const int64_t iy,
const WeightsSymmetric5& weights,
float* JXL_RESTRICT row_out) {
const HWY_FULL(float) d;
const auto w0 = LoadDup128(d, weights.c);
const auto w1 = LoadDup128(d, weights.r);
const auto w2 = LoadDup128(d, weights.R);
const auto w4 = LoadDup128(d, weights.d);
const auto w5 = LoadDup128(d, weights.L);
const auto w8 = LoadDup128(d, weights.D);
const size_t ysize = rect.ysize();
const WrapY wrap_y;
// Unrolled loop over all 5 rows of the kernel.
auto sum0 = WeightedSum(in, wrap_y, ix, iy, ysize, w0, w1, w2);
sum0 += WeightedSum(in, wrap_y, ix, iy - 2, ysize, w2, w5, w8);
auto sum1 = WeightedSum(in, wrap_y, ix, iy + 2, ysize, w2, w5, w8);
sum0 += WeightedSum(in, wrap_y, ix, iy - 1, ysize, w1, w4, w5);
sum1 += WeightedSum(in, wrap_y, ix, iy + 1, ysize, w1, w4, w5);
Store(sum0 + sum1, d, row_out + ix);
}
template <class WrapY>
static void Symmetric5Row(const ImageF& in, const Rect& rect, const int64_t iy,
const WeightsSymmetric5& weights,
float* JXL_RESTRICT row_out) {
const int64_t kRadius = 2;
const size_t xsize = rect.xsize();
size_t ix = 0;
const HWY_FULL(float) d;
const size_t N = Lanes(d);
const size_t aligned_x = RoundUpTo(kRadius, N);
for (; ix < std::min(aligned_x, xsize); ++ix) {
row_out[ix] = Symmetric5Border<WrapY>(in, rect, ix, iy, weights);
}
for (; ix + N + kRadius <= xsize; ix += N) {
Symmetric5Interior<WrapY>(in, rect, ix, iy, weights, row_out);
}
for (; ix < xsize; ++ix) {
row_out[ix] = Symmetric5Border<WrapY>(in, rect, ix, iy, weights);
}
}
static JXL_NOINLINE void Symmetric5BorderRow(const ImageF& in, const Rect& rect,
const int64_t iy,
const WeightsSymmetric5& weights,
float* JXL_RESTRICT row_out) {
return Symmetric5Row<WrapMirror>(in, rect, iy, weights, row_out);
}
// Semi-vectorized (interior pixels Fonly); called directly like slow::, unlike
// the fully vectorized strategies below.
void Symmetric5(const ImageF& in, const Rect& rect,
const WeightsSymmetric5& weights, ThreadPool* pool,
ImageF* JXL_RESTRICT out) {
PROFILER_FUNC;
const size_t ysize = rect.ysize();
JXL_CHECK(RunOnPool(
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
[&](const uint32_t task, size_t /*thread*/) {
const int64_t iy = task;
if (iy < 2 || iy >= static_cast<ssize_t>(ysize) - 2) {
Symmetric5BorderRow(in, rect, iy, weights, out->Row(iy));
} else {
Symmetric5Row<WrapUnchanged>(in, rect, iy, weights, out->Row(iy));
}
},
"Symmetric5x5Convolution"));
}
// NOLINTNEXTLINE(google-readability-namespace-comments)
} // namespace HWY_NAMESPACE
} // namespace jxl
HWY_AFTER_NAMESPACE();
#if HWY_ONCE
namespace jxl {
HWY_EXPORT(Symmetric5);
void Symmetric5(const ImageF& in, const Rect& rect,
const WeightsSymmetric5& weights, ThreadPool* pool,
ImageF* JXL_RESTRICT out) {
return HWY_DYNAMIC_DISPATCH(Symmetric5)(in, rect, weights, pool, out);
}
} // namespace jxl
#endif // HWY_ONCE

View file

@ -148,9 +148,9 @@ void TestConvolve() {
ThreadPoolInternal pool3(3);
for (size_t ysize = kConvolveMaxRadius; ysize < 16; ++ysize) {
JXL_DEBUG(JXL_DEBUG_CONVOLVE,
"%" PRIuS " x %" PRIuS
" (target %d)===============================",
xsize, ysize, HWY_TARGET);
"%" PRIuS " x %" PRIuS " (target %" PRIx64
")===============================",
xsize, ysize, static_cast<int64_t>(HWY_TARGET));
JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sym3------------------");
VerifySymmetric3(xsize, ysize, null_pool, &rng);

View file

@ -51,7 +51,7 @@ int TestInit(void* jpegxl_opaque, size_t num_threads) { return 0; }
} // namespace
TEST_F(DataParallelTest, RunnerCalledParamenters) {
TEST_F(DataParallelTest, RunnerCalledParameters) {
EXPECT_TRUE(pool_.Run(
1234, 5678, [](size_t /* num_threads */) { return true; },
[](uint32_t /* task */, size_t /* thread */) { return; }));

View file

@ -48,7 +48,7 @@ inline int DecodeVarLenUint16(BitReader* input) {
return 0;
}
Status ReadHistogram(int precision_bits, std::vector<int>* counts,
Status ReadHistogram(int precision_bits, std::vector<int32_t>* counts,
BitReader* input) {
int simple_code = input->ReadBits(1);
if (simple_code == 1) {
@ -228,7 +228,7 @@ Status DecodeANSCodes(const size_t num_histograms,
AliasTable::Entry* alias_tables =
reinterpret_cast<AliasTable::Entry*>(result->alias_tables.get());
for (size_t c = 0; c < num_histograms; ++c) {
std::vector<int> counts;
std::vector<int32_t> counts;
if (!ReadHistogram(ANS_LOG_TAB_SIZE, &counts, in)) {
return JXL_FAILURE("Invalid histogram bitstream.");
}

View file

@ -9,11 +9,14 @@
#include "lib/jxl/render_pipeline/stage_blending.h"
#include "lib/jxl/render_pipeline/stage_chroma_upsampling.h"
#include "lib/jxl/render_pipeline/stage_epf.h"
#include "lib/jxl/render_pipeline/stage_from_linear.h"
#include "lib/jxl/render_pipeline/stage_gaborish.h"
#include "lib/jxl/render_pipeline/stage_noise.h"
#include "lib/jxl/render_pipeline/stage_patches.h"
#include "lib/jxl/render_pipeline/stage_splines.h"
#include "lib/jxl/render_pipeline/stage_spot.h"
#include "lib/jxl/render_pipeline/stage_to_linear.h"
#include "lib/jxl/render_pipeline/stage_tone_mapping.h"
#include "lib/jxl/render_pipeline/stage_upsampling.h"
#include "lib/jxl/render_pipeline/stage_write.h"
#include "lib/jxl/render_pipeline/stage_xyb.h"
@ -89,7 +92,9 @@ Status PassesDecoderState::PreparePipeline(ImageBundle* decoded,
}
if ((frame_header.flags & FrameHeader::kPatches) != 0) {
builder.AddStage(GetPatchesStage(&shared->image_features.patches));
builder.AddStage(
GetPatchesStage(&shared->image_features.patches,
3 + shared->metadata->m.num_extra_channels));
}
if ((frame_header.flags & FrameHeader::kSplines) != 0) {
builder.AddStage(GetSplineStage(&shared->image_features.splines));
@ -150,19 +155,29 @@ Status PassesDecoderState::PreparePipeline(ImageBundle* decoded,
height, rgb_output_is_rgba,
has_alpha, alpha_c));
} else {
bool linear = false;
if (frame_header.color_transform == ColorTransform::kYCbCr) {
builder.AddStage(GetYCbCrStage());
} else if (frame_header.color_transform == ColorTransform::kXYB) {
builder.AddStage(GetXYBStage(output_encoding_info));
builder.AddStage(GetXYBStage(output_encoding_info.opsin_params));
linear = true;
} // Nothing to do for kNone.
if (options.coalescing && NeedsBlending(this)) {
if (linear) {
builder.AddStage(GetFromLinearStage(output_encoding_info));
linear = false;
}
builder.AddStage(
GetBlendingStage(this, output_encoding_info.color_encoding));
}
if (options.coalescing && frame_header.CanBeReferenced() &&
!frame_header.save_before_color_transform) {
if (linear) {
builder.AddStage(GetFromLinearStage(output_encoding_info));
linear = false;
}
builder.AddStage(GetWriteToImageBundleStage(
&frame_storage_for_referencing, output_encoding_info.color_encoding));
}
@ -180,10 +195,30 @@ Status PassesDecoderState::PreparePipeline(ImageBundle* decoded,
}
}
auto tone_mapping_stage = GetToneMappingStage(output_encoding_info);
if (tone_mapping_stage) {
if (!linear) {
auto to_linear_stage = GetToLinearStage(output_encoding_info);
if (!to_linear_stage) {
return JXL_FAILURE(
"attempting to perform tone mapping on colorspace not "
"convertible to linear");
}
builder.AddStage(std::move(to_linear_stage));
linear = true;
}
builder.AddStage(std::move(tone_mapping_stage));
}
if (linear) {
builder.AddStage(GetFromLinearStage(output_encoding_info));
linear = false;
}
if (pixel_callback.IsPresent()) {
builder.AddStage(GetWriteToPixelCallbackStage(pixel_callback, width,
height, rgb_output_is_rgba,
has_alpha, alpha_c));
builder.AddStage(GetWriteToPixelCallbackStage(
pixel_callback, width, height, rgb_output_is_rgba, has_alpha,
unpremul_alpha, alpha_c));
} else if (rgb_output) {
builder.AddStage(GetWriteToU8Stage(rgb_output, rgb_stride, height,
rgb_output_is_rgba, has_alpha,

View file

@ -8,6 +8,7 @@
#include <stdint.h>
#include <atomic>
#include <hwy/base.h> // HWY_ALIGN_MAX
#include "jxl/decode.h"
@ -89,6 +90,9 @@ struct PassesDecoderState {
// If true, rgb_output or callback output is RGBA using 4 instead of 3 bytes
// per pixel.
bool rgb_output_is_rgba;
// If true, the RGBA output will be unpremultiplied before writing to the
// output callback (the output buffer case is handled in ConvertToExternal).
bool unpremul_alpha;
// Callback for line-by-line output.
PixelCallback pixel_callback;
@ -134,7 +138,9 @@ struct PassesDecoderState {
rgb_output = nullptr;
rgb_output_is_rgba = false;
unpremul_alpha = false;
fast_xyb_srgb8_conversion = false;
pixel_callback = PixelCallback();
used_acs = 0;
upsampler8x = GetUpsamplingStage(shared->metadata->transform_data, 0, 3);

View file

@ -37,8 +37,8 @@ void InverseMoveToFrontTransform(uint8_t* v, int v_len) {
}
}
bool VerifyContextMap(const std::vector<uint8_t>& context_map,
const size_t num_htrees) {
Status VerifyContextMap(const std::vector<uint8_t>& context_map,
const size_t num_htrees) {
std::vector<bool> have_htree(num_htrees);
size_t num_found = 0;
for (const uint8_t htree : context_map) {
@ -58,8 +58,8 @@ bool VerifyContextMap(const std::vector<uint8_t>& context_map,
} // namespace
bool DecodeContextMap(std::vector<uint8_t>* context_map, size_t* num_htrees,
BitReader* input) {
Status DecodeContextMap(std::vector<uint8_t>* context_map, size_t* num_htrees,
BitReader* input) {
bool is_simple = input->ReadFixedBits<1>();
if (is_simple) {
int bits_per_entry = input->ReadFixedBits<2>();

View file

@ -22,8 +22,8 @@ constexpr size_t kMaxClusters = 256;
// context_map->size() must be the number of possible context ids.
// Sets *num_htrees to the number of different histogram ids in
// *context_map.
bool DecodeContextMap(std::vector<uint8_t>* context_map, size_t* num_htrees,
BitReader* input);
Status DecodeContextMap(std::vector<uint8_t>* context_map, size_t* num_htrees,
BitReader* input);
} // namespace jxl

View file

@ -451,14 +451,15 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
JxlEndianness endianness, size_t stride,
jxl::ThreadPool* pool, void* out_image,
size_t out_size, const PixelCallback& out_callback,
jxl::Orientation undo_orientation) {
jxl::Orientation undo_orientation,
bool unpremul_alpha) {
bool want_alpha = num_channels == 2 || num_channels == 4;
size_t color_channels = num_channels <= 2 ? 1 : 3;
const Image3F* color = &ib.color();
// Undo premultiplied alpha.
Image3F unpremul;
if (ib.AlphaIsPremultiplied() && ib.HasAlpha()) {
if (ib.AlphaIsPremultiplied() && ib.HasAlpha() && unpremul_alpha) {
unpremul = Image3F(color->xsize(), color->ysize());
CopyImageTo(*color, &unpremul);
for (size_t y = 0; y < unpremul.ysize(); y++) {

View file

@ -38,7 +38,8 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
JxlEndianness endianness, size_t stride_out,
jxl::ThreadPool* thread_pool, void* out_image,
size_t out_size, const PixelCallback& out_callback,
jxl::Orientation undo_orientation);
jxl::Orientation undo_orientation,
bool unpremul_alpha = false);
// Converts single-channel image to interleaved void* pixel buffer with the
// given format, with a single channel.

View file

@ -1,177 +0,0 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/jxl/dec_file.h"
#include <stddef.h>
#include <utility>
#include <vector>
#include "jxl/decode.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/override.h"
#include "lib/jxl/base/profiler.h"
#include "lib/jxl/color_management.h"
#include "lib/jxl/common.h"
#include "lib/jxl/dec_bit_reader.h"
#include "lib/jxl/dec_frame.h"
#include "lib/jxl/frame_header.h"
#include "lib/jxl/headers.h"
#include "lib/jxl/icc_codec.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/jpeg/dec_jpeg_data_writer.h"
namespace jxl {
namespace {
Status DecodeHeaders(BitReader* reader, CodecInOut* io) {
JXL_RETURN_IF_ERROR(ReadSizeHeader(reader, &io->metadata.size));
JXL_RETURN_IF_ERROR(ReadImageMetadata(reader, &io->metadata.m));
io->metadata.transform_data.nonserialized_xyb_encoded =
io->metadata.m.xyb_encoded;
JXL_RETURN_IF_ERROR(Bundle::Read(reader, &io->metadata.transform_data));
return true;
}
} // namespace
// To avoid the complexity of file I/O and buffering, we assume the bitstream
// is loaded (or for large images/sequences: mapped into) memory.
Status DecodeFile(const DecompressParams& dparams,
const Span<const uint8_t> file, CodecInOut* JXL_RESTRICT io,
ThreadPool* pool) {
PROFILER_ZONE("DecodeFile uninstrumented");
// Marker
JxlSignature signature = JxlSignatureCheck(file.data(), file.size());
if (signature == JXL_SIG_NOT_ENOUGH_BYTES || signature == JXL_SIG_INVALID) {
return JXL_FAILURE("File does not start with known JPEG XL signature");
}
std::unique_ptr<jpeg::JPEGData> jpeg_data = nullptr;
if (dparams.keep_dct) {
if (io->Main().jpeg_data == nullptr) {
return JXL_FAILURE("Caller must set jpeg_data");
}
jpeg_data = std::move(io->Main().jpeg_data);
}
Status ret = true;
{
BitReader reader(file);
BitReaderScopedCloser reader_closer(&reader, &ret);
if (reader.ReadFixedBits<16>() != 0x0AFF) {
// We don't have a naked codestream. Make a quick & dirty attempt to find
// the codestream.
// TODO(jon): get rid of this whole function
const unsigned char* begin = file.data();
const unsigned char* end = file.data() + file.size() - 4;
while (begin < end) {
if (!memcmp(begin, "jxlc", 4)) break;
begin++;
}
if (begin >= end) return JXL_FAILURE("Couldn't find jxl codestream");
reader.SkipBits(8 * (begin - file.data() + 2));
unsigned int firstbytes = reader.ReadFixedBits<16>();
if (firstbytes != 0x0AFF)
return JXL_FAILURE("Codestream didn't start with FF0A but with %X",
firstbytes);
}
{
JXL_RETURN_IF_ERROR(DecodeHeaders(&reader, io));
size_t xsize = io->metadata.xsize();
size_t ysize = io->metadata.ysize();
JXL_RETURN_IF_ERROR(VerifyDimensions(&io->constraints, xsize, ysize));
}
if (io->metadata.m.color_encoding.WantICC()) {
PaddedBytes icc;
JXL_RETURN_IF_ERROR(ReadICC(&reader, &icc));
JXL_RETURN_IF_ERROR(io->metadata.m.color_encoding.SetICC(std::move(icc)));
}
// Set ICC profile in jpeg_data.
if (jpeg_data) {
Status res = jpeg::SetJPEGDataFromICC(io->metadata.m.color_encoding.ICC(),
jpeg_data.get());
if (!res) {
return res;
}
}
PassesDecoderState dec_state;
JXL_RETURN_IF_ERROR(dec_state.output_encoding_info.Set(
io->metadata,
ColorEncoding::LinearSRGB(io->metadata.m.color_encoding.IsGray())));
if (io->metadata.m.have_preview) {
JXL_RETURN_IF_ERROR(reader.JumpToByteBoundary());
JXL_RETURN_IF_ERROR(DecodeFrame(dparams, &dec_state, pool, &reader,
&io->preview_frame, io->metadata,
&io->constraints, /*is_preview=*/true));
}
// Only necessary if no ICC and no preview.
JXL_RETURN_IF_ERROR(reader.JumpToByteBoundary());
if (io->metadata.m.have_animation && dparams.keep_dct) {
return JXL_FAILURE("Cannot decode to JPEG an animation");
}
io->frames.clear();
Status dec_ok(false);
do {
io->frames.emplace_back(&io->metadata.m);
if (jpeg_data) {
io->frames.back().jpeg_data = std::move(jpeg_data);
}
// Skip frames that are not displayed.
bool found_displayed_frame = true;
do {
dec_ok =
DecodeFrame(dparams, &dec_state, pool, &reader, &io->frames.back(),
io->metadata, &io->constraints);
if (!dparams.allow_partial_files) {
JXL_RETURN_IF_ERROR(dec_ok);
} else if (!dec_ok) {
io->frames.pop_back();
found_displayed_frame = false;
break;
}
} while (dec_state.shared->frame_header.frame_type !=
FrameType::kRegularFrame &&
dec_state.shared->frame_header.frame_type !=
FrameType::kSkipProgressive);
if (found_displayed_frame) {
// if found_displayed_frame is true io->frames shouldn't be empty
// because we added a frame before the loop.
JXL_ASSERT(!io->frames.empty());
io->dec_pixels += io->frames.back().xsize() * io->frames.back().ysize();
}
} while (!dec_state.shared->frame_header.is_last && dec_ok);
if (io->frames.empty()) return JXL_FAILURE("Not enough data.");
if (dparams.check_decompressed_size && !dparams.allow_partial_files &&
dparams.max_downsampling == 1) {
if (reader.TotalBitsConsumed() != file.size() * kBitsPerByte) {
return JXL_FAILURE("DecodeFile reader position not at EOF.");
}
}
// Suppress errors when decoding partial files with DC frames.
if (!reader.AllReadsWithinBounds() && dparams.allow_partial_files) {
reader_closer.CloseAndSuppressError();
}
io->CheckMetadata();
// reader is closed here.
}
return ret;
}
} // namespace jxl

View file

@ -1,48 +0,0 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#ifndef LIB_JXL_DEC_FILE_H_
#define LIB_JXL_DEC_FILE_H_
// Top-level interface for JXL decoding.
#include <stdint.h>
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/codec_in_out.h"
#include "lib/jxl/dec_params.h"
namespace jxl {
// Decodes the preview image, if present, and stores it in `preview`.
// Must be the first frame in the file. Does nothing if there is no preview
// frame present according to the metadata.
Status DecodePreview(const DecompressParams& dparams,
const CodecMetadata& metadata,
BitReader* JXL_RESTRICT reader, ThreadPool* pool,
ImageBundle* JXL_RESTRICT preview, uint64_t* dec_pixels,
const SizeConstraints* constraints);
// Implementation detail: currently decodes to linear sRGB. The contract is:
// `io` appears 'identical' (modulo compression artifacts) to the encoder input
// in a color-aware viewer. Note that `io->metadata.m.color_encoding`
// identifies the color space that was passed to the encoder; clients that want
// that same encoding must call `io->TransformTo` afterwards.
Status DecodeFile(const DecompressParams& params,
const Span<const uint8_t> file, CodecInOut* io,
ThreadPool* pool = nullptr);
static inline Status DecodeFile(const DecompressParams& params,
const PaddedBytes& file, CodecInOut* io,
ThreadPool* pool = nullptr) {
return DecodeFile(params, Span<const uint8_t>(file), io, pool);
}
} // namespace jxl
#endif // LIB_JXL_DEC_FILE_H_

View file

@ -15,6 +15,7 @@
#include <utility>
#include <vector>
#include "jxl/types.h"
#include "lib/jxl/ac_context.h"
#include "lib/jxl/ac_strategy.h"
#include "lib/jxl/ans_params.h"
@ -35,7 +36,6 @@
#include "lib/jxl/dec_cache.h"
#include "lib/jxl/dec_group.h"
#include "lib/jxl/dec_modular.h"
#include "lib/jxl/dec_params.h"
#include "lib/jxl/dec_patch_dictionary.h"
#include "lib/jxl/dec_xyb.h"
#include "lib/jxl/epf.h"
@ -77,186 +77,72 @@ Status DecodeGlobalDCInfo(BitReader* reader, bool is_jpeg,
}
} // namespace
Status DecodeFrameHeader(BitReader* JXL_RESTRICT reader,
FrameHeader* JXL_RESTRICT frame_header) {
JXL_ASSERT(frame_header->nonserialized_metadata != nullptr);
JXL_RETURN_IF_ERROR(ReadFrameHeader(reader, frame_header));
return true;
}
static BitReader* GetReaderForSection(
size_t num_groups, size_t num_passes, size_t group_codes_begin,
const std::vector<uint64_t>& group_offsets,
const std::vector<uint32_t>& group_sizes, BitReader* JXL_RESTRICT reader,
BitReader* JXL_RESTRICT store, size_t index) {
if (num_groups == 1 && num_passes == 1) return reader;
const size_t group_offset = group_codes_begin + group_offsets[index];
const size_t next_group_offset =
group_codes_begin + group_offsets[index] + group_sizes[index];
// The order of these variables must be:
// group_codes_begin <= group_offset <= next_group_offset <= file.size()
JXL_DASSERT(group_codes_begin <= group_offset);
JXL_DASSERT(group_offset <= next_group_offset);
JXL_DASSERT(next_group_offset <= reader->TotalBytes());
const size_t group_size = next_group_offset - group_offset;
const size_t remaining_size = reader->TotalBytes() - group_offset;
const size_t size = std::min(group_size + 8, remaining_size);
*store =
BitReader(Span<const uint8_t>(reader->FirstByte() + group_offset, size));
return store;
}
Status DecodeFrame(const DecompressParams& dparams,
PassesDecoderState* dec_state, ThreadPool* JXL_RESTRICT pool,
BitReader* JXL_RESTRICT reader, ImageBundle* decoded,
const CodecMetadata& metadata,
const SizeConstraints* constraints, bool is_preview) {
PROFILER_ZONE("DecodeFrame uninstrumented");
Status DecodeFrame(PassesDecoderState* dec_state, ThreadPool* JXL_RESTRICT pool,
const uint8_t* next_in, size_t avail_in,
ImageBundle* decoded, const CodecMetadata& metadata,
bool use_slow_rendering_pipeline) {
FrameDecoder frame_decoder(dec_state, metadata, pool,
dparams.use_slow_render_pipeline);
use_slow_rendering_pipeline);
frame_decoder.SetFrameSizeLimits(constraints);
JXL_RETURN_IF_ERROR(frame_decoder.InitFrame(
reader, decoded, is_preview, dparams.allow_partial_files,
dparams.allow_partial_files && dparams.allow_more_progressive_steps,
true));
// Handling of progressive decoding.
const FrameHeader& frame_header = frame_decoder.GetFrameHeader();
{
size_t max_passes = dparams.max_passes;
size_t max_downsampling = std::max(
dparams.max_downsampling >> (frame_header.dc_level * 3), size_t(1));
// TODO(veluca): deal with downsamplings >= 8.
if (max_downsampling >= 8) {
max_passes = 0;
} else {
for (uint32_t i = 0; i < frame_header.passes.num_downsample; ++i) {
if (max_downsampling >= frame_header.passes.downsample[i] &&
max_passes > frame_header.passes.last_pass[i]) {
max_passes = frame_header.passes.last_pass[i] + 1;
}
}
}
// Do not use downsampling for kReferenceOnly frames.
if (frame_header.frame_type == FrameType::kReferenceOnly) {
max_passes = frame_header.passes.num_passes;
}
max_passes = std::min<size_t>(max_passes, frame_header.passes.num_passes);
frame_decoder.SetMaxPasses(max_passes);
}
frame_decoder.SetRenderSpotcolors(dparams.render_spotcolors);
frame_decoder.SetCoalescing(dparams.coalescing);
size_t processed_bytes = reader->TotalBitsConsumed() / kBitsPerByte;
BitReader reader(Span<const uint8_t>(next_in, avail_in));
JXL_RETURN_IF_ERROR(frame_decoder.InitFrame(&reader, decoded,
/*is_preview=*/false,
/*output_needed=*/true));
JXL_RETURN_IF_ERROR(reader.AllReadsWithinBounds());
size_t header_bytes = reader.TotalBitsConsumed() / kBitsPerByte;
JXL_RETURN_IF_ERROR(reader.Close());
size_t processed_bytes = header_bytes;
Status close_ok = true;
std::vector<std::unique_ptr<BitReader>> section_readers;
{
std::vector<std::unique_ptr<BitReaderScopedCloser>> section_closers;
std::vector<FrameDecoder::SectionInfo> section_info;
std::vector<FrameDecoder::SectionStatus> section_status;
size_t bytes_to_skip = 0;
for (size_t i = 0; i < frame_decoder.NumSections(); i++) {
size_t b = frame_decoder.SectionOffsets()[i];
size_t e = b + frame_decoder.SectionSizes()[i];
bytes_to_skip += e - b;
size_t pos = reader->TotalBitsConsumed() / kBitsPerByte;
if (pos + (dparams.allow_more_progressive_steps &&
(i == 0 ||
frame_header.encoding == FrameEncoding::kModular)
? b
: e) <=
reader->TotalBytes() ||
(i == 0 && dparams.allow_more_progressive_steps)) {
auto br = make_unique<BitReader>(Span<const uint8_t>(
reader->FirstByte() + b + pos,
(pos + b > reader->TotalBytes()
? 0
: std::min(reader->TotalBytes() - pos - b, e - b))));
section_info.emplace_back(FrameDecoder::SectionInfo{br.get(), i});
section_closers.emplace_back(
make_unique<BitReaderScopedCloser>(br.get(), &close_ok));
section_readers.emplace_back(std::move(br));
} else if (!dparams.allow_partial_files) {
return JXL_FAILURE("Premature end of stream.");
}
size_t pos = header_bytes;
for (auto toc_entry : frame_decoder.Toc()) {
JXL_RETURN_IF_ERROR(pos + toc_entry.size <= avail_in);
auto br = make_unique<BitReader>(
Span<const uint8_t>(next_in + pos, toc_entry.size));
section_info.emplace_back(
FrameDecoder::SectionInfo{br.get(), toc_entry.id});
section_closers.emplace_back(
make_unique<BitReaderScopedCloser>(br.get(), &close_ok));
section_readers.emplace_back(std::move(br));
pos += toc_entry.size;
}
// Skip over the to-be-decoded sections.
reader->SkipBits(kBitsPerByte * bytes_to_skip);
section_status.resize(section_info.size());
JXL_RETURN_IF_ERROR(frame_decoder.ProcessSections(
section_info.data(), section_info.size(), section_status.data()));
for (size_t i = 0; i < section_status.size(); i++) {
auto s = section_status[i];
if (s == FrameDecoder::kDone) {
processed_bytes += frame_decoder.SectionSizes()[i];
continue;
}
if (dparams.allow_more_progressive_steps && s == FrameDecoder::kPartial) {
continue;
}
if (dparams.max_downsampling > 1 && s == FrameDecoder::kSkipped) {
continue;
}
return JXL_FAILURE("Invalid section %" PRIuS " status: %d",
section_info[i].id, s);
JXL_RETURN_IF_ERROR(section_status[i] == FrameDecoder::kDone);
processed_bytes += frame_decoder.Toc()[i].size;
}
}
JXL_RETURN_IF_ERROR(close_ok);
JXL_RETURN_IF_ERROR(frame_decoder.FinalizeFrame());
decoded->SetDecodedBytes(processed_bytes);
return true;
}
Status FrameDecoder::InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded,
bool is_preview, bool allow_partial_frames,
bool allow_partial_dc_global,
bool output_needed) {
bool is_preview, bool output_needed) {
PROFILER_FUNC;
decoded_ = decoded;
JXL_ASSERT(is_finalized_);
allow_partial_frames_ = allow_partial_frames;
allow_partial_dc_global_ = allow_partial_dc_global;
// Reset the dequantization matrices to their default values.
dec_state_->shared_storage.matrices = DequantMatrices();
frame_header_.nonserialized_is_preview = is_preview;
size_t pos = br->TotalBitsConsumed() / kBitsPerByte;
Status have_frameheader =
br->TotalBytes() > pos && DecodeFrameHeader(br, &frame_header_);
JXL_RETURN_IF_ERROR(have_frameheader || allow_partial_frames);
if (!have_frameheader) {
if (dec_state_->shared_storage.dc_frames[0].xsize() > 0) {
// If we have a (partial) DC frame available, but we don't have the next
// frame header (so allow_partial_frames is true), then we'll assume the
// next frame uses that DC frame (which may not be true, e.g. there might
// first be a ReferenceOnly patch frame, but it's reasonable to assume
// that the DC frame is a good progressive preview)
frame_header_.flags |= FrameHeader::kUseDcFrame;
frame_header_.encoding = FrameEncoding::kVarDCT;
frame_header_.dc_level = 0;
} else
return JXL_FAILURE("Couldn't read frame header");
}
JXL_ASSERT(frame_header_.nonserialized_metadata != nullptr);
JXL_RETURN_IF_ERROR(ReadFrameHeader(br, &frame_header_));
frame_dim_ = frame_header_.ToFrameDimensions();
JXL_DEBUG_V(2, "FrameHeader: %s", frame_header_.DebugString().c_str());
const size_t num_passes = frame_header_.passes.num_passes;
const size_t xsize = frame_dim_.xsize;
const size_t ysize = frame_dim_.ysize;
const size_t num_groups = frame_dim_.num_groups;
// Check validity of frame dimensions.
JXL_RETURN_IF_ERROR(VerifyDimensions(constraints_, xsize, ysize));
// If the previous frame was not a kRegularFrame, `decoded` may have different
// dimensions; must reset to avoid errors.
decoded->RemoveColor();
@ -275,20 +161,31 @@ Status FrameDecoder::InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded,
}
// Read TOC.
uint64_t groups_total_size;
const bool has_ac_global = true;
const size_t toc_entries = NumTocEntries(num_groups, frame_dim_.num_dc_groups,
num_passes, has_ac_global);
JXL_RETURN_IF_ERROR(ReadGroupOffsets(toc_entries, br, &section_offsets_,
&section_sizes_, &groups_total_size) ||
allow_partial_frames);
std::vector<uint32_t> sizes;
std::vector<coeff_order_t> permutation;
JXL_RETURN_IF_ERROR(ReadToc(toc_entries, br, &sizes, &permutation));
bool have_permutation = !permutation.empty();
toc_.resize(toc_entries);
section_sizes_sum_ = 0;
for (size_t i = 0; i < toc_entries; ++i) {
toc_[i].size = sizes[i];
size_t index = have_permutation ? permutation[i] : i;
toc_[index].id = i;
if (section_sizes_sum_ + toc_[i].size < section_sizes_sum_) {
return JXL_FAILURE("group offset overflow");
}
section_sizes_sum_ += toc_[i].size;
}
JXL_DASSERT((br->TotalBitsConsumed() % kBitsPerByte) == 0);
const size_t group_codes_begin = br->TotalBitsConsumed() / kBitsPerByte;
JXL_DASSERT(!section_offsets_.empty());
JXL_DASSERT(!toc_.empty());
// Overflow check.
if (group_codes_begin + groups_total_size < group_codes_begin) {
if (group_codes_begin + section_sizes_sum_ < group_codes_begin) {
return JXL_FAILURE("Invalid group codes");
}
@ -347,9 +244,7 @@ Status FrameDecoder::InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded,
decoded_passes_per_ac_group_.clear();
decoded_passes_per_ac_group_.resize(frame_dim_.num_groups, 0);
processed_section_.clear();
processed_section_.resize(section_offsets_.size());
max_passes_ = frame_header_.passes.num_passes;
num_renders_ = 0;
processed_section_.resize(toc_.size());
allocated_ = false;
return true;
}
@ -382,14 +277,11 @@ Status FrameDecoder::ProcessDCGlobal(BitReader* br) {
if (shared.frame_header.flags & FrameHeader::kNoise) {
JXL_RETURN_IF_ERROR(DecodeNoise(br, &shared.image_features.noise_params));
}
if (!allow_partial_dc_global_ ||
br->TotalBitsConsumed() < br->TotalBytes() * kBitsPerByte) {
JXL_RETURN_IF_ERROR(dec_state_->shared_storage.matrices.DecodeDC(br));
JXL_RETURN_IF_ERROR(dec_state_->shared_storage.matrices.DecodeDC(br));
if (frame_header_.encoding == FrameEncoding::kVarDCT) {
JXL_RETURN_IF_ERROR(
jxl::DecodeGlobalDCInfo(br, decoded_->IsJPEG(), dec_state_, pool_));
}
if (frame_header_.encoding == FrameEncoding::kVarDCT) {
JXL_RETURN_IF_ERROR(
jxl::DecodeGlobalDCInfo(br, decoded_->IsJPEG(), dec_state_, pool_));
}
// Splines' draw cache uses the color correlation map.
if (shared.frame_header.flags & FrameHeader::kSplines) {
@ -398,7 +290,7 @@ Status FrameDecoder::ProcessDCGlobal(BitReader* br) {
dec_state_->shared->cmap));
}
Status dec_status = modular_frame_decoder_.DecodeGlobalInfo(
br, frame_header_, allow_partial_dc_global_);
br, frame_header_, /*allow_truncated_group=*/false);
if (dec_status.IsFatalError()) return dec_status;
if (dec_status) {
decoded_dc_global_ = true;
@ -420,7 +312,8 @@ Status FrameDecoder::ProcessDCGroup(size_t dc_group_id, BitReader* br) {
frame_dim_.dc_group_dim, frame_dim_.dc_group_dim);
JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeGroup(
mrect, br, 3, 1000, ModularStreamId::ModularDC(dc_group_id),
/*zerofill=*/false, nullptr, nullptr, nullptr, allow_partial_frames_));
/*zerofill=*/false, nullptr, nullptr,
/*allow_truncated=*/false));
if (frame_header_.encoding == FrameEncoding::kVarDCT) {
JXL_RETURN_IF_ERROR(
modular_frame_decoder_.DecodeAcMetadata(dc_group_id, br, dec_state_));
@ -562,6 +455,11 @@ Status FrameDecoder::ProcessACGroup(size_t ac_group_id,
const size_t gy = ac_group_id / frame_dim_.xsize_groups;
const size_t x = gx * group_dim;
const size_t y = gy * group_dim;
JXL_DEBUG_V(3,
"Processing AC group %" PRIuS "(%" PRIuS ",%" PRIuS
") group_dim: %" PRIuS " decoded passes: %u new passes: %" PRIuS,
ac_group_id, gx, gy, group_dim,
decoded_passes_per_ac_group_[ac_group_id], num_passes);
RenderPipelineInput render_pipeline_input =
dec_state_->render_pipeline->GetInputBuffers(ac_group_id, thread);
@ -580,23 +478,28 @@ Status FrameDecoder::ProcessACGroup(size_t ac_group_id,
// don't limit to image dimensions here (is done in DecodeGroup)
const Rect mrect(x, y, group_dim, group_dim);
for (size_t i = 0; i < frame_header_.passes.num_passes; i++) {
bool modular_ready = false;
size_t pass0 = decoded_passes_per_ac_group_[ac_group_id];
size_t pass1 =
force_draw ? frame_header_.passes.num_passes : pass0 + num_passes;
for (size_t i = pass0; i < pass1; ++i) {
int minShift, maxShift;
frame_header_.passes.GetDownsamplingBracket(i, minShift, maxShift);
if (i >= decoded_passes_per_ac_group_[ac_group_id] &&
i < decoded_passes_per_ac_group_[ac_group_id] + num_passes) {
bool modular_pass_ready = true;
if (i < pass0 + num_passes) {
JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeGroup(
mrect, br[i - decoded_passes_per_ac_group_[ac_group_id]], minShift,
maxShift, ModularStreamId::ModularAC(ac_group_id, i),
/*zerofill=*/false, dec_state_, &render_pipeline_input, decoded_,
allow_partial_frames_));
} else if (i >= decoded_passes_per_ac_group_[ac_group_id] + num_passes &&
force_draw) {
mrect, br[i - pass0], minShift, maxShift,
ModularStreamId::ModularAC(ac_group_id, i),
/*zerofill=*/false, dec_state_, &render_pipeline_input,
/*allow_truncated=*/false, &modular_pass_ready));
} else {
JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeGroup(
mrect, nullptr, minShift, maxShift,
ModularStreamId::ModularAC(ac_group_id, i), /*zerofill=*/true,
dec_state_, &render_pipeline_input, decoded_, allow_partial_frames_));
dec_state_, &render_pipeline_input,
/*allow_truncated=*/false, &modular_pass_ready));
}
if (modular_pass_ready) modular_ready = true;
}
decoded_passes_per_ac_group_[ac_group_id] += num_passes;
@ -627,19 +530,21 @@ Status FrameDecoder::ProcessACGroup(size_t ac_group_id,
}
}
if (!modular_frame_decoder_.UsesFullImage() && !decoded_->IsJPEG() &&
should_run_pipeline) {
render_pipeline_input.Done();
if (!modular_frame_decoder_.UsesFullImage() && !decoded_->IsJPEG()) {
if (should_run_pipeline && modular_ready) {
render_pipeline_input.Done();
} else if (force_draw) {
return JXL_FAILURE("Modular group decoding failed.");
}
}
return true;
}
void FrameDecoder::MarkSections(const SectionInfo* sections, size_t num,
SectionStatus* section_status) {
num_sections_done_ = num;
num_sections_done_ += num;
for (size_t i = 0; i < num; i++) {
if (section_status[i] == SectionStatus::kSkipped ||
section_status[i] == SectionStatus::kPartial) {
if (section_status[i] != SectionStatus::kDone) {
processed_section_[sections[i].id] = false;
num_sections_done_--;
}
@ -656,7 +561,9 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
std::vector<std::vector<size_t>> ac_group_sec(
frame_dim_.num_groups,
std::vector<size_t>(frame_header_.passes.num_passes, num));
std::vector<size_t> num_ac_passes(frame_dim_.num_groups);
// This keeps track of the number of ac passes we want to process during this
// call of ProcessSections.
std::vector<size_t> desired_num_ac_passes(frame_dim_.num_groups);
bool single_section =
frame_dim_.num_groups == 1 && frame_header_.passes.num_passes == 1;
if (single_section) {
@ -666,7 +573,7 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
processed_section_[0] = true;
ac_group_sec[0].resize(1);
dc_global_sec = ac_global_sec = dc_group_sec[0] = ac_group_sec[0][0] = 0;
num_ac_passes[0] = 1;
desired_num_ac_passes[0] = 1;
} else {
section_status[0] = SectionStatus::kDuplicate;
}
@ -691,9 +598,6 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
if (acp >= frame_header_.passes.num_passes) {
return JXL_FAILURE("Invalid section ID");
}
if (acp >= max_passes_) {
continue;
}
ac_group_sec[acg][acp] = i;
}
processed_section_[sections[i].id] = true;
@ -701,12 +605,14 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
// Count number of new passes per group.
for (size_t g = 0; g < ac_group_sec.size(); g++) {
size_t j = 0;
for (; j + decoded_passes_per_ac_group_[g] < max_passes_; j++) {
for (; j + decoded_passes_per_ac_group_[g] <
frame_header_.passes.num_passes;
j++) {
if (ac_group_sec[g][j + decoded_passes_per_ac_group_[g]] == num) {
break;
}
}
num_ac_passes[g] = j;
desired_num_ac_passes[g] = j;
}
}
if (dc_global_sec != num) {
@ -747,33 +653,9 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
dec_state_->PreparePipeline(decoded_, pipeline_options));
FinalizeDC();
JXL_RETURN_IF_ERROR(AllocateOutput());
if (pause_at_progressive_ && !single_section) {
bool can_return_dc = true;
if (single_section) {
// If there's only one group and one pass, there is no separate section
// for DC and the entire full resolution image is available at once.
can_return_dc = false;
}
if (!decoded_->metadata()->extra_channel_info.empty()) {
// If extra channels are encoded with modular without squeeze, they
// don't support DC. If the are encoded with squeeze, DC works in theory
// but the implementation may not yet correctly support this for Flush.
// Therefore, can't correctly pause for a progressive step if there is
// an extra channel (including alpha channel)
can_return_dc = false;
}
if (frame_header_.encoding != FrameEncoding::kVarDCT) {
// DC is not guaranteed to be available in modular mode and may be a
// black image. If squeeze is used, it may be available depending on the
// current implementation.
// TODO(lode): do return DC if it's known that flushing at this point
// will produce a valid 1/8th downscaled image with modular encoding.
can_return_dc = false;
}
if (can_return_dc) {
MarkSections(sections, num, section_status);
return true;
}
if (progressive_detail_ >= JxlProgressiveDetail::kDC) {
MarkSections(sections, num, section_status);
return true;
}
}
@ -782,13 +664,22 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
section_status[ac_global_sec] = SectionStatus::kDone;
}
if (progressive_detail_ >= JxlProgressiveDetail::kLastPasses) {
// Mark that we only want the next progression pass.
size_t target_complete_passes = NextNumPassesToPause();
for (size_t i = 0; i < ac_group_sec.size(); i++) {
desired_num_ac_passes[i] =
std::min(desired_num_ac_passes[i],
target_complete_passes - decoded_passes_per_ac_group_[i]);
}
}
if (decoded_ac_global_) {
// Mark all the AC groups that we received as not complete yet.
for (size_t i = 0; i < ac_group_sec.size(); i++) {
if (num_ac_passes[i] == 0 && !modular_frame_decoder_.UsesFullImage()) {
continue;
if (desired_num_ac_passes[i] != 0) {
dec_state_->render_pipeline->ClearDone(i);
}
dec_state_->render_pipeline->ClearDone(i);
}
JXL_RETURN_IF_ERROR(RunOnPool(
@ -797,24 +688,25 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
return PrepareStorage(num_threads,
decoded_passes_per_ac_group_.size());
},
[this, &ac_group_sec, &num_ac_passes, &num, &sections, &section_status,
&has_error](size_t g, size_t thread) {
if (num_ac_passes[g] == 0) { // no new AC pass, nothing to do.
[this, &ac_group_sec, &desired_num_ac_passes, &num, &sections,
&section_status, &has_error](size_t g, size_t thread) {
if (desired_num_ac_passes[g] == 0) {
// no new AC pass, nothing to do
return;
}
(void)num;
size_t first_pass = decoded_passes_per_ac_group_[g];
BitReader* JXL_RESTRICT readers[kMaxNumPasses];
for (size_t i = 0; i < num_ac_passes[g]; i++) {
for (size_t i = 0; i < desired_num_ac_passes[g]; i++) {
JXL_ASSERT(ac_group_sec[g][first_pass + i] != num);
readers[i] = sections[ac_group_sec[g][first_pass + i]].br;
}
if (!ProcessACGroup(g, readers, num_ac_passes[g],
if (!ProcessACGroup(g, readers, desired_num_ac_passes[g],
GetStorageLocation(thread, g),
/*force_draw=*/false, /*dc_only=*/false)) {
has_error = true;
} else {
for (size_t i = 0; i < num_ac_passes[g]; i++) {
for (size_t i = 0; i < desired_num_ac_passes[g]; i++) {
section_status[ac_group_sec[g][first_pass + i]] =
SectionStatus::kDone;
}
@ -857,9 +749,9 @@ Status FrameDecoder::Flush() {
// We don't have all AC yet: force a draw of all the missing areas.
// Mark all sections as not complete.
for (size_t i = 0; i < decoded_passes_per_ac_group_.size(); i++) {
if (decoded_passes_per_ac_group_[i] == frame_header_.passes.num_passes)
continue;
dec_state_->render_pipeline->ClearDone(i);
if (decoded_passes_per_ac_group_[i] < frame_header_.passes.num_passes) {
dec_state_->render_pipeline->ClearDone(i);
}
}
std::atomic<bool> has_error{false};
JXL_RETURN_IF_ERROR(RunOnPool(
@ -887,10 +779,9 @@ Status FrameDecoder::Flush() {
}
// undo global modular transforms and copy int pixel buffers to float ones
JXL_RETURN_IF_ERROR(modular_frame_decoder_.FinalizeDecoding(
dec_state_, pool_, decoded_, is_finalized_));
JXL_RETURN_IF_ERROR(modular_frame_decoder_.FinalizeDecoding(dec_state_, pool_,
is_finalized_));
num_renders_++;
return true;
}
@ -913,7 +804,7 @@ bool FrameDecoder::HasEverything() const {
if (!have_dc_group) return false;
}
for (auto& nb_passes : decoded_passes_per_ac_group_) {
if (nb_passes < max_passes_) return false;
if (nb_passes < frame_header_.passes.num_passes) return false;
}
return true;
}
@ -967,18 +858,9 @@ Status FrameDecoder::FinalizeFrame() {
return true;
}
if (!finalized_dc_) {
// We don't have all of DC: EPF might not behave correctly (and is not
// particularly useful anyway on upsampling results), so we disable it.
dec_state_->shared_storage.frame_header.loop_filter.epf_iters = 0;
}
if (!HasEverything() && !allow_partial_frames_) {
return JXL_FAILURE(
"FinalizeFrame called before the frame was fully decoded");
}
if (!finalized_dc_) {
JXL_ASSERT(allow_partial_frames_);
JXL_RETURN_IF_ERROR(AllocateOutput());
// We don't have all of DC, and render pipeline is not created yet, so we
// can not call Flush() yet.
return JXL_FAILURE("FinalizeFrame called before DC was fully decoded");
}
JXL_RETURN_IF_ERROR(Flush());

View file

@ -9,6 +9,7 @@
#include <stdint.h>
#include "jxl/decode.h"
#include "jxl/types.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/span.h"
@ -19,31 +20,20 @@
#include "lib/jxl/dec_bit_reader.h"
#include "lib/jxl/dec_cache.h"
#include "lib/jxl/dec_modular.h"
#include "lib/jxl/dec_params.h"
#include "lib/jxl/frame_header.h"
#include "lib/jxl/headers.h"
#include "lib/jxl/image_bundle.h"
namespace jxl {
// TODO(veluca): remove DecodeFrameHeader once the API migrates to FrameDecoder.
// `frame_header` must have nonserialized_metadata and
// nonserialized_is_preview set.
Status DecodeFrameHeader(BitReader* JXL_RESTRICT reader,
FrameHeader* JXL_RESTRICT frame_header);
// Decodes a frame. Groups may be processed in parallel by `pool`.
// See DecodeFile for explanation of c_decoded.
// `io` is only used for reading maximum image size. Also updates
// `dec_state` with the new frame header.
// `metadata` is the metadata that applies to all frames of the codestream
// `decoded->metadata` must already be set and must match metadata.m.
Status DecodeFrame(const DecompressParams& dparams,
PassesDecoderState* dec_state, ThreadPool* JXL_RESTRICT pool,
BitReader* JXL_RESTRICT reader, ImageBundle* decoded,
const CodecMetadata& metadata,
const SizeConstraints* constraints, bool is_preview = false);
// Used in the encoder to model decoder behaviour, and in tests.
Status DecodeFrame(PassesDecoderState* dec_state, ThreadPool* JXL_RESTRICT pool,
const uint8_t* next_in, size_t avail_in,
ImageBundle* decoded, const CodecMetadata& metadata,
bool use_slow_rendering_pipeline = false);
// TODO(veluca): implement "forced drawing".
class FrameDecoder {
@ -56,28 +46,25 @@ class FrameDecoder {
frame_header_(&metadata),
use_slow_rendering_pipeline_(use_slow_rendering_pipeline) {}
// `constraints` must outlive the FrameDecoder if not null, or stay alive
// until the next call to SetFrameSizeLimits.
void SetFrameSizeLimits(const SizeConstraints* constraints) {
constraints_ = constraints;
}
void SetRenderSpotcolors(bool rsc) { render_spotcolors_ = rsc; }
void SetCoalescing(bool c) { coalescing_ = c; }
// Read FrameHeader and table of contents from the given BitReader.
// Also checks frame dimensions for their limits, and sets the output
// image buffer.
// TODO(veluca): remove the `allow_partial_frames` flag - this should be moved
// on callers.
Status InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded,
bool is_preview, bool allow_partial_frames,
bool allow_partial_dc_global, bool output_needed);
bool is_preview, bool output_needed);
struct SectionInfo {
BitReader* JXL_RESTRICT br;
size_t id;
};
struct TocEntry {
size_t size;
size_t id;
};
enum SectionStatus {
// Processed correctly.
kDone = 0,
@ -118,22 +105,20 @@ class FrameDecoder {
// soon as the frame header is known.
static int SavedAs(const FrameHeader& header);
// Returns offset of this section after the end of the TOC. The end of the TOC
// is the byte position of the bit reader after InitFrame was called.
const std::vector<uint64_t>& SectionOffsets() const {
return section_offsets_;
}
const std::vector<uint32_t>& SectionSizes() const { return section_sizes_; }
size_t NumSections() const { return section_sizes_.size(); }
uint64_t SumSectionSizes() const { return section_sizes_sum_; }
const std::vector<TocEntry>& Toc() const { return toc_; }
// TODO(veluca): remove once we remove --downsampling flag.
void SetMaxPasses(size_t max_passes) { max_passes_ = max_passes; }
const FrameHeader& GetFrameHeader() const { return frame_header_; }
// Returns whether a DC image has been decoded, accessible at low resolution
// at passes.shared_storage.dc_storage
bool HasDecodedDC() const { return finalized_dc_; }
bool HasDecodedAll() const { return NumSections() == num_sections_done_; }
bool HasDecodedAll() const { return toc_.size() == num_sections_done_; }
size_t NumCompletePasses() const {
return *std::min_element(decoded_passes_per_ac_group_.begin(),
decoded_passes_per_ac_group_.end());
};
// If enabled, ProcessSections will stop and return true when the DC
// sections have been processed, instead of starting the AC sections. This
@ -141,7 +126,59 @@ class FrameDecoder {
// 1/8th*1/8th resolution image). The return value of true then does not mean
// all sections have been processed, use HasDecodedDC and HasDecodedAll
// to check the true finished state.
void SetPauseAtProgressive() { pause_at_progressive_ = true; }
// Returns the progressive detail that will be effective for the frame.
JxlProgressiveDetail SetPauseAtProgressive(JxlProgressiveDetail prog_detail) {
bool single_section =
frame_dim_.num_groups == 1 && frame_header_.passes.num_passes == 1;
if (frame_header_.frame_type != kSkipProgressive &&
// If there's only one group and one pass, there is no separate section
// for DC and the entire full resolution image is available at once.
!single_section &&
// If extra channels are encoded with modular without squeeze, they
// don't support DC. If the are encoded with squeeze, DC works in theory
// but the implementation may not yet correctly support this for Flush.
// Therefore, can't correctly pause for a progressive step if there is
// an extra channel (including alpha channel)
// TOOD(firsching): Check if this is still the case.
decoded_->metadata()->extra_channel_info.empty() &&
// DC is not guaranteed to be available in modular mode and may be a
// black image. If squeeze is used, it may be available depending on the
// current implementation.
// TODO(lode): do return DC if it's known that flushing at this point
// will produce a valid 1/8th downscaled image with modular encoding.
frame_header_.encoding == FrameEncoding::kVarDCT) {
progressive_detail_ = prog_detail;
} else {
progressive_detail_ = JxlProgressiveDetail::kFrames;
}
if (progressive_detail_ >= JxlProgressiveDetail::kPasses) {
for (size_t i = 1; i < frame_header_.passes.num_passes; ++i) {
passes_to_pause_.push_back(i);
}
} else if (progressive_detail_ >= JxlProgressiveDetail::kLastPasses) {
for (size_t i = 0; i < frame_header_.passes.num_downsample; ++i) {
passes_to_pause_.push_back(frame_header_.passes.last_pass[i] + 1);
}
// The format does not guarantee that these values are sorted.
std::sort(passes_to_pause_.begin(), passes_to_pause_.end());
}
return progressive_detail_;
}
size_t NextNumPassesToPause() const {
auto it = std::upper_bound(passes_to_pause_.begin(), passes_to_pause_.end(),
NumCompletePasses());
return (it != passes_to_pause_.end() ? *it
: std::numeric_limits<size_t>::max());
}
void MaybeSetUnpremultiplyAlpha(bool unpremul_alpha) {
const jxl::ExtraChannelInfo* alpha =
decoded_->metadata()->Find(jxl::ExtraChannel::kAlpha);
if (alpha && alpha->alpha_associated && unpremul_alpha) {
dec_state_->unpremul_alpha = true;
}
}
// Sets the buffer to which uint8 sRGB pixels will be decoded. This is not
// supported for all images. If it succeeds, HasRGBBuffer() will return true.
@ -156,7 +193,9 @@ class FrameDecoder {
// orientation. When outputting to the ImageBundle, no orientation is undone.
void MaybeSetRGB8OutputBuffer(uint8_t* rgb_output, size_t stride,
bool is_rgba, bool undo_orientation) const {
if (!CanDoLowMemoryPath(undo_orientation)) return;
if (!CanDoLowMemoryPath(undo_orientation) || dec_state_->unpremul_alpha) {
return;
}
dec_state_->rgb_output = rgb_output;
dec_state_->rgb_output_is_rgba = is_rgba;
dec_state_->rgb_stride = stride;
@ -165,6 +204,8 @@ class FrameDecoder {
if (decoded_->metadata()->xyb_encoded &&
dec_state_->output_encoding_info.color_encoding.IsSRGB() &&
dec_state_->output_encoding_info.all_default_opsin &&
dec_state_->output_encoding_info.desired_intensity_target ==
dec_state_->output_encoding_info.orig_intensity_target &&
HasFastXYBTosRGB8() && frame_header_.needs_color_transform()) {
dec_state_->fast_xyb_srgb8_conversion = true;
}
@ -183,7 +224,7 @@ class FrameDecoder {
// results in not setting the buffer if the image has a non-identity EXIF
// orientation. When outputting to the ImageBundle, no orientation is undone.
void MaybeSetFloatCallback(const PixelCallback& pixel_callback, bool is_rgba,
bool undo_orientation) const {
bool unpremul_alpha, bool undo_orientation) const {
if (!CanDoLowMemoryPath(undo_orientation)) return;
dec_state_->pixel_callback = pixel_callback;
dec_state_->rgb_output_is_rgba = is_rgba;
@ -221,11 +262,12 @@ class FrameDecoder {
group_dec_caches_.resize(storage_size);
}
use_task_id_ = num_threads > num_tasks;
bool use_group_ids = (modular_frame_decoder_.UsesFullImage() &&
(frame_header_.encoding == FrameEncoding::kVarDCT ||
(frame_header_.flags & FrameHeader::kNoise)));
if (dec_state_->render_pipeline) {
JXL_RETURN_IF_ERROR(dec_state_->render_pipeline->PrepareForThreads(
storage_size,
/*use_group_ids=*/modular_frame_decoder_.UsesFullImage() &&
frame_header_.encoding == FrameEncoding::kVarDCT));
storage_size, use_group_ids));
}
return true;
}
@ -248,16 +290,13 @@ class FrameDecoder {
PassesDecoderState* dec_state_;
ThreadPool* pool_;
std::vector<uint64_t> section_offsets_;
std::vector<uint32_t> section_sizes_;
size_t max_passes_;
std::vector<TocEntry> toc_;
uint64_t section_sizes_sum_;
// TODO(veluca): figure out the duplication between these and dec_state_.
FrameHeader frame_header_;
FrameDimensions frame_dim_;
ImageBundle* decoded_;
ModularFrameDecoder modular_frame_decoder_;
bool allow_partial_frames_;
bool allow_partial_dc_global_;
bool render_spotcolors_ = true;
bool coalescing_ = true;
@ -270,14 +309,10 @@ class FrameDecoder {
bool finalized_dc_ = true;
size_t num_sections_done_ = 0;
bool is_finalized_ = true;
size_t num_renders_ = 0;
bool allocated_ = false;
std::vector<GroupDecCache> group_dec_caches_;
// Frame size limits.
const SizeConstraints* constraints_ = nullptr;
// Whether or not the task id should be used for storage indexing, instead of
// the thread id.
bool use_task_id_ = false;
@ -285,7 +320,10 @@ class FrameDecoder {
// Testing setting: whether or not to use the slow rendering pipeline.
bool use_slow_rendering_pipeline_;
bool pause_at_progressive_ = false;
JxlProgressiveDetail progressive_detail_ = kFrames;
// Number of completed passes where section decoding should pause.
// Used for progressive details at least kLastPasses.
std::vector<int> passes_to_pause_;
};
} // namespace jxl

View file

@ -241,8 +241,10 @@ Status DecodeGroupImpl(GetBlock* JXL_RESTRICT get_block,
r[i] =
Rect(block_rect.x0() >> hshift[i], block_rect.y0() >> vshift[i],
block_rect.xsize() >> hshift[i], block_rect.ysize() >> vshift[i]);
JXL_ASSERT(r[i].IsInside({0, 0, dec_state->shared->dc->Plane(i).xsize(),
dec_state->shared->dc->Plane(i).ysize()}));
if (!r[i].IsInside({0, 0, dec_state->shared->dc->Plane(i).xsize(),
dec_state->shared->dc->Plane(i).ysize()})) {
return JXL_FAILURE("Frame dimensions are too big for the image.");
}
}
for (size_t by = 0; by < ysize_blocks; ++by) {

View file

@ -21,7 +21,6 @@
#include "lib/jxl/dec_ans.h"
#include "lib/jxl/dec_bit_reader.h"
#include "lib/jxl/dec_cache.h"
#include "lib/jxl/dec_params.h"
#include "lib/jxl/frame_header.h"
#include "lib/jxl/image.h"
#include "lib/jxl/quantizer.h"

View file

@ -7,6 +7,8 @@
#include <stdint.h>
#include <atomic>
#include <sstream>
#include <vector>
#include "lib/jxl/frame_header.h"
@ -149,6 +151,28 @@ void int_to_float(const pixel_type* const JXL_RESTRICT row_in,
}
}
std::string ModularStreamId::DebugString() const {
std::ostringstream os;
os << (kind == kGlobalData ? "ModularGlobal"
: kind == kVarDCTDC ? "VarDCTDC"
: kind == kModularDC ? "ModularDC"
: kind == kACMetadata ? "ACMeta"
: kind == kQuantTable ? "QuantTable"
: kind == kModularAC ? "ModularAC"
: "");
if (kind == kVarDCTDC || kind == kModularDC || kind == kACMetadata ||
kind == kModularAC) {
os << " group " << group_id;
}
if (kind == kModularAC) {
os << " pass " << pass_id;
}
if (kind == kQuantTable) {
os << " " << quant_table_id;
}
return os.str();
}
Status ModularFrameDecoder::DecodeGlobalInfo(BitReader* reader,
const FrameHeader& frame_header,
bool allow_truncated_group) {
@ -218,6 +242,8 @@ Status ModularFrameDecoder::DecodeGlobalInfo(BitReader* reader,
all_same_shift = false;
}
JXL_DEBUG_V(6, "DecodeGlobalInfo: full_image (w/o transforms) %s",
gi.DebugString().c_str());
ModularOptions options;
options.max_chan_size = frame_dim.group_dim;
options.group_dim = frame_dim.group_dim;
@ -248,12 +274,15 @@ Status ModularFrameDecoder::DecodeGlobalInfo(BitReader* reader,
}
}
full_image = std::move(gi);
JXL_DEBUG_V(6, "DecodeGlobalInfo: full_image (with transforms) %s",
full_image.DebugString().c_str());
return dec_status;
}
void ModularFrameDecoder::MaybeDropFullImage() {
if (full_image.transform.empty() && !have_something && all_same_shift) {
use_full_image = false;
JXL_DEBUG_V(6, "Dropping full image");
for (auto& ch : full_image.channel) {
// keep metadata on channels around, but dealloc their planes
ch.plane = Plane<pixel_type>();
@ -264,8 +293,11 @@ void ModularFrameDecoder::MaybeDropFullImage() {
Status ModularFrameDecoder::DecodeGroup(
const Rect& rect, BitReader* reader, int minShift, int maxShift,
const ModularStreamId& stream, bool zerofill, PassesDecoderState* dec_state,
RenderPipelineInput* render_pipeline_input, ImageBundle* output,
bool allow_truncated) {
RenderPipelineInput* render_pipeline_input, bool allow_truncated,
bool* should_run_pipeline) {
JXL_DEBUG_V(6, "Decoding %s with rect %s and shift bracket %d..%d %s",
stream.DebugString().c_str(), Description(rect).c_str(), minShift,
maxShift, zerofill ? "using zerofill" : "");
JXL_DASSERT(stream.kind == ModularStreamId::kModularDC ||
stream.kind == ModularStreamId::kModularAC);
const size_t xsize = rect.xsize();
@ -302,7 +334,19 @@ Status ModularFrameDecoder::DecodeGroup(
if (zerofill && use_full_image) return true;
// Return early if there's nothing to decode. Otherwise there might be
// problems later (in ModularImageToDecodedRect).
if (gi.channel.empty()) return true;
if (gi.channel.empty()) {
if (dec_state && should_run_pipeline) {
const auto& frame_header = dec_state->shared->frame_header;
const auto* metadata = frame_header.nonserialized_metadata;
if (do_color || metadata->m.num_extra_channels > 0) {
// Signal to FrameDecoder that we do not have some of the required input
// for the render pipeline.
*should_run_pipeline = false;
}
}
JXL_DEBUG_V(6, "Nothing to decode, returning early.");
return true;
}
ModularOptions options;
if (!zerofill) {
auto status = ModularGenericDecompress(
@ -407,11 +451,11 @@ Status ModularFrameDecoder::DecodeAcMetadata(size_t group_id, BitReader* reader,
uint32_t local_used_acs = 0;
for (size_t iy = 0; iy < r.ysize(); iy++) {
size_t y = r.y0() + iy;
int* row_qf = r.Row(&dec_state->shared_storage.raw_quant_field, iy);
int32_t* row_qf = r.Row(&dec_state->shared_storage.raw_quant_field, iy);
uint8_t* row_epf = r.Row(&dec_state->shared_storage.epf_sharpness, iy);
int* row_in_1 = image.channel[2].plane.Row(0);
int* row_in_2 = image.channel[2].plane.Row(1);
int* row_in_3 = image.channel[3].plane.Row(iy);
int32_t* row_in_1 = image.channel[2].plane.Row(0);
int32_t* row_in_2 = image.channel[2].plane.Row(1);
int32_t* row_in_3 = image.channel[3].plane.Row(iy);
for (size_t ix = 0; ix < r.xsize(); ix++) {
size_t x = r.x0() + ix;
int sharpness = row_in_3[ix];
@ -448,8 +492,8 @@ Status ModularFrameDecoder::DecodeAcMetadata(size_t group_id, BitReader* reader,
}
JXL_RETURN_IF_ERROR(
ac_strategy.SetNoBoundsCheck(x, y, AcStrategy::Type(row_in_1[num])));
row_qf[ix] =
1 + std::max(0, std::min(Quantizer::kQuantMax - 1, row_in_2[num]));
row_qf[ix] = 1 + std::max<int32_t>(0, std::min(Quantizer::kQuantMax - 1,
row_in_2[num]));
num++;
}
}
@ -605,7 +649,13 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
DivCeil(modular_rect.xsize(), 1 << ch_in.hshift),
DivCeil(modular_rect.ysize(), 1 << ch_in.vshift));
mr = mr.Crop(ch_in.plane);
if (r.ysize() != mr.ysize() || r.xsize() != mr.xsize()) {
return JXL_FAILURE("Dimension mismatch: trying to fit a %" PRIuS
"x%" PRIuS
" modular channel into "
"a %" PRIuS "x%" PRIuS " rect",
mr.xsize(), mr.ysize(), r.xsize(), r.ysize());
}
for (size_t y = 0; y < r.ysize(); ++y) {
float* const JXL_RESTRICT row_out =
r.Row(render_pipeline_input.GetBuffer(3 + ec).first, y);
@ -627,31 +677,35 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
Status ModularFrameDecoder::FinalizeDecoding(PassesDecoderState* dec_state,
jxl::ThreadPool* pool,
ImageBundle* output,
bool inplace) {
if (!use_full_image) return true;
Image gi = (inplace ? std::move(full_image) : full_image.clone());
size_t xsize = gi.w;
size_t ysize = gi.h;
JXL_DEBUG_V(3, "Finalizing decoding for modular image: %s",
gi.DebugString().c_str());
// Don't use threads if total image size is smaller than a group
if (xsize * ysize < frame_dim.group_dim * frame_dim.group_dim) pool = nullptr;
// Undo the global transforms
gi.undo_transforms(global_header.wp_header, pool);
for (auto t : global_transform) {
JXL_RETURN_IF_ERROR(t.Inverse(gi, global_header.wp_header));
}
JXL_DASSERT(global_transform.empty());
if (gi.error) return JXL_FAILURE("Undoing transforms failed");
for (size_t i = 0; i < dec_state->shared->frame_dim.num_groups; i++) {
dec_state->render_pipeline->ClearDone(i);
}
std::atomic<bool> has_error{false};
JXL_RETURN_IF_ERROR(RunOnPool(
pool, 0, dec_state->shared->frame_dim.num_groups,
[&](size_t num_threads) {
return dec_state->render_pipeline->PrepareForThreads(
num_threads,
/*use_group_ids=*/dec_state->shared->frame_header.encoding ==
FrameEncoding::kVarDCT);
const auto& frame_header = dec_state->shared->frame_header;
bool use_group_ids = (frame_header.encoding == FrameEncoding::kVarDCT ||
(frame_header.flags & FrameHeader::kNoise));
return dec_state->render_pipeline->PrepareForThreads(num_threads,
use_group_ids);
},
[&](const uint32_t group, size_t thread_id) {
RenderPipelineInput input =
@ -701,7 +755,7 @@ Status ModularFrameDecoder::DecodeQuantTable(
encoding->qraw.qtable->resize(required_size_x * required_size_y * 3);
for (size_t c = 0; c < 3; c++) {
for (size_t y = 0; y < required_size_y; y++) {
int* JXL_RESTRICT row = image.channel[c].Row(y);
int32_t* JXL_RESTRICT row = image.channel[c].Row(y);
for (size_t x = 0; x < required_size_x; x++) {
(*encoding->qraw.qtable)[c * required_size_x * required_size_y +
y * required_size_x + x] = row[x];

View file

@ -8,15 +8,15 @@
#include <stddef.h>
#include <string>
#include "lib/jxl/aux_out_fwd.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/dec_bit_reader.h"
#include "lib/jxl/dec_cache.h"
#include "lib/jxl/dec_params.h"
#include "lib/jxl/frame_header.h"
#include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/modular/encoding/encoding.h"
#include "lib/jxl/modular/modular_image.h"
@ -82,6 +82,7 @@ struct ModularStreamId {
static size_t Num(const FrameDimensions& frame_dim, size_t passes) {
return ModularAC(0, passes).ID(frame_dim);
}
std::string DebugString() const;
};
class ModularFrameDecoder {
@ -93,7 +94,7 @@ class ModularFrameDecoder {
int maxShift, const ModularStreamId& stream, bool zerofill,
PassesDecoderState* dec_state,
RenderPipelineInput* render_pipeline_input,
ImageBundle* output, bool allow_truncated);
bool allow_truncated, bool* should_run_pipeline = nullptr);
// Decodes a VarDCT DC group (`group_id`) from the given `reader`.
Status DecodeVarDCTDC(size_t group_id, BitReader* reader,
PassesDecoderState* dec_state);
@ -111,7 +112,7 @@ class ModularFrameDecoder {
// if it is false, it can be called multiple times (e.g. for progressive
// steps)
Status FinalizeDecoding(PassesDecoderState* dec_state, jxl::ThreadPool* pool,
ImageBundle* output, bool inplace);
bool inplace);
bool have_dc() const { return have_something; }
void MaybeDropFullImage();
bool UsesFullImage() const { return use_full_image; }

View file

@ -1,52 +0,0 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#ifndef LIB_JXL_DEC_PARAMS_H_
#define LIB_JXL_DEC_PARAMS_H_
// Parameters and flags that govern JXL decompression.
#include <stddef.h>
#include <stdint.h>
#include <limits>
#include "lib/jxl/base/override.h"
namespace jxl {
struct DecompressParams {
// If true, checks at the end of decoding that all of the compressed data
// was consumed by the decoder.
bool check_decompressed_size = true;
// If true, skip dequant and iDCT and decode to JPEG (only if possible)
bool keep_dct = false;
// If true, render spot colors (otherwise only returned as extra channels)
bool render_spotcolors = true;
// If true, coalesce frames (otherwise return unblended frames)
bool coalescing = true;
// How many passes to decode at most. By default, decode everything.
uint32_t max_passes = std::numeric_limits<uint32_t>::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;
// Try to decode as much as possible of a truncated codestream, but only whole
// sections at a time.
bool allow_partial_files = false;
// Allow even more progression.
bool allow_more_progressive_steps = false;
// Internal test-only setting: whether or not to use the slow rendering
// pipeline.
bool use_slow_render_pipeline = false;
};
} // namespace jxl
#endif // LIB_JXL_DEC_PARAMS_H_

Some files were not shown because too many files have changed in this diff Show more