зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1754067
- Update libjxl to 4322679b1c418addc2284c5ea84fc2c3935b4a75; r=saschanaz
Differential Revision: https://phabricator.services.mozilla.com/D138418
This commit is contained in:
Родитель
79d6ccf336
Коммит
8b8d16f20c
|
@ -79,14 +79,16 @@ SOURCES += [
|
|||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/low_memory_render_pipeline.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_blending.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_chroma_upsampling.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.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_upsampling.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write_to_ib.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_xyb.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_ycbcr.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/splines.cc",
|
||||
|
|
|
@ -1,36 +1,19 @@
|
|||
# Version of this schema
|
||||
schema: 1
|
||||
|
||||
bugzilla:
|
||||
# Bugzilla product and component for this directory and subdirectories
|
||||
product: Core
|
||||
component: "ImageLib"
|
||||
|
||||
# Document the source of externally hosted code
|
||||
origin:
|
||||
|
||||
# Short name of the package/library
|
||||
name: libjxl
|
||||
|
||||
description: JPEG XL image format reference implementation
|
||||
|
||||
# Full URL for the package's homepage/etc
|
||||
# Usually different from repository url
|
||||
url: https://github.com/libjxl/libjxl
|
||||
|
||||
# Human-readable identifier for this version/release
|
||||
# Generally "version NNN", "tag SSS", "bookmark SSS"
|
||||
release: commit 9a74bd70b7932750deb78a8aebd6e041ce7f8b01 (2021-12-28T23:30:10Z).
|
||||
release: commit 4322679b1c418addc2284c5ea84fc2c3935b4a75 (2022-02-07T20:56:39Z).
|
||||
|
||||
# Revision to pull in
|
||||
# Must be a long or short commit SHA (long preferred)
|
||||
# NOTE(krosylight): Update highway together when updating this!
|
||||
revision: 9a74bd70b7932750deb78a8aebd6e041ce7f8b01
|
||||
revision: 4322679b1c418addc2284c5ea84fc2c3935b4a75
|
||||
|
||||
# The package's license, where possible using the mnemonic from
|
||||
# https://spdx.org/licenses/
|
||||
# Multiple licenses can be specified (as a YAML list)
|
||||
# A "LICENSE" file must exist containing the full license text
|
||||
license: Apache-2.0
|
||||
|
||||
license-file: LICENSE
|
||||
|
@ -49,10 +32,6 @@ vendoring:
|
|||
vendor-directory: third_party/jpeg-xl
|
||||
|
||||
exclude:
|
||||
- doc/
|
||||
- third_party/testdata/
|
||||
- tools/
|
||||
|
||||
|
||||
|
||||
|
||||
- doc
|
||||
- third_party/testdata
|
||||
- tools
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots or example input/output images to help explain your problem.
|
||||
|
||||
**Environment**
|
||||
- OS: [e.g. Windows]
|
||||
- Compiler version: [e.g. clang 11.0.1]
|
||||
- CPU type: [e.g. x86_64]
|
||||
- cjxl/djxl version string: [e.g. cjxl [v0.3.7 | SIMD supported: SSE4,Scalar]]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
|
@ -0,0 +1,548 @@
|
|||
# Copyright (c) the JPEG XL Project 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
|
||||
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:
|
||||
ubuntu_build:
|
||||
name: Ubuntu Build ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os || 'ubuntu-latest' }}
|
||||
strategy:
|
||||
matrix:
|
||||
# We have one job per "name" in the matrix. Attributes are set on the
|
||||
# specific job names.
|
||||
name: [release, debug, asan, msan, scalar]
|
||||
include:
|
||||
- name: release
|
||||
test_in_pr: true
|
||||
# Track static stack size on build and check it doesn't exceed 3 kB.
|
||||
env_stack_size: 1
|
||||
max_stack: 3000
|
||||
# Conformance tooling test requires numpy.
|
||||
apt_pkgs: python3-numpy
|
||||
- name: lowprecision
|
||||
mode: release
|
||||
test_in_pr: true
|
||||
cmake_args: -DCMAKE_CXX_FLAGS=-DJXL_HIGH_PRECISION=0
|
||||
- name: debug
|
||||
# Runs on AVX3 CPUs require more stack than others. Make sure to
|
||||
# test on AVX3-enabled CPUs when changing this value.
|
||||
env_test_stack_size: 4000
|
||||
# Build scalar-only hwy instructions.
|
||||
- name: scalar
|
||||
mode: release
|
||||
cxxflags: -DHWY_DISABLED_TARGETS=~HWY_SCALAR
|
||||
# Disabling optional features to speed up msan build a little bit.
|
||||
- name: msan
|
||||
skip_install: true
|
||||
cmake_args: >-
|
||||
-DJPEGXL_ENABLE_DEVTOOLS=OFF -DJPEGXL_ENABLE_PLUGINS=OFF
|
||||
-DJPEGXL_ENABLE_VIEWERS=OFF
|
||||
- name: asan
|
||||
skip_install: true
|
||||
- name: coverage
|
||||
apt_pkgs: gcovr
|
||||
# Coverage builds require a bit more RAM.
|
||||
env_test_stack_size: 2048
|
||||
# Exclude roundtrip tests from the unittest coverage.
|
||||
ctest_args: -E '^JxlTest'
|
||||
# Build with support for decoding to JPEG bytes disabled. Produces a
|
||||
# smaller build if only decoding to pixels is needed.
|
||||
- name: release-nojpeg
|
||||
mode: release
|
||||
cxxflags: -DJXL_DEBUG_ON_ABORT=0
|
||||
cmake_args: >-
|
||||
-DJPEGXL_ENABLE_TRANSCODE_JPEG=OFF
|
||||
-DJPEGXL_ENABLE_PLUGINS=OFF
|
||||
-DJPEGXL_ENABLE_VIEWERS=OFF
|
||||
# Builds with gcc in release mode
|
||||
- name: release:gcc8
|
||||
mode: release
|
||||
apt_pkgs: gcc-8 g++-8
|
||||
cmake_args: >-
|
||||
-DCMAKE_C_COMPILER=gcc-8 -DCMAKE_CXX_COMPILER=g++-8
|
||||
# Builds with clang-5 in release mode
|
||||
- name: release:clang-5
|
||||
os: ubuntu-18.04
|
||||
mode: release
|
||||
# TODO(eustas): investigate, why static brotli library is not found.
|
||||
skip_install: true
|
||||
apt_pkgs: clang-5.0
|
||||
cmake_args: >-
|
||||
-DCMAKE_C_COMPILER=clang-5.0 -DCMAKE_CXX_COMPILER=clang++-5.0
|
||||
-DJPEGXL_ENABLE_PLUGINS=OFF
|
||||
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
# Whether we track the stack size.
|
||||
STACK_SIZE: ${{ matrix.env_stack_size }}
|
||||
TEST_STACK_LIMIT: ${{ matrix.env_test_stack_size }}
|
||||
WILL_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: Install build deps
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y \
|
||||
ccache \
|
||||
clang-7 \
|
||||
cmake \
|
||||
doxygen \
|
||||
libbenchmark-dev \
|
||||
libbenchmark-tools \
|
||||
libbrotli-dev \
|
||||
libgdk-pixbuf2.0-dev \
|
||||
libgflags-dev \
|
||||
libgif-dev \
|
||||
libgtest-dev \
|
||||
libgtk2.0-dev \
|
||||
libjpeg-dev \
|
||||
libopenexr-dev \
|
||||
libpng-dev \
|
||||
libwebp-dev \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
xvfb \
|
||||
${{ matrix.apt_pkgs }} \
|
||||
#
|
||||
echo "CC=clang-7" >> $GITHUB_ENV
|
||||
echo "CXX=clang++-7" >> $GITHUB_ENV
|
||||
- name: Checkout the source
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 2
|
||||
- name: Sphinx dependencies
|
||||
# Dependencies for sphinx HTML documentation
|
||||
if: matrix.name == 'release'
|
||||
run: |
|
||||
pip3 install -r doc/sphinx/requirements.txt
|
||||
- name: Git environment
|
||||
id: git-env
|
||||
run: |
|
||||
echo "::set-output name=parent::$(git rev-parse ${{ github.sha }}^)"
|
||||
shell: bash
|
||||
- name: ccache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
# When the cache hits the key it is not updated, so if this is a rebuild
|
||||
# of the same Pull Request it will reuse the cache if still around. For
|
||||
# either Pull Requests or new pushes to main, this will use the parent
|
||||
# hash as the starting point from the restore-keys entry.
|
||||
key: ${{ runner.os }}-${{ github.sha }}-${{ matrix.name }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ steps.git-env.outputs.parent }}-${{ matrix.name }}
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir -p ${CCACHE_DIR}
|
||||
echo "max_size = 200M" > ${CCACHE_DIR}/ccache.conf
|
||||
mode="${{ matrix.mode }}"
|
||||
build_tests=$([ "$WILL_RUN_TESTS" == "true" ] && echo "ON" || echo "OFF")
|
||||
[[ -n "${mode}" ]] || mode="${{ matrix.name }}"
|
||||
./ci.sh ${mode} -DJPEGXL_FORCE_SYSTEM_BROTLI=ON \
|
||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
|
||||
-DBUILD_TESTING=${build_tests} \
|
||||
${{ matrix.cmake_args }}
|
||||
env:
|
||||
SKIP_TEST: 1
|
||||
CMAKE_CXX_FLAGS: ${{ matrix.cxxflags }}
|
||||
- name: ccache stats
|
||||
run: ccache --show-stats
|
||||
- name: Build stats ${{ matrix.name }}
|
||||
if: matrix.mode == 'release' || matrix.name == 'release'
|
||||
run: |
|
||||
tools/build_stats.py --save build/stats.json \
|
||||
--max-stack ${{ matrix.max_stack || '0' }} \
|
||||
cjxl djxl libjxl.so libjxl_dec.so
|
||||
# Check that we can build the example project against the installed libs.
|
||||
- name: Install and build examples
|
||||
if: |
|
||||
(matrix.mode == 'release' || matrix.name == 'release') &&
|
||||
!matrix.skip_install
|
||||
run: |
|
||||
set -x
|
||||
sudo cmake --build build -- install
|
||||
cmake -Bbuild-example -Hexamples -G Ninja
|
||||
cmake --build build-example
|
||||
if ldd build-example/decode_oneshot_static | grep libjxl; then
|
||||
echo "decode_oneshot_static is not using the static lib" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Test that the built binaries run.
|
||||
echo -e -n "PF\n1 1\n-1.0\nrrrrggggbbbb" > 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
|
||||
build-example/decode_oneshot_static test.jxl dec-static.pfm dec-static.icc
|
||||
# Run the tests on push and when requested in pull_request.
|
||||
- name: Test ${{ matrix.mode }}
|
||||
if: env.WILL_RUN_TESTS == 'true'
|
||||
run: |
|
||||
./ci.sh test ${{ matrix.ctest_args }}
|
||||
# Print the running time summary for the slowest tests.
|
||||
- name: Test runtime stats
|
||||
run: |
|
||||
sort build/Testing/Temporary/CTestCostData.txt -k 3 -n | tail -n 20 || true
|
||||
- name: Build HTML documentation (sphinx/readthetdocs)
|
||||
if: matrix.name == 'release'
|
||||
run: |
|
||||
cmake --build build -- rtd-html
|
||||
- name: Coverage report
|
||||
if: github.event_name == 'push' && matrix.name == 'coverage'
|
||||
run: |
|
||||
./ci.sh coverage_report
|
||||
- name: Coverage upload to Codecov
|
||||
if: github.event_name == 'push' && matrix.name == 'coverage'
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
flags: unittests
|
||||
files: build/coverage.xml
|
||||
- name: Fast benchmark ${{ matrix.mode }}
|
||||
if: |
|
||||
github.event_name == 'push' ||
|
||||
(github.event_name == 'pull_request' && (
|
||||
matrix.test_in_pr ||
|
||||
contains(github.event.pull_request.labels.*.names, 'CI:full')))
|
||||
run: |
|
||||
STORE_IMAGES=0 ./ci.sh fast_benchmark
|
||||
# Run gbench once, just to make sure it runs, not for actual benchmarking.
|
||||
# This doesn't work on msan because we use gbench library from the system
|
||||
# which is not instrumented by msan.
|
||||
- name: gbench check
|
||||
if: |
|
||||
matrix.name == 'release' || (
|
||||
github.event_name == 'push' && matrix.name != 'msan')
|
||||
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:
|
||||
build_target: i686-linux-gnu
|
||||
|
||||
env:
|
||||
BUILD_DIR: build
|
||||
|
||||
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
|
||||
libgtest-dev:${{ matrix.arch }}
|
||||
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
|
||||
# Some tests have a small floating point error on i686.
|
||||
# TODO(deymo): Re-enable i686 tests.
|
||||
if: |
|
||||
matrix.build_target != 'i686-linux-gnu' && (
|
||||
github.event_name == 'push' ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
contains(github.event.pull_request.labels.*.names, 'CI:full')))
|
||||
run: |
|
||||
./ci.sh test
|
||||
env:
|
||||
BUILD_TARGET: ${{ matrix.build_target }}
|
||||
|
||||
windows_msys:
|
||||
name: Windows MSYS2 / ${{ matrix.arch }}
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: x86_64
|
||||
msystem: mingw64
|
||||
- arch: i686
|
||||
msystem: mingw32
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- name: Checkout the source
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 1
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.msystem }}
|
||||
update: true
|
||||
path-type: inherit
|
||||
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
|
||||
|
||||
- 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 \
|
||||
-DJPEGXL_FORCE_SYSTEM_GTEST=ON \
|
||||
-B build \
|
||||
-G Ninja
|
||||
- name: CMake build
|
||||
run: cmake --build build
|
||||
- name: Test
|
||||
if: |
|
||||
github.event_name == 'push' ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
contains(github.event.pull_request.labels.*.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
|
||||
|
||||
wasm32_build:
|
||||
name: WASM wasm32/${{ matrix.variant }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
EM_VERSION: 2.0.23
|
||||
V8_VERSION: 9.3.22
|
||||
V8: ${{ github.workspace }}/.jsvu/v8
|
||||
BUILD_TARGET: wasm32
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- variant: scalar
|
||||
- variant: simd
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 1
|
||||
- name: Install build deps
|
||||
shell: bash
|
||||
run: |
|
||||
set -x
|
||||
sudo apt update
|
||||
pkgs=(
|
||||
# Build dependencies
|
||||
ccache
|
||||
cmake
|
||||
doxygen
|
||||
ninja-build
|
||||
pkg-config
|
||||
)
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install -y "${pkgs[@]}"
|
||||
|
||||
- name: Git environment
|
||||
id: git-env
|
||||
run: |
|
||||
echo "::set-output name=parent::$(git rev-parse ${{ github.sha }}^)"
|
||||
shell: bash
|
||||
- name: ccache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ github.sha }}-${{ matrix.variant }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ steps.git-env.outputs.parent }}-${{ matrix.variant }}
|
||||
|
||||
- name: Install emsdk
|
||||
uses: mymindstorm/setup-emsdk@v10
|
||||
# TODO(deymo): We could cache this action but it doesn't work when running
|
||||
# in a matrix.
|
||||
with:
|
||||
version: ${{env.EM_VERSION}}
|
||||
no-cache: true
|
||||
- name: Install v8
|
||||
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"
|
||||
# TODO(deymo): Build and install other dependencies like libpng, libjpeg,
|
||||
# etc.
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir -p ${CCACHE_DIR}
|
||||
echo "max_size = 200M" > ${CCACHE_DIR}/ccache.conf
|
||||
if [[ "${{ matrix.variant }}" == "simd" ]]; then
|
||||
export ENABLE_WASM_SIMD=1
|
||||
fi
|
||||
emconfigure ./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
|
||||
env:
|
||||
SKIP_TEST: 1
|
||||
- name: ccache stats
|
||||
run: ccache --show-stats
|
||||
|
||||
- name: Test
|
||||
if: |
|
||||
github.event_name == 'push' ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
contains(github.event.pull_request.labels.*.names, 'CI:full'))
|
||||
run: |
|
||||
emconfigure ./ci.sh test
|
|
@ -0,0 +1,125 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# Workflow for running conformance tests.
|
||||
|
||||
name: Conformance
|
||||
on:
|
||||
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:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
steps:
|
||||
- name: Install build deps
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y \
|
||||
ccache \
|
||||
clang-7 \
|
||||
cmake \
|
||||
doxygen \
|
||||
libbenchmark-dev \
|
||||
libbenchmark-tools \
|
||||
libbrotli-dev \
|
||||
libgdk-pixbuf2.0-dev \
|
||||
libgflags-dev \
|
||||
libgif-dev \
|
||||
libgtest-dev \
|
||||
libgtk2.0-dev \
|
||||
libjpeg-dev \
|
||||
libopenexr-dev \
|
||||
libpng-dev \
|
||||
libwebp-dev \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
xvfb \
|
||||
${{ matrix.apt_pkgs }} \
|
||||
#
|
||||
echo "CC=clang-7" >> $GITHUB_ENV
|
||||
echo "CXX=clang++-7" >> $GITHUB_ENV
|
||||
- name: Checkout the jxl source
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 2
|
||||
- name: Git environment
|
||||
id: git-env
|
||||
run: |
|
||||
echo "::set-output name=parent::$(git rev-parse ${{ github.sha }}^)"
|
||||
shell: bash
|
||||
- name: ccache
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
# When the cache hits the key it is not updated, so if this is a rebuild
|
||||
# of the same Pull Request it will reuse the cache if still around. For
|
||||
# either Pull Requests or new pushes to main, this will use the parent
|
||||
# hash as the starting point from the restore-keys entry.
|
||||
key: ${{ runner.os }}-${{ github.sha }}-${{ matrix.name }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ steps.git-env.outputs.parent }}-${{ matrix.name }}
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir -p ${CCACHE_DIR}
|
||||
echo "max_size = 200M" > ${CCACHE_DIR}/ccache.conf
|
||||
./ci.sh 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
|
||||
cp build/libjxl_dec.so.0.7.0 build/tools/conformance
|
||||
env:
|
||||
SKIP_TEST: 1
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: conformance_binary
|
||||
path: |
|
||||
build/tools/conformance/djxl_conformance
|
||||
build/tools/conformance/libjxl_dec.so.0.7.0
|
||||
- name: ccache stats
|
||||
run: ccache --show-stats
|
||||
|
||||
run:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
# 'main_level10' currently fails
|
||||
name: [main_level5]
|
||||
steps:
|
||||
- name: Install deps
|
||||
run: |
|
||||
pip install numpy
|
||||
- name: Checkout the conformance source
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: libjxl/conformance
|
||||
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: Run conformance tests
|
||||
run: |
|
||||
chmod +x djxl_conformance
|
||||
ln -s libjxl_dec.so.0.7.0 libjxl_dec.so.0.7
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:`pwd`
|
||||
python conformance/scripts/conformance.py \
|
||||
--decoder=`pwd`/djxl_conformance \
|
||||
--corpus=`pwd`/conformance/testcases/${{ matrix.name }}.txt
|
|
@ -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.
|
||||
|
||||
# Workflow for building and then debugging on a specific commit.
|
||||
|
||||
name: Build and Test debugging
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- ci-*-debug
|
||||
|
||||
jobs:
|
||||
ubuntu_build:
|
||||
name: Ubuntu Build and SSH
|
||||
runs-on: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- name: Install build deps
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y \
|
||||
ccache \
|
||||
clang-7 \
|
||||
cmake \
|
||||
doxygen \
|
||||
libbrotli-dev \
|
||||
libgdk-pixbuf2.0-dev \
|
||||
libgflags-dev \
|
||||
libgif-dev \
|
||||
libgtest-dev \
|
||||
libgtk2.0-dev \
|
||||
libjpeg-dev \
|
||||
libopenexr-dev \
|
||||
libpng-dev \
|
||||
libwebp-dev \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
xvfb \
|
||||
${{ matrix.apt_pkgs }} \
|
||||
#
|
||||
echo "CC=clang-7" >> $GITHUB_ENV
|
||||
echo "CXX=clang++-7" >> $GITHUB_ENV
|
||||
- name: Checkout the source
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 2
|
||||
- name: Build
|
||||
run: |
|
||||
./ci.sh $(echo ${{ github.ref }} | sed 's_refs/heads/ci-\([a-z_]*\)-debug_\1_') \
|
||||
-DJPEGXL_FORCE_SYSTEM_BROTLI=ON
|
||||
env:
|
||||
SKIP_TEST: 1
|
||||
- name: Setup tmate session
|
||||
uses: mxschmitt/action-tmate@v3
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# CI on pull-requests to run the fuzzer from oss-fuzz. See:
|
||||
#
|
||||
# https://google.github.io/oss-fuzz/getting-started/continuous-integration/
|
||||
|
||||
name: CIFuzz
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
paths:
|
||||
- '**.c'
|
||||
- '**.cc'
|
||||
- '**.cmake'
|
||||
- '**.h'
|
||||
- '**CMakeLists.txt'
|
||||
- .github/workflows/fuzz.yml
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
fuzzing:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v2
|
||||
id: checkout
|
||||
with:
|
||||
# The build_fuzzers action checks out the code to the storage/libjxl
|
||||
# directory already, but doesn't check out the submodules. This step
|
||||
# is a workaround for checking out the submodules.
|
||||
path: storage/libjxl
|
||||
submodules: true
|
||||
- name: Build Fuzzers
|
||||
id: build
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'libjxl'
|
||||
language: c++
|
||||
- name: Run Fuzzers
|
||||
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
|
||||
with:
|
||||
oss-fuzz-project-name: 'libjxl'
|
||||
language: c++
|
||||
fuzz-seconds: 600
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@v1
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./out/artifacts
|
|
@ -0,0 +1,42 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# Workflow to run pull-requests specific checks.
|
||||
|
||||
name: PR
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
# Checks that the AUTHORS files is updated with new contributors.
|
||||
authors:
|
||||
runs-on: [ubuntu-latest]
|
||||
steps:
|
||||
- name: Checkout the source
|
||||
uses: actions/checkout@v2
|
||||
- name: Check AUTHORS file
|
||||
run:
|
||||
./ci.sh authors
|
||||
|
||||
format:
|
||||
runs-on: [ubuntu-latest]
|
||||
steps:
|
||||
- name: Install build deps
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y \
|
||||
clang-format \
|
||||
clang-format-7 \
|
||||
clang-format-8 \
|
||||
clang-format-9 \
|
||||
clang-format-10 \
|
||||
clang-format-11 \
|
||||
#
|
||||
- name: Checkout the source
|
||||
uses: actions/checkout@v2
|
||||
- name: clang-format
|
||||
run:
|
||||
./ci.sh lint >&2
|
|
@ -0,0 +1,362 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# Workflow for building the release binaries.
|
||||
#
|
||||
# This workflow runs as a post-submit step, when pushing to main or the release
|
||||
# branches (v*.*.x), and when creating a release in GitHub.
|
||||
#
|
||||
# In the GitHub release case, in addition to build the release binaries it also
|
||||
# uploads the binaries to the given release automatically.
|
||||
|
||||
name: Release build / deploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v*.*.x
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
jobs:
|
||||
ubuntu_static_x86_64:
|
||||
name: Release linux x86_64 static
|
||||
runs-on: [ubuntu-latest]
|
||||
steps:
|
||||
- name: Install build deps
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y \
|
||||
asciidoc \
|
||||
clang \
|
||||
cmake \
|
||||
doxygen \
|
||||
libbrotli-dev \
|
||||
libgdk-pixbuf2.0-dev \
|
||||
libgflags-dev \
|
||||
libgif-dev \
|
||||
libgtest-dev \
|
||||
libgtk2.0-dev \
|
||||
libjpeg-dev \
|
||||
libopenexr-dev \
|
||||
libpng-dev \
|
||||
libwebp-dev \
|
||||
ninja-build \
|
||||
pkg-config \
|
||||
#
|
||||
echo "CC=clang" >> $GITHUB_ENV
|
||||
echo "CXX=clang++" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout the source
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Build
|
||||
env:
|
||||
SKIP_TEST: 1
|
||||
run: |
|
||||
./ci.sh release \
|
||||
-DJPEGXL_DEP_LICENSE_DIR=/usr/share/doc \
|
||||
-DJPEGXL_STATIC=ON \
|
||||
-DBUILD_TESTING=OFF \
|
||||
-DJPEGXL_ENABLE_VIEWERS=OFF \
|
||||
-DJPEGXL_ENABLE_PLUGINS=OFF \
|
||||
-DJPEGXL_ENABLE_OPENEXR=OFF \
|
||||
|
||||
- name: Package release tarball
|
||||
run: |
|
||||
cd build
|
||||
tar -zcvf ${{ runner.workspace }}/release_file.tar.gz \
|
||||
LICENSE* tools/{cjxl,djxl,benchmark_xl}
|
||||
ln -s ${{ runner.workspace }}/release_file.tar.gz \
|
||||
${{ runner.workspace }}/jxl-linux-x86_64-static-${{ github.event.release.tag_name }}.tar.gz
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: jxl-linux-x86_64-static
|
||||
path: ${{ runner.workspace }}/release_file.tar.gz
|
||||
|
||||
- name: Upload binaries to release
|
||||
if: github.event_name == 'release'
|
||||
uses: AButler/upload-release-assets@v2.0
|
||||
with:
|
||||
files: ${{ runner.workspace }}/jxl-linux-x86_64-static-${{ github.event.release.tag_name }}.tar.gz
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
# Build .deb packages Ubuntu/Debian
|
||||
release_ubuntu_pkg:
|
||||
name: .deb packages / ${{ matrix.os }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu:20.04
|
||||
- ubuntu:18.04
|
||||
- debian:buster
|
||||
- debian:bullseye
|
||||
- debian:bookworm
|
||||
- debian:sid
|
||||
|
||||
container:
|
||||
image: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Set env
|
||||
shell: 'bash'
|
||||
id: 'env'
|
||||
run: |
|
||||
artifact_name="jxl-debs-amd64-${matrix_os/:/-}"
|
||||
echo ${artifact_name}
|
||||
echo "::set-output name=artifact_name::${artifact_name}"
|
||||
env:
|
||||
matrix_os: ${{ matrix.os }}
|
||||
|
||||
- name: Install build deps
|
||||
run: |
|
||||
apt update
|
||||
DEBIAN_FRONTEND=noninteractive apt install -y \
|
||||
build-essential \
|
||||
devscripts \
|
||||
#
|
||||
|
||||
- name: Install git (only 18.04)
|
||||
if: matrix.os == 'ubuntu:18.04'
|
||||
# Ubuntu 18.04 ships with git 2.17 but we need 2.18 or newer for
|
||||
# actions/checkout@v2 to work
|
||||
shell: 'bash'
|
||||
run: |
|
||||
apt install -y \
|
||||
libcurl4-openssl-dev \
|
||||
libexpat1-dev \
|
||||
libssl-dev \
|
||||
wget \
|
||||
zlib1g-dev \
|
||||
#
|
||||
git_version="2.32.0"
|
||||
wget -nv \
|
||||
"https://github.com/git/git/archive/refs/tags/v${git_version}.tar.gz"
|
||||
tar -zxf "v${git_version}.tar.gz"
|
||||
cd "git-${git_version}"
|
||||
make prefix=/usr -j4 install
|
||||
|
||||
- name: Checkout the source
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Stamp non-release versions
|
||||
# Stamps the built package with the commit date as part of the version
|
||||
# after the version number so newer release candidates can override older
|
||||
# ones.
|
||||
if: github.event_name != 'release'
|
||||
shell: 'bash'
|
||||
run: |
|
||||
# Committer timestamp.
|
||||
set -x
|
||||
commit_timestamp=$(git show -s --format=%ct)
|
||||
commit_datetime=$(date --utc "--date=@${commit_timestamp}" '+%Y%m%d%H%M%S')
|
||||
commit_ref=$(git rev-parse --short HEAD)
|
||||
sem_version=$(dpkg-parsechangelog --show-field Version)
|
||||
sem_version="${sem_version%%-*}"
|
||||
deb_version="${sem_version}~alpha${commit_datetime}-0+git${commit_ref}"
|
||||
dch -M --distribution unstable -b --newversion "${deb_version}" \
|
||||
"Stamping build with version ${deb_version}"
|
||||
|
||||
- name: Stamp release versions
|
||||
# Mark the version as released
|
||||
if: github.event_name == 'release'
|
||||
shell: 'bash'
|
||||
run: |
|
||||
if head -n1 debian/changelog | grep UNRELEASED; then
|
||||
dch -M --distribution unstable --release ''
|
||||
fi
|
||||
|
||||
- name: Install gtest (only 18.04)
|
||||
if: matrix.os == 'ubuntu:18.04'
|
||||
# In Ubuntu 18.04 no package installed the libgtest.a. libgtest-dev
|
||||
# installs the source files only.
|
||||
run: |
|
||||
apt install -y libgtest-dev cmake
|
||||
for prj in googletest googlemock; do
|
||||
(cd /usr/src/googletest/${prj}/ &&
|
||||
cmake CMakeLists.txt -DCMAKE_INSTALL_PREFIX=/usr &&
|
||||
make all install)
|
||||
done
|
||||
# Remove libgmock-dev dependency in Ubuntu 18.04. It doesn't exist there.
|
||||
sed '/libgmock-dev,/d' -i debian/control
|
||||
|
||||
- name: Install gmock-dev (debian:sid)
|
||||
# gtest-dev cmake depends on gmock-dev, but it is not installed by the
|
||||
# package.
|
||||
if: matrix.os == 'debian:sid'
|
||||
run: |
|
||||
apt install -y libgmock-dev
|
||||
|
||||
- name: Remove libjxl-gimp-plugin package (only 18.04)
|
||||
if: matrix.os == 'ubuntu:18.04'
|
||||
run: |
|
||||
# Gimp 2.8 is not supported.
|
||||
sed -i '/Package: libjxl-gimp-plugin/,/^$/d' debian/control
|
||||
|
||||
- name: Build hwy
|
||||
run: |
|
||||
apt build-dep -y ./third_party/highway
|
||||
./ci.sh debian_build highway
|
||||
dpkg -i build/debs/libhwy-dev_*_amd64.deb
|
||||
|
||||
- name: Build libjxl
|
||||
run: |
|
||||
apt build-dep -y .
|
||||
./ci.sh debian_build jpeg-xl
|
||||
|
||||
- name: Stats
|
||||
run: |
|
||||
./ci.sh debian_stats
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: ${{ steps.env.outputs.artifact_name }}
|
||||
path: |
|
||||
build/debs/*jxl*.*
|
||||
|
||||
- name: Package release tarball
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
(cd build/debs/; find -maxdepth 1 -name '*jxl*.*') | \
|
||||
tar -zcvf release_file.tar.gz -C build/debs/ -T -
|
||||
ln -s release_file.tar.gz \
|
||||
${{ steps.env.outputs.artifact_name }}-${{ github.event.release.tag_name }}.tar.gz
|
||||
|
||||
- name: Upload binaries to release
|
||||
if: github.event_name == 'release'
|
||||
uses: AButler/upload-release-assets@v2.0
|
||||
with:
|
||||
files: ${{ steps.env.outputs.artifact_name }}-${{ github.event.release.tag_name }}.tar.gz
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
windows_build:
|
||||
name: Windows Build (vcpkg / ${{ matrix.triplet }})
|
||||
runs-on: [windows-latest]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- triplet: x86-windows-static
|
||||
arch: '-A Win32'
|
||||
- triplet: x64-windows-static
|
||||
arch: '-A x64'
|
||||
|
||||
env:
|
||||
VCPKG_VERSION: '2021.05.12'
|
||||
VCPKG_ROOT: vcpkg
|
||||
VCPKG_DISABLE_METRICS: 1
|
||||
|
||||
steps:
|
||||
- name: Checkout the source
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 2
|
||||
|
||||
- uses: actions/cache@v2
|
||||
id: cache-vcpkg
|
||||
with:
|
||||
path: vcpkg
|
||||
key: ${{ runner.os }}-vcpkg-${{ env.VCPKG_VERSION }}-${{ matrix.triplet }}
|
||||
|
||||
- name: Download vcpkg
|
||||
if: steps.cache-vcpkg.outputs.cache-hit != 'true'
|
||||
# wget doesn't seem to work under bash.
|
||||
shell: 'powershell'
|
||||
run: |
|
||||
C:\msys64\usr\bin\wget.exe -nv `
|
||||
https://github.com/microsoft/vcpkg/archive/refs/tags/${{ env.VCPKG_VERSION }}.zip `
|
||||
-O vcpkg.zip
|
||||
- name: Bootstrap vcpkg
|
||||
if: steps.cache-vcpkg.outputs.cache-hit != 'true'
|
||||
shell: 'bash'
|
||||
run: |
|
||||
set -x
|
||||
unzip -q vcpkg.zip
|
||||
rm -rf ${VCPKG_ROOT}
|
||||
mv vcpkg-${VCPKG_VERSION} ${VCPKG_ROOT}
|
||||
${VCPKG_ROOT}/bootstrap-vcpkg.sh
|
||||
|
||||
- name: Install libraries with vcpkg
|
||||
shell: 'bash'
|
||||
run: |
|
||||
set -x
|
||||
${VCPKG_ROOT}/vcpkg --triplet ${{ matrix.triplet }} install \
|
||||
gflags \
|
||||
giflib \
|
||||
libjpeg-turbo \
|
||||
libpng \
|
||||
libwebp \
|
||||
#
|
||||
|
||||
- name: Configure
|
||||
shell: 'bash'
|
||||
run: |
|
||||
set -x
|
||||
mkdir build
|
||||
cmake -Bbuild -H. ${{ matrix.arch }} \
|
||||
-DBUILD_TESTING=OFF \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_INSTALL_PREFIX=`pwd`/prefix \
|
||||
-DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake \
|
||||
-DJPEGXL_ENABLE_OPENEXR=OFF \
|
||||
-DJPEGXL_ENABLE_PLUGINS=OFF \
|
||||
-DJPEGXL_ENABLE_TCMALLOC=OFF \
|
||||
-DJPEGXL_ENABLE_VIEWERS=OFF \
|
||||
-DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} \
|
||||
-DJPEGXL_BUNDLE_GFLAGS=ON \
|
||||
#
|
||||
- name: Build
|
||||
shell: 'bash'
|
||||
run: |
|
||||
set -x
|
||||
cmake --build build --config Release
|
||||
- name: Install
|
||||
shell: 'bash'
|
||||
run: |
|
||||
set -x
|
||||
cmake --build build --config Release --target install
|
||||
for pkg in giflib libjpeg-turbo libpng libwebp zlib; do
|
||||
cp vcpkg/installed/${{matrix.triplet}}/share/${pkg}/copyright \
|
||||
prefix/bin/LICENSE.${pkg}
|
||||
done
|
||||
cp third_party/sjpeg/COPYING prefix/bin/LICENSE.sjpeg
|
||||
cp third_party/skcms/LICENSE prefix/bin/LICENSE.skcms
|
||||
cp third_party/highway/LICENSE prefix/bin/LICENSE.highway
|
||||
cp third_party/brotli/LICENSE prefix/bin/LICENSE.brotli
|
||||
cp LICENSE prefix/bin/LICENSE.libjxl
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: jxl-${{matrix.triplet}}
|
||||
path: |
|
||||
prefix/bin/*
|
||||
|
||||
- name: Package release zip
|
||||
if: github.event_name == 'release'
|
||||
shell: 'powershell'
|
||||
run: |
|
||||
Compress-Archive -Path prefix\bin\* `
|
||||
-DestinationPath jxl-${{matrix.triplet}}.zip
|
||||
|
||||
- name: Upload binaries to release
|
||||
if: github.event_name == 'release'
|
||||
uses: AButler/upload-release-assets@v2.0
|
||||
with:
|
||||
files: jxl-${{matrix.triplet}}.zip
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -20,6 +20,9 @@ Alex Xu (Hello71) <alex_y_xu@yahoo.ca>
|
|||
Alexander Sago <cagelight@gmail.com>
|
||||
Andrius Lukas Narbutas <andrius4669@gmail.com>
|
||||
Artem Selishchev
|
||||
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>
|
||||
Jon Sneyers <jon@cloudinary.com>
|
||||
|
@ -34,4 +37,5 @@ Pieter Wuille
|
|||
Samuel Leong <wvvwvvvvwvvw@gmail.com>
|
||||
Vincent Torri <vincent.torri@gmail.com>
|
||||
xiota
|
||||
Yonatan Nebenzhal <yonatan.nebenzhl@gmail.com>
|
||||
Ziemowit Zabawa <ziemek.zabawa@outlook.com>
|
||||
|
|
|
@ -76,8 +76,10 @@ 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.
|
||||
|
@ -95,6 +97,8 @@ 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
|
||||
|
@ -326,7 +330,9 @@ set(DOXYGEN_GENERATE_HTML "YES")
|
|||
set(DOXYGEN_GENERATE_XML "YES")
|
||||
set(DOXYGEN_STRIP_FROM_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lib/include")
|
||||
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "README.md")
|
||||
if(JPEGXL_WARNINGS_AS_ERRORS)
|
||||
set(DOXYGEN_WARN_AS_ERROR "YES")
|
||||
endif()
|
||||
set(DOXYGEN_QUIET "YES")
|
||||
doxygen_add_docs(doc
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/include"
|
||||
|
|
|
@ -30,7 +30,7 @@ For more details and other workflows see the "Advanced guide" below.
|
|||
### Checking out the code
|
||||
|
||||
```bash
|
||||
git clone https://github.com/libjxl/libjxl.git --recursive
|
||||
git clone https://github.com/libjxl/libjxl.git --recursive --shallow-submodules
|
||||
```
|
||||
|
||||
This repository uses git submodules to handle some third party dependencies
|
||||
|
@ -38,9 +38,20 @@ under `third_party`, that's why is important to pass `--recursive`. If you
|
|||
didn't check out with `--recursive`, or any submodule has changed, run:
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive --depth 1 --recommend-shallow
|
||||
```
|
||||
|
||||
The `--shallow-submodules` and `--depth 1 --recommend-shallow` options create
|
||||
shallow clones which only downloads the commits requested, and is all that is
|
||||
needed to build `libjxl`. Should full clones be necessary, you could always run:
|
||||
|
||||
```bash
|
||||
git submodule foreach git fetch --unshallow
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
which pulls the rest of the commits in the submodules.
|
||||
|
||||
Important: If you downloaded a zip file or tarball from the web interface you
|
||||
won't get the needed submodules and the code will not compile. You can download
|
||||
these external dependencies from source running `./deps.sh`. The git workflow
|
||||
|
@ -52,7 +63,7 @@ Required dependencies for compiling the code, in a Debian/Ubuntu based
|
|||
distribution run:
|
||||
|
||||
```bash
|
||||
sudo apt install cmake pkg-config libbrotli-dev
|
||||
sudo apt install cmake pkg-config libbrotli-dev libgflags-dev
|
||||
```
|
||||
|
||||
Optional dependencies for supporting other formats in the `cjxl`/`djxl` tools,
|
||||
|
|
|
@ -697,7 +697,8 @@ cmd_msan() {
|
|||
strip_dead_code
|
||||
cmake_configure "$@" \
|
||||
-DCMAKE_CROSSCOMPILING=1 -DRUN_HAVE_STD_REGEX=0 -DRUN_HAVE_POSIX_REGEX=0 \
|
||||
-DJPEGXL_ENABLE_TCMALLOC=OFF -DJPEGXL_WARNINGS_AS_ERRORS=OFF
|
||||
-DJPEGXL_ENABLE_TCMALLOC=OFF -DJPEGXL_WARNINGS_AS_ERRORS=OFF \
|
||||
-DCMAKE_REQUIRED_LINK_OPTIONS="${msan_linker_flags[@]}"
|
||||
cmake_build_and_test
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ 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,
|
||||
|
|
|
@ -13,6 +13,7 @@ MYDIR=$(dirname $(realpath "$0"))
|
|||
|
||||
# Git revisions we use for the given submodules. Update these whenever you
|
||||
# update a git submodule.
|
||||
THIRD_PARTY_GFLAGS="827c769e5fc98e0f2a34c47cef953cc6328abced"
|
||||
THIRD_PARTY_HIGHWAY="e69083a12a05caf037cabecdf1b248b7579705a5"
|
||||
THIRD_PARTY_SKCMS="64374756e03700d649f897dbd98c95e78c30c7da"
|
||||
THIRD_PARTY_SJPEG="868ab558fad70fcbe8863ba4e85179eeb81cc840"
|
||||
|
@ -65,11 +66,12 @@ Current directory is a git repository, downloading dependencies via git:
|
|||
git submodule update --init --recursive
|
||||
|
||||
EOF
|
||||
git -C "${MYDIR}" submodule update --init --recursive
|
||||
git -C "${MYDIR}" submodule update --init --recursive --depth 1 --recommend-shallow
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Sources downloaded from a tarball.
|
||||
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 \
|
||||
|
|
|
@ -19,6 +19,9 @@ pkg_check_modules(JxlThreads REQUIRED IMPORTED_TARGET libjxl_threads)
|
|||
add_executable(decode_oneshot decode_oneshot.cc)
|
||||
target_link_libraries(decode_oneshot PkgConfig::Jxl PkgConfig::JxlThreads)
|
||||
|
||||
add_executable(decode_progressive decode_progressive.cc)
|
||||
target_link_libraries(decode_progressive PkgConfig::Jxl PkgConfig::JxlThreads)
|
||||
|
||||
add_executable(encode_oneshot encode_oneshot.cc)
|
||||
target_link_libraries(encode_oneshot PkgConfig::Jxl PkgConfig::JxlThreads)
|
||||
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This C++ example decodes a JPEG XL image progressively (input bytes are
|
||||
// passed in chunks). The example outputs the intermediate steps to PAM files.
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/decode.h"
|
||||
#include "jxl/decode_cxx.h"
|
||||
#include "jxl/resizable_parallel_runner.h"
|
||||
#include "jxl/resizable_parallel_runner_cxx.h"
|
||||
|
||||
bool WritePAM(const char* filename, const uint8_t* buffer, size_t w, size_t h) {
|
||||
FILE* fp = fopen(filename, "wb");
|
||||
if (!fp) {
|
||||
fprintf(stderr, "Could not open %s for writing", filename);
|
||||
return false;
|
||||
}
|
||||
fprintf(fp,
|
||||
"P7\nWIDTH %" PRIu64 "\nHEIGHT %" PRIu64
|
||||
"\nDEPTH 4\nMAXVAL 255\nTUPLTYPE "
|
||||
"RGB_ALPHA\nENDHDR\n",
|
||||
static_cast<uint64_t>(w), static_cast<uint64_t>(h));
|
||||
fwrite(buffer, 1, w * h * 4, fp);
|
||||
if (fclose(fp) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Decodes JPEG XL image to 8-bit integer RGBA pixels and an ICC Profile, in a
|
||||
* progressive way, saving the intermediate steps.
|
||||
*/
|
||||
bool DecodeJpegXlProgressive(const uint8_t* jxl, size_t size,
|
||||
const char* filename, size_t chunksize) {
|
||||
std::vector<uint8_t> pixels;
|
||||
std::vector<uint8_t> icc_profile;
|
||||
size_t xsize = 0, ysize = 0;
|
||||
|
||||
// Multi-threaded parallel runner.
|
||||
auto runner = JxlResizableParallelRunnerMake(nullptr);
|
||||
|
||||
auto dec = JxlDecoderMake(nullptr);
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO |
|
||||
JXL_DEC_COLOR_ENCODING |
|
||||
JXL_DEC_FULL_IMAGE)) {
|
||||
fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(),
|
||||
JxlResizableParallelRunner,
|
||||
runner.get())) {
|
||||
fprintf(stderr, "JxlDecoderSetParallelRunner failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
JxlBasicInfo info;
|
||||
JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
|
||||
|
||||
size_t seen = 0;
|
||||
JxlDecoderSetInput(dec.get(), jxl, chunksize);
|
||||
size_t remaining = chunksize;
|
||||
|
||||
for (;;) {
|
||||
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
|
||||
|
||||
if (status == JXL_DEC_ERROR) {
|
||||
fprintf(stderr, "Decoder error\n");
|
||||
return false;
|
||||
} else if (status == JXL_DEC_NEED_MORE_INPUT || status == JXL_DEC_SUCCESS ||
|
||||
status == JXL_DEC_FULL_IMAGE) {
|
||||
seen += remaining - JxlDecoderReleaseInput(dec.get());
|
||||
printf("Flushing after %" PRIu64 " bytes\n", static_cast<uint64_t>(seen));
|
||||
if (status == JXL_DEC_NEED_MORE_INPUT &&
|
||||
JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec.get())) {
|
||||
printf("flush error (no preview yet)\n");
|
||||
} else {
|
||||
char fname[1024];
|
||||
if (snprintf(fname, 1024, "%s-%" PRIu64 ".pam", filename,
|
||||
static_cast<uint64_t>(seen)) >= 1024) {
|
||||
fprintf(stderr, "Filename too long\n");
|
||||
return false;
|
||||
};
|
||||
if (!WritePAM(fname, pixels.data(), xsize, ysize)) {
|
||||
fprintf(stderr, "Error writing progressive output\n");
|
||||
}
|
||||
}
|
||||
remaining = size - seen;
|
||||
if (remaining > chunksize) remaining = chunksize;
|
||||
if (remaining == 0) {
|
||||
if (status == JXL_DEC_NEED_MORE_INPUT) {
|
||||
fprintf(stderr, "Error, already provided all input\n");
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
JxlDecoderSetInput(dec.get(), jxl + seen, remaining);
|
||||
} else if (status == JXL_DEC_BASIC_INFO) {
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) {
|
||||
fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
|
||||
return false;
|
||||
}
|
||||
xsize = info.xsize;
|
||||
ysize = info.ysize;
|
||||
JxlResizableParallelRunnerSetThreads(
|
||||
runner.get(),
|
||||
JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize));
|
||||
} else if (status == JXL_DEC_COLOR_ENCODING) {
|
||||
// Get the ICC color profile of the pixel data
|
||||
size_t icc_size;
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderGetICCProfileSize(dec.get(), &format,
|
||||
JXL_COLOR_PROFILE_TARGET_ORIGINAL,
|
||||
&icc_size)) {
|
||||
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
|
||||
return false;
|
||||
}
|
||||
icc_profile.resize(icc_size);
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile(
|
||||
dec.get(), &format,
|
||||
JXL_COLOR_PROFILE_TARGET_ORIGINAL,
|
||||
icc_profile.data(), icc_profile.size())) {
|
||||
fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
|
||||
return false;
|
||||
}
|
||||
} else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
|
||||
size_t buffer_size;
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) {
|
||||
fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n");
|
||||
return false;
|
||||
}
|
||||
if (buffer_size != xsize * ysize * 4) {
|
||||
fprintf(stderr, "Invalid out buffer size %" PRIu64 " != %" PRIu64 "\n",
|
||||
static_cast<uint64_t>(buffer_size),
|
||||
static_cast<uint64_t>(xsize * ysize * 4));
|
||||
return false;
|
||||
}
|
||||
pixels.resize(xsize * ysize * 4);
|
||||
void* pixels_buffer = (void*)pixels.data();
|
||||
size_t pixels_buffer_size = pixels.size() * sizeof(float);
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format,
|
||||
pixels_buffer,
|
||||
pixels_buffer_size)) {
|
||||
fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Unknown decoder status\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LoadFile(const char* filename, std::vector<uint8_t>* out) {
|
||||
FILE* file = fopen(filename, "rb");
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fseek(file, 0, SEEK_END) != 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
long size = ftell(file);
|
||||
// Avoid invalid file or directory.
|
||||
if (size >= LONG_MAX || size < 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fseek(file, 0, SEEK_SET) != 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
out->resize(size);
|
||||
size_t readsize = fread(out->data(), 1, size, file);
|
||||
if (fclose(file) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return readsize == static_cast<size_t>(size);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 3) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Usage: %s <jxl> <basename> [chunksize]\n"
|
||||
"Where:\n"
|
||||
" jxl = input JPEG XL image filename\n"
|
||||
" basename = prefix of output filenames\n"
|
||||
" chunksize = loads chunksize bytes at a time and writes\n"
|
||||
" intermediate results to basename-[bytes loaded].pam\n"
|
||||
"Output files will be overwritten.\n",
|
||||
argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* jxl_filename = argv[1];
|
||||
const char* png_filename = argv[2];
|
||||
|
||||
std::vector<uint8_t> jxl;
|
||||
if (!LoadFile(jxl_filename, &jxl)) {
|
||||
fprintf(stderr, "couldn't load %s\n", jxl_filename);
|
||||
return 1;
|
||||
}
|
||||
size_t chunksize = jxl.size();
|
||||
if (argc > 3) {
|
||||
long cs = atol(argv[3]);
|
||||
if (cs < 100) {
|
||||
fprintf(stderr, "Chunk size is too low, try at least 100 bytes\n");
|
||||
return 1;
|
||||
}
|
||||
chunksize = cs;
|
||||
}
|
||||
|
||||
if (!DecodeJpegXlProgressive(jxl.data(), jxl.size(), png_filename,
|
||||
chunksize)) {
|
||||
fprintf(stderr, "Error while decoding the jxl file\n");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
add_executable(decode_oneshot ${CMAKE_CURRENT_LIST_DIR}/decode_oneshot.cc)
|
||||
target_link_libraries(decode_oneshot jxl_dec jxl_threads)
|
||||
add_executable(decode_progressive ${CMAKE_CURRENT_LIST_DIR}/decode_progressive.cc)
|
||||
target_link_libraries(decode_progressive jxl_dec jxl_threads)
|
||||
add_executable(encode_oneshot ${CMAKE_CURRENT_LIST_DIR}/encode_oneshot.cc)
|
||||
target_link_libraries(encode_oneshot jxl jxl_threads)
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
build/
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
set -e
|
||||
|
||||
DIR=$(realpath "$(dirname "$0")")
|
||||
|
||||
mkdir -p /tmp/build-android
|
||||
cd /tmp/build-android
|
||||
|
||||
CXX="$ANDROID_NDK"/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang++
|
||||
if ! command -v "$CXX" >/dev/null ; then
|
||||
printf >&2 '%s: Android C++ compiler not found, is ANDROID_NDK set properly?\n' "${0##*/}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[ -f lodepng.cpp ] || curl -o lodepng.cpp --url 'https://raw.githubusercontent.com/lvandeve/lodepng/8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a/lodepng.cpp'
|
||||
[ -f lodepng.h ] || curl -o lodepng.h --url 'https://raw.githubusercontent.com/lvandeve/lodepng/8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a/lodepng.h'
|
||||
[ -f lodepng.o ] || "$CXX" lodepng.cpp -O3 -o lodepng.o -c
|
||||
|
||||
"$CXX" -O3 -DFASTLL_ENABLE_NEON_INTRINSICS -fopenmp \
|
||||
-I. lodepng.o \
|
||||
"${DIR}"/fast_lossless.cc "${DIR}"/fast_lossless_main.cc \
|
||||
-o fast_lossless
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
set -e
|
||||
|
||||
DIR=$(realpath "$(dirname "$0")")
|
||||
mkdir -p "$DIR"/build
|
||||
cd "$DIR"/build
|
||||
|
||||
# set CXX to clang++ if not set in the environment
|
||||
CXX="${CXX-clang++}"
|
||||
if ! command -v "$CXX" >/dev/null ; then
|
||||
printf >&2 '%s: C++ compiler not found\n' "${0##*/}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[ -f lodepng.cpp ] || curl -o lodepng.cpp --url 'https://raw.githubusercontent.com/lvandeve/lodepng/8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a/lodepng.cpp'
|
||||
[ -f lodepng.h ] || curl -o lodepng.h --url 'https://raw.githubusercontent.com/lvandeve/lodepng/8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a/lodepng.h'
|
||||
[ -f lodepng.o ] || "$CXX" lodepng.cpp -O3 -mavx2 -o lodepng.o -c
|
||||
|
||||
"$CXX" -O3 -mavx2 -DFASTLL_ENABLE_AVX2_INTRINSICS -fopenmp \
|
||||
-I. lodepng.o \
|
||||
"$DIR"/fast_lossless.cc "$DIR"/fast_lossless_main.cc \
|
||||
-o fast_lossless
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef FAST_LOSSLESS_H
|
||||
#define FAST_LOSSLESS_H
|
||||
#include <stdlib.h>
|
||||
|
||||
size_t FastLosslessEncode(const unsigned char* rgba, size_t width,
|
||||
size_t row_stride, size_t height, size_t nb_chans,
|
||||
size_t bitdepth, int effort, unsigned char** output);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright (c) the JPEG XL Project 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "fast_lossless.h"
|
||||
#include "lodepng.h"
|
||||
#include "pam-input.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "Usage: %s in.png out.jxl [effort] [num_reps]\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* in = argv[1];
|
||||
const char* out = argv[2];
|
||||
int effort = argc >= 4 ? atoi(argv[3]) : 2;
|
||||
size_t num_reps = argc >= 5 ? atoi(argv[4]) : 1;
|
||||
|
||||
if (effort < 0 || effort > 127) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Effort should be between 0 and 127 (default is 2, more is slower)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned char* png;
|
||||
unsigned w, h;
|
||||
size_t nb_chans = 4, bitdepth = 8;
|
||||
|
||||
unsigned error = lodepng_decode32_file(&png, &w, &h, in);
|
||||
|
||||
size_t width = w, height = h;
|
||||
if (error && !DecodePAM(in, &png, &width, &height, &nb_chans, &bitdepth)) {
|
||||
fprintf(stderr, "lodepng error %u: %s\n", error, lodepng_error_text(error));
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t encoded_size = 0;
|
||||
unsigned char* encoded = nullptr;
|
||||
size_t stride = width * nb_chans * (bitdepth > 8 ? 2 : 1);
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
for (size_t _ = 0; _ < num_reps; _++) {
|
||||
free(encoded);
|
||||
encoded_size = FastLosslessEncode(png, width, stride, height, nb_chans,
|
||||
bitdepth, effort, &encoded);
|
||||
}
|
||||
auto stop = std::chrono::high_resolution_clock::now();
|
||||
if (num_reps > 1) {
|
||||
float us =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(stop - start)
|
||||
.count();
|
||||
size_t pixels = size_t{width} * size_t{height} * num_reps;
|
||||
float mps = pixels / us;
|
||||
fprintf(stderr, "%10.3f MP/s\n", mps);
|
||||
fprintf(stderr, "%10.3f bits/pixel\n",
|
||||
encoded_size * 8.0 / float(width) / float(height));
|
||||
}
|
||||
|
||||
FILE* o = fopen(out, "wb");
|
||||
if (!o) {
|
||||
fprintf(stderr, "error opening %s: %s\n", out, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
if (fwrite(encoded, 1, encoded_size, o) != encoded_size) {
|
||||
fprintf(stderr, "error writing to %s: %s\n", out, strerror(errno));
|
||||
}
|
||||
fclose(o);
|
||||
}
|
|
@ -0,0 +1,289 @@
|
|||
// Copyright (c) the JPEG XL Project 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 <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
bool error_msg(const char* message) {
|
||||
fprintf(stderr, "%s\n", message);
|
||||
return false;
|
||||
}
|
||||
#define return_on_error(X) \
|
||||
if (!X) return false;
|
||||
|
||||
size_t Log2(uint32_t value) { return 31 - __builtin_clz(value); }
|
||||
|
||||
struct HeaderPNM {
|
||||
size_t xsize;
|
||||
size_t ysize;
|
||||
bool is_gray; // PGM
|
||||
bool has_alpha; // PAM
|
||||
size_t bits_per_sample;
|
||||
};
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
explicit Parser(uint8_t* data, size_t length)
|
||||
: pos_(data), end_(data + length) {}
|
||||
|
||||
// Sets "pos" to the first non-header byte/pixel on success.
|
||||
bool ParseHeader(HeaderPNM* header, const uint8_t** pos) {
|
||||
// codec.cc ensures we have at least two bytes => no range check here.
|
||||
if (pos_[0] != 'P') return false;
|
||||
const uint8_t type = pos_[1];
|
||||
pos_ += 2;
|
||||
|
||||
switch (type) {
|
||||
case '5':
|
||||
header->is_gray = true;
|
||||
return ParseHeaderPNM(header, pos);
|
||||
|
||||
case '6':
|
||||
header->is_gray = false;
|
||||
return ParseHeaderPNM(header, pos);
|
||||
|
||||
case '7':
|
||||
return ParseHeaderPAM(header, pos);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exposed for testing
|
||||
bool ParseUnsigned(size_t* number) {
|
||||
if (pos_ == end_) return error_msg("PNM: reached end before number");
|
||||
if (!IsDigit(*pos_)) return error_msg("PNM: expected unsigned number");
|
||||
|
||||
*number = 0;
|
||||
while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
|
||||
*number *= 10;
|
||||
*number += *pos_ - '0';
|
||||
++pos_;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseSigned(double* number) {
|
||||
if (pos_ == end_) return error_msg("PNM: reached end before signed");
|
||||
|
||||
if (*pos_ != '-' && *pos_ != '+' && !IsDigit(*pos_)) {
|
||||
return error_msg("PNM: expected signed number");
|
||||
}
|
||||
|
||||
// Skip sign
|
||||
const bool is_neg = *pos_ == '-';
|
||||
if (is_neg || *pos_ == '+') {
|
||||
++pos_;
|
||||
if (pos_ == end_) return error_msg("PNM: reached end before digits");
|
||||
}
|
||||
|
||||
// Leading digits
|
||||
*number = 0.0;
|
||||
while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
|
||||
*number *= 10;
|
||||
*number += *pos_ - '0';
|
||||
++pos_;
|
||||
}
|
||||
|
||||
// Decimal places?
|
||||
if (pos_ < end_ && *pos_ == '.') {
|
||||
++pos_;
|
||||
double place = 0.1;
|
||||
while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
|
||||
*number += (*pos_ - '0') * place;
|
||||
place *= 0.1;
|
||||
++pos_;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_neg) *number = -*number;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool IsDigit(const uint8_t c) { return '0' <= c && c <= '9'; }
|
||||
static bool IsLineBreak(const uint8_t c) { return c == '\r' || c == '\n'; }
|
||||
static bool IsWhitespace(const uint8_t c) {
|
||||
return IsLineBreak(c) || c == '\t' || c == ' ';
|
||||
}
|
||||
|
||||
bool SkipBlank() {
|
||||
if (pos_ == end_) return error_msg("PNM: reached end before blank");
|
||||
const uint8_t c = *pos_;
|
||||
if (c != ' ' && c != '\n') return error_msg("PNM: expected blank");
|
||||
++pos_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipSingleWhitespace() {
|
||||
if (pos_ == end_) return error_msg("PNM: reached end before whitespace");
|
||||
if (!IsWhitespace(*pos_)) return error_msg("PNM: expected whitespace");
|
||||
++pos_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipWhitespace() {
|
||||
if (pos_ == end_) return error_msg("PNM: reached end before whitespace");
|
||||
if (!IsWhitespace(*pos_) && *pos_ != '#') {
|
||||
return error_msg("PNM: expected whitespace/comment");
|
||||
}
|
||||
|
||||
while (pos_ < end_ && IsWhitespace(*pos_)) {
|
||||
++pos_;
|
||||
}
|
||||
|
||||
// Comment(s)
|
||||
while (pos_ != end_ && *pos_ == '#') {
|
||||
while (pos_ != end_ && !IsLineBreak(*pos_)) {
|
||||
++pos_;
|
||||
}
|
||||
// Newline(s)
|
||||
while (pos_ != end_ && IsLineBreak(*pos_)) pos_++;
|
||||
}
|
||||
|
||||
while (pos_ < end_ && IsWhitespace(*pos_)) {
|
||||
++pos_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MatchString(const char* keyword) {
|
||||
const uint8_t* ppos = pos_;
|
||||
while (*keyword) {
|
||||
if (ppos >= end_) return error_msg("PAM: unexpected end of input");
|
||||
if (*keyword != *ppos) return false;
|
||||
ppos++;
|
||||
keyword++;
|
||||
}
|
||||
pos_ = ppos;
|
||||
return_on_error(SkipWhitespace());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseHeaderPAM(HeaderPNM* header, const uint8_t** pos) {
|
||||
size_t num_channels = 3;
|
||||
size_t max_val = 255;
|
||||
while (!MatchString("ENDHDR")) {
|
||||
return_on_error(SkipWhitespace());
|
||||
if (MatchString("WIDTH")) {
|
||||
return_on_error(ParseUnsigned(&header->xsize));
|
||||
} else if (MatchString("HEIGHT")) {
|
||||
return_on_error(ParseUnsigned(&header->ysize));
|
||||
} else if (MatchString("DEPTH")) {
|
||||
return_on_error(ParseUnsigned(&num_channels));
|
||||
} else if (MatchString("MAXVAL")) {
|
||||
return_on_error(ParseUnsigned(&max_val));
|
||||
} else if (MatchString("TUPLTYPE")) {
|
||||
if (MatchString("RGB_ALPHA")) {
|
||||
header->has_alpha = true;
|
||||
} else if (MatchString("RGB")) {
|
||||
} else if (MatchString("GRAYSCALE_ALPHA")) {
|
||||
header->has_alpha = true;
|
||||
header->is_gray = true;
|
||||
} else if (MatchString("GRAYSCALE")) {
|
||||
header->is_gray = true;
|
||||
} else if (MatchString("BLACKANDWHITE_ALPHA")) {
|
||||
header->has_alpha = true;
|
||||
header->is_gray = true;
|
||||
max_val = 1;
|
||||
} else if (MatchString("BLACKANDWHITE")) {
|
||||
header->is_gray = true;
|
||||
max_val = 1;
|
||||
} else {
|
||||
return error_msg("PAM: unknown TUPLTYPE");
|
||||
}
|
||||
} else {
|
||||
return error_msg("PAM: unknown header keyword");
|
||||
}
|
||||
}
|
||||
if (num_channels !=
|
||||
(header->has_alpha ? 1 : 0) + (header->is_gray ? 1 : 3)) {
|
||||
return error_msg("PAM: bad DEPTH");
|
||||
}
|
||||
if (max_val == 0 || max_val >= 65536) {
|
||||
return error_msg("PAM: bad MAXVAL");
|
||||
}
|
||||
header->bits_per_sample = Log2(max_val + 1);
|
||||
|
||||
*pos = pos_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseHeaderPNM(HeaderPNM* header, const uint8_t** pos) {
|
||||
return_on_error(SkipWhitespace());
|
||||
return_on_error(ParseUnsigned(&header->xsize));
|
||||
|
||||
return_on_error(SkipWhitespace());
|
||||
return_on_error(ParseUnsigned(&header->ysize));
|
||||
|
||||
return_on_error(SkipWhitespace());
|
||||
size_t max_val;
|
||||
return_on_error(ParseUnsigned(&max_val));
|
||||
if (max_val == 0 || max_val >= 65536) {
|
||||
return error_msg("PNM: bad MaxVal");
|
||||
}
|
||||
header->bits_per_sample = Log2(max_val + 1);
|
||||
|
||||
return_on_error(SkipSingleWhitespace());
|
||||
|
||||
*pos = pos_;
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint8_t* pos_;
|
||||
const uint8_t* const end_;
|
||||
};
|
||||
|
||||
bool load_file(unsigned char** out, size_t* outsize, const char* filename) {
|
||||
FILE* file;
|
||||
file = fopen(filename, "rb");
|
||||
if (!file) return false;
|
||||
if (fseek(file, 0, SEEK_END) != 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
*outsize = ftell(file);
|
||||
if (*outsize == LONG_MAX || *outsize < 9 || fseek(file, 0, SEEK_SET)) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
*out = (unsigned char*)malloc(*outsize);
|
||||
if (!(*out)) return false;
|
||||
size_t readsize;
|
||||
readsize = fread(*out, 1, *outsize, file);
|
||||
fclose(file);
|
||||
if (readsize != *outsize) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodePAM(const char* filename, uint8_t** buffer, size_t* w, size_t* h,
|
||||
size_t* nb_chans, size_t* bitdepth) {
|
||||
unsigned char* in_file;
|
||||
size_t in_size;
|
||||
if (!load_file(&in_file, &in_size, filename))
|
||||
return error_msg("Could not read input file");
|
||||
Parser parser(in_file, in_size);
|
||||
HeaderPNM header = {};
|
||||
const uint8_t* pos = nullptr;
|
||||
if (!parser.ParseHeader(&header, &pos)) return false;
|
||||
|
||||
if (header.bits_per_sample == 0 || header.bits_per_sample > 12) {
|
||||
return error_msg("PNM: bits_per_sample invalid (can do at most 12-bit)");
|
||||
}
|
||||
*w = header.xsize;
|
||||
*h = header.ysize;
|
||||
*bitdepth = header.bits_per_sample;
|
||||
*nb_chans = (header.is_gray ? 1 : 3) + (header.has_alpha ? 1 : 0);
|
||||
|
||||
size_t pnm_remaining_size = in_file + in_size - pos;
|
||||
size_t buffer_size = *w * *h * *nb_chans * (*bitdepth > 8 ? 2 : 1);
|
||||
if (pnm_remaining_size < buffer_size) {
|
||||
return error_msg("PNM file too small");
|
||||
}
|
||||
*buffer = (uint8_t*)malloc(buffer_size);
|
||||
memcpy(*buffer, pos, buffer_size);
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
APNG Disassembler 2.8
|
||||
|
||||
Deconstructs APNG files into individual frames.
|
||||
|
||||
http://apngdis.sourceforge.net
|
||||
|
||||
Copyright (c) 2010-2015 Max Stepin
|
||||
maxst at users.sourceforge.net
|
||||
|
||||
zlib license
|
||||
------------
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
|
@ -46,6 +46,7 @@
|
|||
#include "jxl/encode.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
#include "lib/jxl/base/scope_guard.h"
|
||||
#include "lib/jxl/common.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
#include "png.h" /* original (unpatched) libpng is ok */
|
||||
|
@ -315,7 +316,6 @@ int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr,
|
|||
if (!png_ptr || !info_ptr) return 1;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -339,7 +339,6 @@ int processing_data(png_structp png_ptr, png_infop info_ptr, unsigned char* p,
|
|||
if (!png_ptr || !info_ptr) return 1;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -354,7 +353,6 @@ int processing_finish(png_structp png_ptr, png_infop info_ptr,
|
|||
if (!png_ptr || !info_ptr) return 1;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -367,8 +365,6 @@ int processing_finish(png_structp png_ptr, png_infop info_ptr,
|
|||
(void)BlobsReaderPNG::Decode(text_ptr[i], metadata);
|
||||
}
|
||||
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -382,20 +378,37 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
|||
unsigned int id, j, w, h, w0, h0, x0, y0;
|
||||
unsigned int delay_num, delay_den, dop, bop, rowbytes, imagesize;
|
||||
unsigned char sig[8];
|
||||
png_structp png_ptr;
|
||||
png_infop info_ptr;
|
||||
png_structp png_ptr = nullptr;
|
||||
png_infop info_ptr = nullptr;
|
||||
PaddedBytes chunk;
|
||||
PaddedBytes chunkIHDR;
|
||||
std::vector<PaddedBytes> chunksInfo;
|
||||
bool isAnimated = false;
|
||||
bool skipFirst = false;
|
||||
bool hasInfo = false;
|
||||
bool all_dispose_bg = true;
|
||||
APNGFrame frameRaw = {};
|
||||
uint32_t num_channels;
|
||||
JxlPixelFormat format;
|
||||
unsigned int bytes_per_pixel = 0;
|
||||
|
||||
struct FrameInfo {
|
||||
PackedImage data;
|
||||
uint32_t duration;
|
||||
size_t x0, xsize;
|
||||
size_t y0, ysize;
|
||||
uint32_t dispose_op;
|
||||
uint32_t blend_op;
|
||||
};
|
||||
|
||||
std::vector<FrameInfo> frames;
|
||||
|
||||
// Make sure png memory is released in any case.
|
||||
auto scope_guard = MakeScopeGuard([&]() {
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
// Just in case. Not all versions on libpng wipe-out the pointers.
|
||||
png_ptr = nullptr;
|
||||
info_ptr = nullptr;
|
||||
});
|
||||
|
||||
r = {bytes.data(), bytes.data() + bytes.size()};
|
||||
// Not a PNG => not an error
|
||||
unsigned char png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10};
|
||||
|
@ -434,14 +447,12 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
|||
|
||||
if (!processing_start(png_ptr, info_ptr, (void*)&frameRaw, hasInfo,
|
||||
chunkIHDR, chunksInfo)) {
|
||||
bool last_base_was_none = true;
|
||||
while (!r.Eof()) {
|
||||
id = read_chunk(&r, &chunk);
|
||||
if (!id) break;
|
||||
|
||||
if (id == kId_acTL && !hasInfo && !isAnimated) {
|
||||
isAnimated = true;
|
||||
skipFirst = true;
|
||||
ppf->info.have_animation = true;
|
||||
ppf->info.animation.tps_numerator = 1000;
|
||||
ppf->info.animation.tps_denominator = 1;
|
||||
|
@ -450,38 +461,12 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
|||
if (hasInfo) {
|
||||
if (!processing_finish(png_ptr, info_ptr, &ppf->metadata)) {
|
||||
// Allocates the frame buffer.
|
||||
ppf->frames.emplace_back(w0, h0, format);
|
||||
auto* frame = &ppf->frames.back();
|
||||
|
||||
frame->frame_info.duration = delay_num * 1000 / delay_den;
|
||||
frame->x0 = x0;
|
||||
frame->y0 = y0;
|
||||
// TODO(veluca): this could in principle be implemented.
|
||||
if (last_base_was_none && !all_dispose_bg &&
|
||||
(x0 != 0 || y0 != 0 || w0 != w || h0 != h || bop != 0)) {
|
||||
return JXL_FAILURE(
|
||||
"APNG with dispose-to-0 is not supported for non-full or "
|
||||
"blended frames");
|
||||
}
|
||||
switch (dop) {
|
||||
case 0:
|
||||
frame->use_for_next_frame = true;
|
||||
last_base_was_none = false;
|
||||
all_dispose_bg = false;
|
||||
break;
|
||||
case 2:
|
||||
frame->use_for_next_frame = false;
|
||||
all_dispose_bg = false;
|
||||
break;
|
||||
default:
|
||||
frame->use_for_next_frame = false;
|
||||
last_base_was_none = true;
|
||||
}
|
||||
frame->blend = bop != 0;
|
||||
|
||||
uint32_t duration = delay_num * 1000 / delay_den;
|
||||
frames.push_back(FrameInfo{PackedImage(w0, h0, format), duration,
|
||||
x0, w0, y0, h0, dop, bop});
|
||||
auto& frame = frames.back().data;
|
||||
for (size_t y = 0; y < h0; ++y) {
|
||||
memcpy(static_cast<uint8_t*>(frame->color.pixels()) +
|
||||
frame->color.stride * y,
|
||||
memcpy(static_cast<uint8_t*>(frame.pixels()) + frame.stride * y,
|
||||
frameRaw.rows[y], bytes_per_pixel * w0);
|
||||
}
|
||||
} else {
|
||||
|
@ -493,6 +478,11 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
|||
errorstate = false;
|
||||
break;
|
||||
}
|
||||
if (chunk.size() < 34) {
|
||||
return JXL_FAILURE("Received a chunk that is too small (%" PRIuS
|
||||
"B)",
|
||||
chunk.size());
|
||||
}
|
||||
// At this point the old frame is done. Let's start a new one.
|
||||
w0 = png_get_uint_32(chunk.data() + 12);
|
||||
h0 = png_get_uint_32(chunk.data() + 16);
|
||||
|
@ -517,13 +507,6 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
|||
chunkIHDR, chunksInfo)) {
|
||||
break;
|
||||
}
|
||||
|
||||
} else
|
||||
skipFirst = false;
|
||||
|
||||
if (ppf->frames.size() == (skipFirst ? 1 : 0)) {
|
||||
bop = 0;
|
||||
if (dop == 2) dop = 1;
|
||||
}
|
||||
} else if (id == kId_IDAT) {
|
||||
// First IDAT chunk means we now have all header info
|
||||
|
@ -552,7 +535,13 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
|||
if (colortype & 4 ||
|
||||
png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
|
||||
ppf->info.alpha_bits = ppf->info.bits_per_sample;
|
||||
if (sigbits) ppf->info.alpha_bits = sigbits->alpha;
|
||||
if (sigbits) {
|
||||
if (sigbits->alpha &&
|
||||
sigbits->alpha != ppf->info.bits_per_sample) {
|
||||
return JXL_FAILURE("Unsupported alpha bit-depth");
|
||||
}
|
||||
ppf->info.alpha_bits = sigbits->alpha;
|
||||
}
|
||||
} else {
|
||||
ppf->info.alpha_bits = 0;
|
||||
}
|
||||
|
@ -650,6 +639,110 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
|||
}
|
||||
|
||||
if (errorstate) return false;
|
||||
|
||||
bool has_nontrivial_background = false;
|
||||
bool previous_frame_should_be_cleared = false;
|
||||
enum {
|
||||
DISPOSE_OP_NONE = 0,
|
||||
DISPOSE_OP_BACKGROUND = 1,
|
||||
DISPOSE_OP_PREVIOUS = 2,
|
||||
};
|
||||
enum {
|
||||
BLEND_OP_SOURCE = 0,
|
||||
BLEND_OP_OVER = 1,
|
||||
};
|
||||
for (size_t i = 0; i < frames.size(); i++) {
|
||||
auto& frame = frames[i];
|
||||
JXL_ASSERT(frame.data.xsize == frame.xsize);
|
||||
JXL_ASSERT(frame.data.ysize == frame.ysize);
|
||||
|
||||
// Before encountering a DISPOSE_OP_NONE frame, the canvas is filled with 0,
|
||||
// so DISPOSE_OP_BACKGROUND and DISPOSE_OP_PREVIOUS are equivalent.
|
||||
if (frame.dispose_op == DISPOSE_OP_NONE) {
|
||||
has_nontrivial_background = true;
|
||||
}
|
||||
bool should_blend = frame.blend_op == BLEND_OP_OVER;
|
||||
bool use_for_next_frame =
|
||||
has_nontrivial_background && frame.dispose_op != DISPOSE_OP_PREVIOUS;
|
||||
size_t x0 = frame.x0;
|
||||
size_t y0 = frame.y0;
|
||||
|
||||
if (previous_frame_should_be_cleared) {
|
||||
size_t xs = frame.data.xsize;
|
||||
size_t ys = frame.data.ysize;
|
||||
size_t px0 = frames[i - 1].x0;
|
||||
size_t py0 = frames[i - 1].y0;
|
||||
size_t pxs = frames[i - 1].xsize;
|
||||
size_t pys = frames[i - 1].ysize;
|
||||
if (px0 >= x0 && py0 >= y0 && px0 + pxs <= x0 + xs &&
|
||||
py0 + pys <= y0 + ys && frame.blend_op == BLEND_OP_SOURCE &&
|
||||
use_for_next_frame) {
|
||||
// If the previous frame is entirely contained in the current frame and
|
||||
// we are using BLEND_OP_SOURCE, nothing special needs to be done.
|
||||
ppf->frames.emplace_back(std::move(frame.data));
|
||||
} else if (px0 == x0 && py0 == y0 && px0 + pxs == x0 + xs &&
|
||||
py0 + pys == y0 + ys && use_for_next_frame) {
|
||||
// If the new frame has the same size as the old one, but we are
|
||||
// blending, we can instead just not blend.
|
||||
should_blend = false;
|
||||
ppf->frames.emplace_back(std::move(frame.data));
|
||||
} else if (px0 <= x0 && py0 <= y0 && px0 + pxs >= x0 + xs &&
|
||||
py0 + pys >= y0 + ys && use_for_next_frame) {
|
||||
// If the new frame is contained within the old frame, we can pad the
|
||||
// new frame with zeros and not blend.
|
||||
PackedImage new_data(pxs, pys, frame.data.format);
|
||||
memset(new_data.pixels(), 0, new_data.pixels_size);
|
||||
for (size_t y = 0; y < ys; y++) {
|
||||
size_t bytes_per_pixel =
|
||||
PackedImage::BitsPerChannel(new_data.format.data_type) *
|
||||
new_data.format.num_channels / 8;
|
||||
memcpy(static_cast<uint8_t*>(new_data.pixels()) +
|
||||
new_data.stride * (y + y0 - py0) +
|
||||
bytes_per_pixel * (x0 - px0),
|
||||
static_cast<const uint8_t*>(frame.data.pixels()) +
|
||||
frame.data.stride * y,
|
||||
xs * bytes_per_pixel);
|
||||
}
|
||||
|
||||
x0 = px0;
|
||||
y0 = py0;
|
||||
should_blend = false;
|
||||
ppf->frames.emplace_back(std::move(new_data));
|
||||
} else {
|
||||
// If all else fails, insert a dummy blank frame with kReplace.
|
||||
PackedImage blank(pxs, pys, frame.data.format);
|
||||
memset(blank.pixels(), 0, blank.pixels_size);
|
||||
ppf->frames.emplace_back(std::move(blank));
|
||||
auto& pframe = ppf->frames.back();
|
||||
pframe.x0 = px0;
|
||||
pframe.y0 = py0;
|
||||
pframe.frame_info.duration = 0;
|
||||
pframe.blend = false;
|
||||
pframe.use_for_next_frame = true;
|
||||
|
||||
ppf->frames.emplace_back(std::move(frame.data));
|
||||
}
|
||||
} else {
|
||||
ppf->frames.emplace_back(std::move(frame.data));
|
||||
}
|
||||
|
||||
auto& pframe = ppf->frames.back();
|
||||
pframe.x0 = x0;
|
||||
pframe.y0 = y0;
|
||||
pframe.frame_info.duration = frame.duration;
|
||||
pframe.blend = should_blend;
|
||||
pframe.use_for_next_frame = use_for_next_frame;
|
||||
|
||||
if (has_nontrivial_background &&
|
||||
frame.dispose_op == DISPOSE_OP_BACKGROUND) {
|
||||
previous_frame_should_be_cleared = true;
|
||||
} else {
|
||||
previous_frame_should_be_cleared = false;
|
||||
}
|
||||
}
|
||||
if (ppf->frames.empty()) return JXL_FAILURE("No frames decoded");
|
||||
ppf->frames.back().frame_info.is_last = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -214,8 +214,8 @@ Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
|||
|
||||
if (ppf->info.have_animation) {
|
||||
frame->frame_info.duration = gcb.DelayTime;
|
||||
frame->x0 = image_rect.x0();
|
||||
frame->y0 = image_rect.y0();
|
||||
frame->x0 = total_rect.x0();
|
||||
frame->y0 = total_rect.y0();
|
||||
if (last_base_was_none) {
|
||||
replace = true;
|
||||
}
|
||||
|
|
|
@ -122,8 +122,8 @@ Status EncodeImagePNM(const CodecInOut* io, const ColorEncoding& c_desired,
|
|||
const ImageBundle* transformed;
|
||||
JXL_RETURN_IF_ERROR(TransformIfNeeded(
|
||||
*to_color_transform, c_desired, GetJxlCms(), pool, &store, &transformed));
|
||||
size_t stride = ib.oriented_xsize() *
|
||||
(c_desired.Channels() * bits_per_sample) / kBitsPerByte;
|
||||
size_t bytes_per_sample = floating_point ? 4 : bits_per_sample > 8 ? 2 : 1;
|
||||
size_t stride = ib.oriented_xsize() * c_desired.Channels() * bytes_per_sample;
|
||||
PaddedBytes pixels(stride * ib.oriented_ysize());
|
||||
JXL_RETURN_IF_ERROR(ConvertToExternal(
|
||||
*transformed, bits_per_sample, floating_point, c_desired.Channels(),
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "jxl/codestream_header.h"
|
||||
#include "jxl/encode.h"
|
||||
#include "jxl/types.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/common.h"
|
||||
|
@ -69,7 +70,6 @@ class PackedImage {
|
|||
JxlPixelFormat format;
|
||||
size_t pixels_size;
|
||||
|
||||
private:
|
||||
static size_t BitsPerChannel(JxlDataType data_type) {
|
||||
switch (data_type) {
|
||||
case JXL_TYPE_BOOLEAN:
|
||||
|
@ -89,6 +89,7 @@ class PackedImage {
|
|||
return 0; // Indicate invalid data type.
|
||||
}
|
||||
|
||||
private:
|
||||
static size_t CalcStride(const JxlPixelFormat& format, size_t xsize) {
|
||||
size_t stride = xsize * (BitsPerChannel(format.data_type) *
|
||||
format.num_channels / jxl::kBitsPerByte);
|
||||
|
@ -108,7 +109,7 @@ class PackedImage {
|
|||
class PackedFrame {
|
||||
public:
|
||||
template <typename... Args>
|
||||
PackedFrame(Args... args) : color(args...) {}
|
||||
explicit PackedFrame(Args&&... args) : color(std::forward<Args>(args)...) {}
|
||||
|
||||
// The Frame metadata.
|
||||
JxlFrameHeader frame_info = {};
|
||||
|
@ -163,6 +164,7 @@ class PackedPixelFile {
|
|||
std::vector<PackedFrame> frames;
|
||||
|
||||
PackedMetadata metadata;
|
||||
PackedPixelFile() { JxlEncoderInitBasicInfo(&info); };
|
||||
};
|
||||
|
||||
} // namespace extras
|
||||
|
|
|
@ -5,32 +5,21 @@
|
|||
|
||||
#include "lib/extras/packed_image_convert.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "jxl/color_encoding.h"
|
||||
#include "jxl/types.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/color_management.h"
|
||||
#include "lib/jxl/dec_external_image.h"
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
#include "lib/jxl/enc_external_image.h"
|
||||
#include "lib/jxl/enc_image_bundle.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
namespace {
|
||||
size_t BitsPerSample(JxlDataType data_type) {
|
||||
switch (data_type) {
|
||||
case JXL_TYPE_BOOLEAN:
|
||||
return 1;
|
||||
case JXL_TYPE_UINT8:
|
||||
return 8;
|
||||
case JXL_TYPE_UINT16:
|
||||
return 16;
|
||||
case JXL_TYPE_UINT32:
|
||||
return 32;
|
||||
case JXL_TYPE_FLOAT:
|
||||
return 32;
|
||||
case JXL_TYPE_FLOAT16:
|
||||
return 16;
|
||||
// No default, give compiler error if new type not handled.
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
|
||||
ThreadPool* pool, CodecInOut* io) {
|
||||
const bool has_alpha = ppf.info.alpha_bits != 0;
|
||||
|
@ -97,7 +86,8 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
|
|||
io->frames.clear();
|
||||
for (const auto& frame : ppf.frames) {
|
||||
JXL_ASSERT(frame.color.pixels() != nullptr);
|
||||
size_t frame_bits_per_sample = BitsPerSample(frame.color.format.data_type);
|
||||
size_t frame_bits_per_sample =
|
||||
frame.color.BitsPerChannel(frame.color.format.data_type);
|
||||
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.
|
||||
|
@ -134,7 +124,7 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
|
|||
/*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));
|
||||
/*float_in=*/float_in, /*align=*/0));
|
||||
|
||||
// TODO(deymo): Convert the extra channels. FIXME!
|
||||
JXL_CHECK(frame.extra_channels.empty());
|
||||
|
@ -156,5 +146,112 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
|
|||
return true;
|
||||
}
|
||||
|
||||
// Allows converting from internal CodecInOut to external PackedPixelFile
|
||||
Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
|
||||
const JxlPixelFormat& pixel_format,
|
||||
const ColorEncoding& c_desired,
|
||||
ThreadPool* pool,
|
||||
PackedPixelFile* ppf) {
|
||||
const bool has_alpha = io.metadata.m.HasAlpha();
|
||||
bool alpha_premultiplied = false;
|
||||
JXL_ASSERT(!io.frames.empty());
|
||||
|
||||
if (has_alpha) {
|
||||
JXL_ASSERT(io.metadata.m.GetAlphaBits() ==
|
||||
io.metadata.m.bit_depth.bits_per_sample);
|
||||
const auto* alpha_channel = io.metadata.m.Find(ExtraChannel::kAlpha);
|
||||
JXL_ASSERT(alpha_channel->bit_depth.exponent_bits_per_sample ==
|
||||
io.metadata.m.bit_depth.exponent_bits_per_sample);
|
||||
alpha_premultiplied = alpha_channel->alpha_associated;
|
||||
}
|
||||
|
||||
// Convert the image metadata
|
||||
ppf->info.xsize = io.metadata.size.xsize();
|
||||
ppf->info.ysize = io.metadata.size.ysize();
|
||||
ppf->info.num_color_channels = io.metadata.m.color_encoding.Channels();
|
||||
ppf->info.bits_per_sample = io.metadata.m.bit_depth.bits_per_sample;
|
||||
ppf->info.exponent_bits_per_sample =
|
||||
io.metadata.m.bit_depth.exponent_bits_per_sample;
|
||||
|
||||
ppf->info.alpha_bits = io.metadata.m.GetAlphaBits();
|
||||
ppf->info.alpha_premultiplied = alpha_premultiplied;
|
||||
|
||||
ppf->info.uses_original_profile = !io.metadata.m.xyb_encoded;
|
||||
JXL_ASSERT(0 < io.metadata.m.orientation && io.metadata.m.orientation <= 8);
|
||||
ppf->info.orientation =
|
||||
static_cast<JxlOrientation>(io.metadata.m.orientation);
|
||||
ppf->info.num_color_channels = io.metadata.m.color_encoding.Channels();
|
||||
|
||||
// Convert animation metadata
|
||||
JXL_ASSERT(io.frames.size() == 1 || io.metadata.m.have_animation);
|
||||
ppf->info.have_animation = io.metadata.m.have_animation;
|
||||
ppf->info.animation.tps_numerator = io.metadata.m.animation.tps_numerator;
|
||||
ppf->info.animation.tps_denominator = io.metadata.m.animation.tps_denominator;
|
||||
ppf->info.animation.num_loops = io.metadata.m.animation.num_loops;
|
||||
|
||||
// Convert the color encoding
|
||||
ppf->icc.assign(c_desired.ICC().begin(), c_desired.ICC().end());
|
||||
if (ppf->icc.empty()) {
|
||||
ConvertInternalToExternalColorEncoding(c_desired, &ppf->color_encoding);
|
||||
}
|
||||
|
||||
// Convert the extra blobs
|
||||
ppf->metadata.exif.assign(io.blobs.exif.begin(), io.blobs.exif.end());
|
||||
ppf->metadata.iptc.assign(io.blobs.iptc.begin(), io.blobs.iptc.end());
|
||||
ppf->metadata.jumbf.assign(io.blobs.jumbf.begin(), io.blobs.jumbf.end());
|
||||
ppf->metadata.xmp.assign(io.blobs.xmp.begin(), io.blobs.xmp.end());
|
||||
const bool float_out = pixel_format.data_type == JXL_TYPE_FLOAT ||
|
||||
pixel_format.data_type == JXL_TYPE_FLOAT16;
|
||||
// Convert the pixels
|
||||
ppf->frames.clear();
|
||||
for (const auto& frame : io.frames) {
|
||||
size_t frame_bits_per_sample = frame.metadata()->bit_depth.bits_per_sample;
|
||||
JXL_ASSERT(frame_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 =
|
||||
frame.metadata()->color_encoding.Channels() + has_alpha;
|
||||
JxlPixelFormat format{/*num_channels=*/num_channels,
|
||||
/*data_type=*/pixel_format.data_type,
|
||||
/*endianness=*/pixel_format.endianness,
|
||||
/*align=*/pixel_format.align};
|
||||
|
||||
PackedFrame packed_frame(frame.oriented_xsize(), frame.oriented_ysize(),
|
||||
format);
|
||||
const size_t bits_per_sample =
|
||||
packed_frame.color.BitsPerChannel(pixel_format.data_type);
|
||||
packed_frame.name = frame.name;
|
||||
packed_frame.frame_info.name_length = frame.name.size();
|
||||
// Color transform
|
||||
ImageBundle ib = frame.Copy();
|
||||
const ImageBundle* to_color_transform = &ib;
|
||||
ImageMetadata metadata = io.metadata.m;
|
||||
ImageBundle store(&metadata);
|
||||
const ImageBundle* transformed;
|
||||
// TODO(firsching): handle the transform here.
|
||||
JXL_RETURN_IF_ERROR(TransformIfNeeded(*to_color_transform, c_desired,
|
||||
GetJxlCms(), pool, &store,
|
||||
&transformed));
|
||||
size_t stride = ib.oriented_xsize() *
|
||||
(c_desired.Channels() * ppf->info.bits_per_sample) /
|
||||
kBitsPerByte;
|
||||
PaddedBytes pixels(stride * ib.oriented_ysize());
|
||||
|
||||
JXL_RETURN_IF_ERROR(ConvertToExternal(
|
||||
*transformed, bits_per_sample, float_out, format.num_channels,
|
||||
format.endianness,
|
||||
/* stride_out=*/packed_frame.color.stride, pool,
|
||||
packed_frame.color.pixels(), packed_frame.color.pixels_size,
|
||||
/*out_callback=*/nullptr, /*out_opaque=*/nullptr,
|
||||
frame.metadata()->GetOrientation()));
|
||||
|
||||
// TODO(firsching): Convert the extra channels, beside one potential alpha
|
||||
// channel. FIXME!
|
||||
JXL_CHECK(frame.extra_channels().size() <= has_alpha);
|
||||
ppf->frames.push_back(std::move(packed_frame));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
// Helper functions to convert from the external image types to the internal
|
||||
// CodecInOut to help transitioning to the external types.
|
||||
|
||||
#include "jxl/types.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
@ -21,6 +22,13 @@ namespace extras {
|
|||
Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
|
||||
ThreadPool* pool, CodecInOut* io);
|
||||
|
||||
// Converts an internal CodecInOut for use with internal function to an external
|
||||
// PackedPixelFile.
|
||||
Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
|
||||
const JxlPixelFormat& pixel_format,
|
||||
const ColorEncoding& c_desired,
|
||||
ThreadPool* pool,
|
||||
PackedPixelFile* ppf);
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
|
|
|
@ -272,6 +272,17 @@ typedef enum {
|
|||
*/
|
||||
JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL = 30,
|
||||
|
||||
/** Prepare the frame for indexing in the frame index box.
|
||||
* 0 = ignore this frame (same as not setting a value),
|
||||
* 1 = index this frame within the Frame Index Box.
|
||||
* If any frames are indexed, the first frame needs to
|
||||
* be indexed, too. If the first frame is not indexed, and
|
||||
* a later frame is attempted to be indexed, JXL_ENC_ERROR will occur.
|
||||
* If non-keyframes, i.e., frames with cropping, blending or patches are
|
||||
* attempted to be indexed, JXL_ENC_ERROR will occur.
|
||||
*/
|
||||
JXL_ENC_FRAME_INDEX_BOX = 31,
|
||||
|
||||
/** Enum value not to be used as an option. This value is added to force the
|
||||
* C compiler to have the enum to take a known size.
|
||||
*/
|
||||
|
@ -818,7 +829,6 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
|
|||
* @param type type of the extra channel.
|
||||
* @param info global extra channel metadata. Object owned by the caller and its
|
||||
* contents are copied internally.
|
||||
* @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error
|
||||
*/
|
||||
JXL_EXPORT void JxlEncoderInitExtraChannelInfo(JxlExtraChannelType type,
|
||||
JxlExtraChannelInfo* info);
|
||||
|
|
|
@ -39,6 +39,7 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
|
|||
jxl/base/profiler.h
|
||||
jxl/base/random.cc
|
||||
jxl/base/random.h
|
||||
jxl/base/scope_guard.h
|
||||
jxl/base/span.h
|
||||
jxl/base/status.cc
|
||||
jxl/base/status.h
|
||||
|
@ -196,6 +197,8 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
|
|||
jxl/render_pipeline/render_pipeline_stage.h
|
||||
jxl/render_pipeline/simple_render_pipeline.cc
|
||||
jxl/render_pipeline/simple_render_pipeline.h
|
||||
jxl/render_pipeline/stage_blending.cc
|
||||
jxl/render_pipeline/stage_blending.h
|
||||
jxl/render_pipeline/stage_chroma_upsampling.cc
|
||||
jxl/render_pipeline/stage_chroma_upsampling.h
|
||||
jxl/render_pipeline/stage_epf.cc
|
||||
|
@ -208,10 +211,12 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
|
|||
jxl/render_pipeline/stage_patches.h
|
||||
jxl/render_pipeline/stage_splines.cc
|
||||
jxl/render_pipeline/stage_splines.h
|
||||
jxl/render_pipeline/stage_spot.cc
|
||||
jxl/render_pipeline/stage_spot.h
|
||||
jxl/render_pipeline/stage_upsampling.cc
|
||||
jxl/render_pipeline/stage_upsampling.h
|
||||
jxl/render_pipeline/stage_write_to_ib.cc
|
||||
jxl/render_pipeline/stage_write_to_ib.h
|
||||
jxl/render_pipeline/stage_write.cc
|
||||
jxl/render_pipeline/stage_write.h
|
||||
jxl/render_pipeline/stage_xyb.cc
|
||||
jxl/render_pipeline/stage_xyb.h
|
||||
jxl/render_pipeline/stage_ycbcr.cc
|
||||
|
@ -219,6 +224,7 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
|
|||
jxl/render_pipeline/test_render_pipeline_stages.h
|
||||
jxl/sanitizers.h
|
||||
jxl/simd_util-inl.h
|
||||
jxl/size_constraints.h
|
||||
jxl/splines.cc
|
||||
jxl/splines.h
|
||||
jxl/toc.cc
|
||||
|
|
|
@ -23,72 +23,70 @@ namespace jxl {
|
|||
// square block frequency along the (i + j == const) diagonals is roughly the
|
||||
// same. For historical reasons, consecutive diagonals are traversed
|
||||
// in alternating directions - so called "zig-zag" (or "snake") order.
|
||||
AcStrategy::CoeffOrderAndLut::CoeffOrderAndLut() {
|
||||
for (size_t s = 0; s < AcStrategy::kNumValidStrategies; s++) {
|
||||
const AcStrategy acs = AcStrategy::FromRawStrategy(s);
|
||||
size_t cx = acs.covered_blocks_x();
|
||||
size_t cy = acs.covered_blocks_y();
|
||||
CoefficientLayout(&cy, &cx);
|
||||
JXL_ASSERT((AcStrategy::CoeffOrderAndLut::kOffset[s + 1] -
|
||||
AcStrategy::CoeffOrderAndLut::kOffset[s]) == cx * cy);
|
||||
coeff_order_t* JXL_RESTRICT order_start =
|
||||
order + AcStrategy::CoeffOrderAndLut::kOffset[s] * kDCTBlockSize;
|
||||
coeff_order_t* JXL_RESTRICT lut_start =
|
||||
lut + AcStrategy::CoeffOrderAndLut::kOffset[s] * kDCTBlockSize;
|
||||
template <bool is_lut>
|
||||
static void CoeffOrderAndLut(AcStrategy acs, coeff_order_t* out) {
|
||||
size_t cx = acs.covered_blocks_x();
|
||||
size_t cy = acs.covered_blocks_y();
|
||||
CoefficientLayout(&cy, &cx);
|
||||
|
||||
// CoefficientLayout ensures cx >= cy.
|
||||
// We compute the zigzag order for a cx x cx block, then discard all the
|
||||
// lines that are not multiple of the ratio between cx and cy.
|
||||
size_t xs = cx / cy;
|
||||
size_t xsm = xs - 1;
|
||||
size_t xss = CeilLog2Nonzero(xs);
|
||||
// First half of the block
|
||||
size_t cur = cx * cy;
|
||||
for (size_t i = 0; i < cx * kBlockDim; i++) {
|
||||
for (size_t j = 0; j <= i; j++) {
|
||||
size_t x = j;
|
||||
size_t y = i - j;
|
||||
if (i % 2) std::swap(x, y);
|
||||
if ((y & xsm) != 0) continue;
|
||||
y >>= xss;
|
||||
size_t val = 0;
|
||||
if (x < cx && y < cy) {
|
||||
val = y * cx + x;
|
||||
} else {
|
||||
val = cur++;
|
||||
}
|
||||
lut_start[y * cx * kBlockDim + x] = val;
|
||||
order_start[val] = y * cx * kBlockDim + x;
|
||||
// CoefficientLayout ensures cx >= cy.
|
||||
// We compute the zigzag order for a cx x cx block, then discard all the
|
||||
// lines that are not multiple of the ratio between cx and cy.
|
||||
size_t xs = cx / cy;
|
||||
size_t xsm = xs - 1;
|
||||
size_t xss = CeilLog2Nonzero(xs);
|
||||
// First half of the block
|
||||
size_t cur = cx * cy;
|
||||
for (size_t i = 0; i < cx * kBlockDim; i++) {
|
||||
for (size_t j = 0; j <= i; j++) {
|
||||
size_t x = j;
|
||||
size_t y = i - j;
|
||||
if (i % 2) std::swap(x, y);
|
||||
if ((y & xsm) != 0) continue;
|
||||
y >>= xss;
|
||||
size_t val = 0;
|
||||
if (x < cx && y < cy) {
|
||||
val = y * cx + x;
|
||||
} else {
|
||||
val = cur++;
|
||||
}
|
||||
if (is_lut) {
|
||||
out[y * cx * kBlockDim + x] = val;
|
||||
} else {
|
||||
out[val] = y * cx * kBlockDim + x;
|
||||
}
|
||||
}
|
||||
// Second half
|
||||
for (size_t ip = cx * kBlockDim - 1; ip > 0; ip--) {
|
||||
size_t i = ip - 1;
|
||||
for (size_t j = 0; j <= i; j++) {
|
||||
size_t x = cx * kBlockDim - 1 - (i - j);
|
||||
size_t y = cx * kBlockDim - 1 - j;
|
||||
if (i % 2) std::swap(x, y);
|
||||
if ((y & xsm) != 0) continue;
|
||||
y >>= xss;
|
||||
size_t val = cur++;
|
||||
lut_start[y * cx * kBlockDim + x] = val;
|
||||
order_start[val] = y * cx * kBlockDim + x;
|
||||
}
|
||||
// Second half
|
||||
for (size_t ip = cx * kBlockDim - 1; ip > 0; ip--) {
|
||||
size_t i = ip - 1;
|
||||
for (size_t j = 0; j <= i; j++) {
|
||||
size_t x = cx * kBlockDim - 1 - (i - j);
|
||||
size_t y = cx * kBlockDim - 1 - j;
|
||||
if (i % 2) std::swap(x, y);
|
||||
if ((y & xsm) != 0) continue;
|
||||
y >>= xss;
|
||||
size_t val = cur++;
|
||||
if (is_lut) {
|
||||
out[y * cx * kBlockDim + x] = val;
|
||||
} else {
|
||||
out[val] = y * cx * kBlockDim + x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const AcStrategy::CoeffOrderAndLut* AcStrategy::CoeffOrder() {
|
||||
static AcStrategy::CoeffOrderAndLut* order =
|
||||
new AcStrategy::CoeffOrderAndLut();
|
||||
return order;
|
||||
void AcStrategy::ComputeNaturalCoeffOrder(coeff_order_t* order) const {
|
||||
CoeffOrderAndLut</*is_lut=*/false>(*this, order);
|
||||
}
|
||||
void AcStrategy::ComputeNaturalCoeffOrderLut(coeff_order_t* lut) const {
|
||||
CoeffOrderAndLut</*is_lut=*/true>(*this, lut);
|
||||
}
|
||||
|
||||
// These definitions are needed before C++17.
|
||||
constexpr size_t AcStrategy::kMaxCoeffBlocks;
|
||||
constexpr size_t AcStrategy::kMaxBlockDim;
|
||||
constexpr size_t AcStrategy::kMaxCoeffArea;
|
||||
constexpr size_t AcStrategy::CoeffOrderAndLut::kOffset[];
|
||||
|
||||
AcStrategyImage::AcStrategyImage(size_t xsize, size_t ysize)
|
||||
: layers_(xsize, ysize) {
|
||||
|
|
|
@ -133,15 +133,8 @@ class AcStrategy {
|
|||
// Round-trip, for any given strategy s:
|
||||
// X = NaturalCoeffOrder(s)[NaturalCoeffOrderLutN(s)[X]]
|
||||
// X = NaturalCoeffOrderLut(s)[NaturalCoeffOrderN(s)[X]]
|
||||
JXL_INLINE const coeff_order_t* NaturalCoeffOrder() const {
|
||||
return CoeffOrder()->order +
|
||||
CoeffOrderAndLut::kOffset[RawStrategy()] * kDCTBlockSize;
|
||||
}
|
||||
|
||||
JXL_INLINE const coeff_order_t* NaturalCoeffOrderLut() const {
|
||||
return CoeffOrder()->lut +
|
||||
CoeffOrderAndLut::kOffset[RawStrategy()] * kDCTBlockSize;
|
||||
}
|
||||
void ComputeNaturalCoeffOrder(coeff_order_t* order) const;
|
||||
void ComputeNaturalCoeffOrderLut(coeff_order_t* lut) const;
|
||||
|
||||
// Number of 8x8 blocks that this strategy will cover. 0 for non-top-left
|
||||
// blocks inside a multi-block transform.
|
||||
|
@ -172,23 +165,6 @@ class AcStrategy {
|
|||
return kLut[size_t(strategy_)];
|
||||
}
|
||||
|
||||
struct CoeffOrderAndLut {
|
||||
// Those offsets get multiplied by kDCTBlockSize.
|
||||
// TODO(veluca): reduce this array by merging together the same order type.
|
||||
static constexpr size_t kOffset[kNumValidStrategies + 1] = {
|
||||
0, 1, 2, 3, 4, 8, 24, 26, 28, 32, 36, 44, 52, 53,
|
||||
54, 55, 56, 57, 58, 122, 154, 186, 442, 570, 698, 1722, 2234, 2746,
|
||||
};
|
||||
static constexpr size_t kTotalTableSize =
|
||||
kOffset[kNumValidStrategies] * kDCTBlockSize;
|
||||
coeff_order_t order[kTotalTableSize];
|
||||
coeff_order_t lut[kTotalTableSize];
|
||||
|
||||
private:
|
||||
CoeffOrderAndLut();
|
||||
friend class AcStrategy;
|
||||
};
|
||||
|
||||
private:
|
||||
friend class AcStrategyRow;
|
||||
JXL_INLINE AcStrategy(Type strategy, bool is_first)
|
||||
|
@ -198,8 +174,6 @@ class AcStrategy {
|
|||
|
||||
Type strategy_;
|
||||
bool is_first_;
|
||||
|
||||
static const CoeffOrderAndLut* CoeffOrder();
|
||||
};
|
||||
|
||||
// Class to use a certain row of the AC strategy.
|
||||
|
|
|
@ -229,7 +229,7 @@ void TestCheckpointing(bool ans, bool lz77) {
|
|||
ANSSymbolReader reader(&decoded_codes, &br);
|
||||
|
||||
ANSSymbolReader::Checkpoint checkpoint;
|
||||
size_t br_pos;
|
||||
size_t br_pos = 0;
|
||||
constexpr size_t kInterval = ANSSymbolReader::kMaxCheckpointInterval - 2;
|
||||
for (size_t i = 0; i < input_values[0].size(); i++) {
|
||||
if (i % kInterval == 0 && i > 0) {
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JXL_BASE_SCOPE_GUARD_H_
|
||||
#define LIB_JXL_BASE_SCOPE_GUARD_H_
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace jxl {
|
||||
|
||||
template <typename Callback>
|
||||
class ScopeGuard {
|
||||
public:
|
||||
// Discourage unnecessary moves / copies.
|
||||
ScopeGuard(const ScopeGuard &) = delete;
|
||||
ScopeGuard &operator=(const ScopeGuard &) = delete;
|
||||
ScopeGuard &operator=(ScopeGuard &&) = delete;
|
||||
|
||||
// Pre-C++17 does not guarantee RVO -> require move constructor.
|
||||
ScopeGuard(ScopeGuard &&other) : callback_(std::move(other.callback_)) {
|
||||
other.armed_ = false;
|
||||
}
|
||||
|
||||
template <typename CallbackParam>
|
||||
ScopeGuard(CallbackParam &&callback)
|
||||
: callback_(std::forward<CallbackParam>(callback)), armed_(true) {}
|
||||
|
||||
~ScopeGuard() {
|
||||
if (armed_) callback_();
|
||||
}
|
||||
|
||||
void Disarm() { armed_ = false; }
|
||||
|
||||
private:
|
||||
Callback callback_;
|
||||
bool armed_;
|
||||
};
|
||||
|
||||
template <typename Callback>
|
||||
ScopeGuard<Callback> MakeScopeGuard(Callback &&callback) {
|
||||
return {std::forward<Callback>(callback)};
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_JXL_BASE_SCOPE_GUARD_H_
|
|
@ -40,7 +40,7 @@ TEST(BlendingTest, Crops) {
|
|||
}
|
||||
|
||||
TEST(BlendingTest, Offset) {
|
||||
const PaddedBytes background_bytes = ReadTestData("jxl/splines.png");
|
||||
const PaddedBytes background_bytes = ReadTestData("jxl/splines.pfm");
|
||||
CodecInOut background;
|
||||
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(background_bytes), &background));
|
||||
const PaddedBytes foreground_bytes =
|
||||
|
@ -87,7 +87,7 @@ TEST(BlendingTest, Offset) {
|
|||
}
|
||||
|
||||
const PaddedBytes expected_bytes =
|
||||
ReadTestData("jxl/blending/grayscale_patches_on_splines.png");
|
||||
ReadTestData("jxl/blending/grayscale_patches_on_splines.pfm");
|
||||
CodecInOut expected;
|
||||
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(expected_bytes), &expected));
|
||||
VerifyRelativeError(*expected.Main().color(), *output.color(), 1. / (2 * 255),
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "lib/jxl/image.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
#include "lib/jxl/luminance.h"
|
||||
#include "lib/jxl/size_constraints.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
|
@ -34,14 +35,6 @@ struct CodecInterval {
|
|||
float width = 1.0f;
|
||||
};
|
||||
|
||||
struct SizeConstraints {
|
||||
// Upper limit on pixel dimensions/area, enforced by VerifyDimensions
|
||||
// (called from decoders). Fuzzers set smaller values to limit memory use.
|
||||
uint32_t dec_max_xsize = 0xFFFFFFFFu;
|
||||
uint32_t dec_max_ysize = 0xFFFFFFFFu;
|
||||
uint64_t dec_max_pixels = 0xFFFFFFFFu; // Might be up to ~0ull
|
||||
};
|
||||
|
||||
template <typename T,
|
||||
class = typename std::enable_if<std::is_unsigned<T>::value>::type>
|
||||
Status VerifyDimensions(const SizeConstraints* constraints, T xs, T ys) {
|
||||
|
|
|
@ -26,16 +26,6 @@
|
|||
|
||||
namespace jxl {
|
||||
|
||||
void SetDefaultOrder(AcStrategy acs, coeff_order_t* JXL_RESTRICT order) {
|
||||
PROFILER_FUNC;
|
||||
const size_t size =
|
||||
kDCTBlockSize * acs.covered_blocks_x() * acs.covered_blocks_y();
|
||||
const coeff_order_t* natural_coeff_order = acs.NaturalCoeffOrder();
|
||||
for (size_t k = 0; k < size; ++k) {
|
||||
order[k] = natural_coeff_order[k];
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t CoeffOrderContext(uint32_t val) {
|
||||
uint32_t token, nbits, bits;
|
||||
HybridUintConfig(0, 0, 0).Encode(val, &token, &nbits, &bits);
|
||||
|
@ -90,6 +80,7 @@ namespace {
|
|||
|
||||
Status DecodeCoeffOrder(AcStrategy acs, coeff_order_t* order, BitReader* br,
|
||||
ANSSymbolReader* reader,
|
||||
std::vector<coeff_order_t>& natural_order,
|
||||
const std::vector<uint8_t>& context_map) {
|
||||
PROFILER_FUNC;
|
||||
const size_t llf = acs.covered_blocks_x() * acs.covered_blocks_y();
|
||||
|
@ -98,9 +89,8 @@ Status DecodeCoeffOrder(AcStrategy acs, coeff_order_t* order, BitReader* br,
|
|||
JXL_RETURN_IF_ERROR(
|
||||
ReadPermutation(llf, size, order, br, reader, context_map));
|
||||
if (order == nullptr) return true;
|
||||
const coeff_order_t* natural_coeff_order = acs.NaturalCoeffOrder();
|
||||
for (size_t k = 0; k < size; ++k) {
|
||||
order[k] = natural_coeff_order[order[k]];
|
||||
order[k] = natural_order[order[k]];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -113,6 +103,7 @@ Status DecodeCoeffOrders(uint16_t used_orders, uint32_t used_acs,
|
|||
std::vector<uint8_t> context_map;
|
||||
ANSCode code;
|
||||
std::unique_ptr<ANSSymbolReader> reader;
|
||||
std::vector<coeff_order_t> natural_order;
|
||||
// Bitstream does not have histograms if no coefficient order is used.
|
||||
if (used_orders != 0) {
|
||||
JXL_RETURN_IF_ERROR(
|
||||
|
@ -130,18 +121,28 @@ Status DecodeCoeffOrders(uint16_t used_orders, uint32_t used_acs,
|
|||
computed |= 1 << ord;
|
||||
AcStrategy acs = AcStrategy::FromRawStrategy(o);
|
||||
bool used = (acs_mask & (1 << ord)) != 0;
|
||||
|
||||
const size_t llf = acs.covered_blocks_x() * acs.covered_blocks_y();
|
||||
const size_t size = kDCTBlockSize * llf;
|
||||
|
||||
if (used || (used_orders & (1 << ord))) {
|
||||
if (natural_order.size() < size) natural_order.resize(size);
|
||||
acs.ComputeNaturalCoeffOrder(natural_order.data());
|
||||
}
|
||||
|
||||
if ((used_orders & (1 << ord)) == 0) {
|
||||
// No need to set the default order if no ACS uses this order.
|
||||
if (used) {
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
SetDefaultOrder(acs, &order[CoeffOrderOffset(ord, c)]);
|
||||
memcpy(&order[CoeffOrderOffset(ord, c)], natural_order.data(),
|
||||
size * sizeof(*order));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
coeff_order_t* dest = used ? &order[CoeffOrderOffset(ord, c)] : nullptr;
|
||||
JXL_RETURN_IF_ERROR(
|
||||
DecodeCoeffOrder(acs, dest, br, reader.get(), context_map));
|
||||
JXL_RETURN_IF_ERROR(DecodeCoeffOrder(acs, dest, br, reader.get(),
|
||||
natural_order, context_map));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,8 +53,6 @@ constexpr uint32_t kPermutationContexts = 8;
|
|||
|
||||
uint32_t CoeffOrderContext(uint32_t val);
|
||||
|
||||
void SetDefaultOrder(AcStrategy acs, coeff_order_t* JXL_RESTRICT order);
|
||||
|
||||
Status DecodeCoeffOrders(uint16_t used_orders, uint32_t used_acs,
|
||||
coeff_order_t* order, BitReader* br);
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ struct CoeffBundle {
|
|||
}
|
||||
}
|
||||
static void B(float* JXL_RESTRICT coeff) {
|
||||
auto sqrt2 = Set(FV<SZ>(), square_root<2>::value);
|
||||
auto sqrt2 = Set(FV<SZ>(), kSqrt2);
|
||||
auto in1 = Load(FV<SZ>(), coeff);
|
||||
auto in2 = Load(FV<SZ>(), coeff + SZ);
|
||||
Store(MulAdd(in1, sqrt2, in2), FV<SZ>(), coeff);
|
||||
|
@ -77,7 +77,7 @@ struct CoeffBundle {
|
|||
auto in2 = Load(FV<SZ>(), coeff + (i - 1) * SZ);
|
||||
Store(in1 + in2, FV<SZ>(), coeff + i * SZ);
|
||||
}
|
||||
auto sqrt2 = Set(FV<SZ>(), square_root<2>::value);
|
||||
auto sqrt2 = Set(FV<SZ>(), kSqrt2);
|
||||
auto in1 = Load(FV<SZ>(), coeff);
|
||||
Store(in1 * sqrt2, FV<SZ>(), coeff);
|
||||
}
|
||||
|
|
|
@ -11,20 +11,9 @@
|
|||
#include <stddef.h>
|
||||
|
||||
namespace jxl {
|
||||
template <size_t V>
|
||||
struct square_root {
|
||||
static constexpr float value = square_root<V / 4>::value * 2;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct square_root<1> {
|
||||
static constexpr float value = 1.0f;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct square_root<2> {
|
||||
static constexpr float value = 1.4142135623730951f;
|
||||
};
|
||||
static constexpr float kSqrt2 = 1.41421356237f;
|
||||
static constexpr float kSqrt0_5 = 0.70710678118f;
|
||||
|
||||
// For n != 0, the n-th basis function of a N-DCT, evaluated in pixel k, has a
|
||||
// value of cos((k+1/2) n/(2N) pi). When downsampling by 2x, we average
|
||||
|
|
|
@ -275,6 +275,33 @@ class ANSSymbolReader {
|
|||
return static_cast<uint32_t>(ret);
|
||||
}
|
||||
|
||||
// Takes a *clustered* idx. Can only use if HuffRleOnly() is true.
|
||||
void ReadHybridUintClusteredHuffRleOnly(size_t ctx,
|
||||
BitReader* JXL_RESTRICT br,
|
||||
uint32_t* value, uint32_t* run) {
|
||||
JXL_DASSERT(HuffRleOnly());
|
||||
br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits
|
||||
size_t token = ReadSymbolHuffWithoutRefill(ctx, br);
|
||||
if (JXL_UNLIKELY(token >= lz77_threshold_)) {
|
||||
*run =
|
||||
ReadHybridUintConfig(lz77_length_uint_, token - lz77_threshold_, br) +
|
||||
lz77_min_length_ - 1;
|
||||
return;
|
||||
}
|
||||
*value = ReadHybridUintConfig(configs[ctx], token, br);
|
||||
return;
|
||||
}
|
||||
bool HuffRleOnly() {
|
||||
if (lz77_window_ == nullptr) return false;
|
||||
if (!use_prefix_code_) return false;
|
||||
for (size_t i = 0; i < kHuffmanTableBits; i++) {
|
||||
if (huffman_data_[lz77_ctx_].table_[i].bits) return false;
|
||||
if (huffman_data_[lz77_ctx_].table_[i].value != 1) return false;
|
||||
}
|
||||
if (configs[lz77_ctx_].split_token > 1) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Takes a *clustered* idx.
|
||||
size_t ReadHybridUintClustered(size_t ctx, BitReader* JXL_RESTRICT br) {
|
||||
if (JXL_UNLIKELY(num_to_copy_ > 0)) {
|
||||
|
@ -403,9 +430,9 @@ class ANSSymbolReader {
|
|||
bool use_prefix_code_;
|
||||
uint32_t state_ = ANS_SIGNATURE << 16u;
|
||||
const HybridUintConfig* JXL_RESTRICT configs;
|
||||
uint32_t log_alpha_size_;
|
||||
uint32_t log_entry_size_;
|
||||
uint32_t entry_size_minus_1_;
|
||||
uint32_t log_alpha_size_{};
|
||||
uint32_t log_entry_size_{};
|
||||
uint32_t entry_size_minus_1_{};
|
||||
|
||||
// LZ77 structures and constants.
|
||||
static constexpr size_t kWindowMask = kWindowSize - 1;
|
||||
|
@ -418,8 +445,8 @@ class ANSSymbolReader {
|
|||
uint32_t lz77_min_length_ = 0;
|
||||
uint32_t lz77_threshold_ = 1 << 20; // bigger than any symbol.
|
||||
HybridUintConfig lz77_length_uint_;
|
||||
uint32_t special_distances_[kNumSpecialDistances];
|
||||
uint32_t num_special_distances_;
|
||||
uint32_t special_distances_[kNumSpecialDistances]{};
|
||||
uint32_t num_special_distances_{};
|
||||
};
|
||||
|
||||
Status DecodeHistograms(BitReader* br, size_t num_contexts, ANSCode* code,
|
||||
|
|
|
@ -53,14 +53,16 @@
|
|||
#include "lib/jxl/passes_state.h"
|
||||
#include "lib/jxl/quant_weights.h"
|
||||
#include "lib/jxl/quantizer.h"
|
||||
#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_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_upsampling.h"
|
||||
#include "lib/jxl/render_pipeline/stage_write_to_ib.h"
|
||||
#include "lib/jxl/render_pipeline/stage_write.h"
|
||||
#include "lib/jxl/render_pipeline/stage_xyb.h"
|
||||
#include "lib/jxl/render_pipeline/stage_ycbcr.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
|
@ -730,13 +732,13 @@ Status FrameDecoder::ProcessACGroup(size_t ac_group_id,
|
|||
return true;
|
||||
}
|
||||
|
||||
void FrameDecoder::PreparePipeline() {
|
||||
Status FrameDecoder::PreparePipeline() {
|
||||
size_t num_c = 3 + frame_header_.nonserialized_metadata->m.num_extra_channels;
|
||||
if ((frame_header_.flags & FrameHeader::kNoise) != 0) {
|
||||
num_c += 3;
|
||||
}
|
||||
|
||||
RenderPipeline::Builder builder(num_c, frame_header_.passes.num_passes);
|
||||
RenderPipeline::Builder builder(num_c);
|
||||
|
||||
if (use_slow_rendering_pipeline_) {
|
||||
builder.UseSimpleImplementation();
|
||||
|
@ -833,36 +835,76 @@ void FrameDecoder::PreparePipeline() {
|
|||
GetWriteToImageBundleStage(&dec_state_->frame_storage_for_referencing));
|
||||
}
|
||||
|
||||
if (frame_header_.color_transform == ColorTransform::kYCbCr) {
|
||||
builder.AddStage(GetYCbCrStage());
|
||||
} else if (frame_header_.color_transform == ColorTransform::kXYB) {
|
||||
builder.AddStage(GetXYBStage(dec_state_->output_encoding_info));
|
||||
} // Nothing to do for kNone.
|
||||
|
||||
if (ImageBlender::NeedsBlending(dec_state_)) {
|
||||
JXL_ABORT("Not implemented: blending");
|
||||
bool has_alpha = false;
|
||||
size_t alpha_c = 0;
|
||||
for (size_t i = 0; i < decoded_->metadata()->extra_channel_info.size(); i++) {
|
||||
if (decoded_->metadata()->extra_channel_info[i].type ==
|
||||
ExtraChannel::kAlpha) {
|
||||
has_alpha = true;
|
||||
alpha_c = 3 + i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (frame_header_.CanBeReferenced() &&
|
||||
!frame_header_.save_before_color_transform) {
|
||||
builder.AddStage(
|
||||
GetWriteToImageBundleStage(&dec_state_->frame_storage_for_referencing));
|
||||
}
|
||||
// TODO(veluca): double-check when blending/no coalescing is enabled.
|
||||
size_t width = coalescing_ ? frame_header_.nonserialized_metadata->xsize()
|
||||
: frame_dim_.xsize_upsampled;
|
||||
size_t height = coalescing_ ? frame_header_.nonserialized_metadata->ysize()
|
||||
: frame_dim_.ysize_upsampled;
|
||||
|
||||
if (render_spotcolors_ &&
|
||||
frame_header_.nonserialized_metadata->m.Find(ExtraChannel::kSpotColor)) {
|
||||
JXL_ABORT("Not implemented: rendering spot colors");
|
||||
}
|
||||
if (dec_state_->pixel_callback) {
|
||||
JXL_ABORT("Not implemented: pixel callback");
|
||||
} else if (dec_state_->fast_xyb_srgb8_conversion) {
|
||||
JXL_ABORT("Not implemented: fast xyb->srgb conversion");
|
||||
} else if (dec_state_->rgb_output) {
|
||||
JXL_ABORT("Not implemented: u8 output");
|
||||
if (dec_state_->fast_xyb_srgb8_conversion) {
|
||||
JXL_ASSERT(!ImageBlender::NeedsBlending(dec_state_));
|
||||
JXL_ASSERT(!frame_header_.CanBeReferenced() ||
|
||||
frame_header_.save_before_color_transform);
|
||||
JXL_ASSERT(!render_spotcolors_);
|
||||
builder.AddStage(GetFastXYBTosRGB8Stage(
|
||||
dec_state_->rgb_output, dec_state_->rgb_stride, width, height,
|
||||
dec_state_->rgb_output_is_rgba, has_alpha, alpha_c));
|
||||
} else {
|
||||
builder.AddStage(GetWriteToImageBundleStage(decoded_));
|
||||
if (frame_header_.color_transform == ColorTransform::kYCbCr) {
|
||||
builder.AddStage(GetYCbCrStage());
|
||||
} else if (frame_header_.color_transform == ColorTransform::kXYB) {
|
||||
builder.AddStage(GetXYBStage(dec_state_->output_encoding_info));
|
||||
} // Nothing to do for kNone.
|
||||
|
||||
if (ImageBlender::NeedsBlending(dec_state_)) {
|
||||
builder.AddStage(GetBlendingStage(
|
||||
dec_state_, dec_state_->output_encoding_info.color_encoding));
|
||||
}
|
||||
|
||||
if (frame_header_.CanBeReferenced() &&
|
||||
!frame_header_.save_before_color_transform) {
|
||||
builder.AddStage(GetWriteToImageBundleStage(
|
||||
&dec_state_->frame_storage_for_referencing));
|
||||
}
|
||||
|
||||
if (render_spotcolors_ && frame_header_.nonserialized_metadata->m.Find(
|
||||
ExtraChannel::kSpotColor)) {
|
||||
for (size_t i = 0; i < decoded_->metadata()->extra_channel_info.size();
|
||||
i++) {
|
||||
// Don't use Find() because there may be multiple spot color channels.
|
||||
const ExtraChannelInfo& eci =
|
||||
decoded_->metadata()->extra_channel_info[i];
|
||||
if (eci.type == ExtraChannel::kSpotColor) {
|
||||
builder.AddStage(GetSpotColorStage(3 + i, eci.spot_color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dec_state_->pixel_callback) {
|
||||
builder.AddStage(GetWriteToPixelCallbackStage(
|
||||
dec_state_->pixel_callback, width, height,
|
||||
dec_state_->rgb_output_is_rgba, has_alpha, alpha_c));
|
||||
} else if (dec_state_->rgb_output) {
|
||||
builder.AddStage(GetWriteToU8Stage(
|
||||
dec_state_->rgb_output, dec_state_->rgb_stride, width, height,
|
||||
dec_state_->rgb_output_is_rgba, has_alpha, alpha_c));
|
||||
} else {
|
||||
builder.AddStage(GetWriteToImageBundleStage(decoded_));
|
||||
}
|
||||
}
|
||||
dec_state_->render_pipeline = std::move(builder).Finalize(frame_dim_);
|
||||
return dec_state_->render_pipeline->IsInitialized();
|
||||
}
|
||||
|
||||
void FrameDecoder::MarkSections(const SectionInfo* sections, size_t num,
|
||||
|
@ -971,7 +1013,7 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
|
|||
if (*std::min_element(decoded_dc_groups_.begin(), decoded_dc_groups_.end()) &&
|
||||
!finalized_dc_) {
|
||||
if (use_slow_rendering_pipeline_) {
|
||||
PreparePipeline();
|
||||
JXL_RETURN_IF_ERROR(PreparePipeline());
|
||||
}
|
||||
FinalizeDC();
|
||||
JXL_RETURN_IF_ERROR(AllocateOutput());
|
||||
|
@ -1298,13 +1340,6 @@ Status FrameDecoder::FinalizeFrame() {
|
|||
if (eci.type == ExtraChannel::kOptional) {
|
||||
continue;
|
||||
}
|
||||
if (eci.type == ExtraChannel::kUnknown ||
|
||||
(int(ExtraChannel::kReserved0) <= int(eci.type) &&
|
||||
int(eci.type) <= int(ExtraChannel::kReserved7))) {
|
||||
return JXL_FAILURE(
|
||||
"Unknown extra channel (bits %u, shift %u, name '%s')\n",
|
||||
eci.bit_depth.bits_per_sample, eci.dim_shift, eci.name.c_str());
|
||||
}
|
||||
if (eci.type == ExtraChannel::kSpotColor) {
|
||||
float scale = eci.spot_color[3];
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
|
|
|
@ -209,7 +209,7 @@ class FrameDecoder {
|
|||
Status ProcessDCGroup(size_t dc_group_id, BitReader* br);
|
||||
void FinalizeDC();
|
||||
Status AllocateOutput();
|
||||
void PreparePipeline();
|
||||
Status PreparePipeline();
|
||||
Status ProcessACGlobal(BitReader* br);
|
||||
Status ProcessACGroup(size_t ac_group_id, BitReader* JXL_RESTRICT* br,
|
||||
size_t num_passes, size_t thread, bool force_draw,
|
||||
|
|
|
@ -259,6 +259,8 @@ 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()}));
|
||||
}
|
||||
|
||||
for (size_t by = 0; by < ysize_blocks; ++by) {
|
||||
|
|
|
@ -100,14 +100,18 @@ void GroupBorderAssigner::GroupDone(size_t group_id, size_t padding,
|
|||
// of border of this group (on the other side), end of border of next group.
|
||||
size_t xpos[4] = {
|
||||
block_rect.x0() == 0 ? 0 : block_rect.x0() * kBlockDim - padx,
|
||||
block_rect.x0() == 0 ? 0 : block_rect.x0() * kBlockDim + padx,
|
||||
block_rect.x0() == 0 ? 0
|
||||
: std::min(frame_dim_.xsize_padded,
|
||||
block_rect.x0() * kBlockDim + padx),
|
||||
is_last_group_x ? frame_dim_.xsize_padded : x1 * kBlockDim - padx,
|
||||
is_last_group_x ? frame_dim_.xsize_padded : x1 * kBlockDim + padx};
|
||||
std::min(frame_dim_.xsize_padded, x1 * kBlockDim + padx)};
|
||||
size_t ypos[4] = {
|
||||
block_rect.y0() == 0 ? 0 : block_rect.y0() * kBlockDim - pady,
|
||||
block_rect.y0() == 0 ? 0 : block_rect.y0() * kBlockDim + pady,
|
||||
block_rect.y0() == 0 ? 0
|
||||
: std::min(frame_dim_.ysize_padded,
|
||||
block_rect.y0() * kBlockDim + pady),
|
||||
is_last_group_y ? frame_dim_.ysize_padded : y1 * kBlockDim - pady,
|
||||
is_last_group_y ? frame_dim_.ysize_padded : y1 * kBlockDim + pady};
|
||||
std::min(frame_dim_.ysize_padded, y1 * kBlockDim + pady)};
|
||||
|
||||
*num_to_finalize = 0;
|
||||
auto append_rect = [&](size_t x0, size_t x1, size_t y0, size_t y1) {
|
||||
|
|
|
@ -1075,15 +1075,28 @@ Status FinalizeImageRect(
|
|||
// the frame is not supposed to be displayed.
|
||||
|
||||
if (dec_state->fast_xyb_srgb8_conversion) {
|
||||
FastXYBTosRGB8(
|
||||
*output_pixel_data_storage,
|
||||
upsampled_frame_rect_for_storage.Lines(available_y, num_ys),
|
||||
upsampled_frame_rect.Lines(available_y, num_ys)
|
||||
.Crop(Rect(0, 0, frame_dim.xsize_upsampled,
|
||||
frame_dim.ysize_upsampled)),
|
||||
alpha, alpha_rect.Lines(available_y, num_ys),
|
||||
dec_state->rgb_output_is_rgba, dec_state->rgb_output, frame_dim.xsize,
|
||||
dec_state->rgb_stride);
|
||||
Rect output_rect = upsampled_frame_rect.Lines(available_y, num_ys)
|
||||
.Crop(Rect(0, 0, frame_dim.xsize_upsampled,
|
||||
frame_dim.ysize_upsampled));
|
||||
for (size_t iy = 0; iy < output_rect.ysize(); iy++) {
|
||||
const float* xyba[4] = {
|
||||
upsampled_frame_rect_for_storage.ConstPlaneRow(
|
||||
*output_pixel_data_storage, 0, available_y + iy),
|
||||
upsampled_frame_rect_for_storage.ConstPlaneRow(
|
||||
*output_pixel_data_storage, 1, available_y + iy),
|
||||
upsampled_frame_rect_for_storage.ConstPlaneRow(
|
||||
*output_pixel_data_storage, 2, available_y + iy),
|
||||
nullptr};
|
||||
if (alpha) {
|
||||
xyba[3] = alpha_rect.ConstRow(*alpha, available_y + iy);
|
||||
}
|
||||
uint8_t* out_buf =
|
||||
dec_state->rgb_output +
|
||||
(iy + output_rect.y0()) * dec_state->rgb_stride +
|
||||
(dec_state->rgb_output_is_rgba ? 4 : 3) * output_rect.x0();
|
||||
FastXYBTosRGB8(xyba, out_buf, dec_state->rgb_output_is_rgba,
|
||||
output_rect.xsize());
|
||||
}
|
||||
} else {
|
||||
if (frame_header.needs_color_transform()) {
|
||||
if (frame_header.color_transform == ColorTransform::kXYB) {
|
||||
|
@ -1295,9 +1308,10 @@ Status FinalizeFrameDecoding(ImageBundle* decoded,
|
|||
&decoded->extra_channels(), std::move(extra_channels_rects)));
|
||||
|
||||
std::vector<Rect> rects_to_process;
|
||||
for (size_t y = 0; y < frame_dim.ysize; y += kGroupDim) {
|
||||
for (size_t x = 0; x < frame_dim.xsize; x += kGroupDim) {
|
||||
Rect rect(x, y, kGroupDim, kGroupDim, frame_dim.xsize, frame_dim.ysize);
|
||||
for (size_t y = 0; y < frame_dim.ysize_upsampled; y += kGroupDim) {
|
||||
for (size_t x = 0; x < frame_dim.xsize_upsampled; x += kGroupDim) {
|
||||
Rect rect(x, y, kGroupDim, kGroupDim, frame_dim.xsize_upsampled,
|
||||
frame_dim.ysize_upsampled);
|
||||
if (rect.xsize() == 0 || rect.ysize() == 0) continue;
|
||||
rects_to_process.push_back(rect);
|
||||
}
|
||||
|
|
|
@ -86,10 +86,9 @@ static inline HWY_MAYBE_UNUSED bool HasFastXYBTosRGB8() {
|
|||
#endif
|
||||
}
|
||||
|
||||
static inline HWY_MAYBE_UNUSED void FastXYBTosRGB8(
|
||||
const Image3F& input, const Rect& input_rect, const Rect& output_buf_rect,
|
||||
const ImageF* alpha, const Rect& alpha_rect, bool is_rgba,
|
||||
uint8_t* JXL_RESTRICT output_buf, size_t xsize, size_t output_stride) {
|
||||
static inline HWY_MAYBE_UNUSED void FastXYBTosRGB8(const float* input[4],
|
||||
uint8_t* output,
|
||||
bool is_rgba, size_t xsize) {
|
||||
// This function is very NEON-specific. As such, it uses intrinsics directly.
|
||||
#if HWY_TARGET == HWY_NEON
|
||||
// WARNING: doing fixed point arithmetic correctly is very complicated.
|
||||
|
@ -173,170 +172,161 @@ static inline HWY_MAYBE_UNUSED void FastXYBTosRGB8(
|
|||
// > 0.0031308f (note that v16 has 13 mantissa bits)
|
||||
return vbslq_s16(vcgeq_s16(v16, vdupq_n_s16(26)), v16_pow, v16_linear);
|
||||
};
|
||||
for (size_t y = 0; y < output_buf_rect.ysize(); y++) {
|
||||
const float* JXL_RESTRICT row_in_x = input_rect.ConstPlaneRow(input, 0, y);
|
||||
const float* JXL_RESTRICT row_in_y = input_rect.ConstPlaneRow(input, 1, y);
|
||||
const float* JXL_RESTRICT row_in_b = input_rect.ConstPlaneRow(input, 2, y);
|
||||
const float* JXL_RESTRICT row_in_a =
|
||||
alpha == nullptr ? nullptr : alpha_rect.ConstRow(*alpha, y);
|
||||
size_t cnt = !is_rgba ? 3 : 4;
|
||||
size_t base_ptr =
|
||||
(y + output_buf_rect.y0()) * output_stride + output_buf_rect.x0() * cnt;
|
||||
for (size_t x = 0; x < output_buf_rect.xsize(); x += 8) {
|
||||
// Normal ranges for xyb for in-gamut sRGB colors:
|
||||
// x: -0.015386 0.028100
|
||||
// y: 0.000000 0.845308
|
||||
// b: 0.000000 0.845308
|
||||
|
||||
// We actually want x * 8 to have some extra precision.
|
||||
// TODO(veluca): consider different approaches here, like vld1q_f32_x2.
|
||||
float32x4_t opsin_x_left = vld1q_f32(row_in_x + x);
|
||||
int16x4_t opsin_x16_times8_left =
|
||||
vqmovn_s32(vcvtq_n_s32_f32(opsin_x_left, 18));
|
||||
float32x4_t opsin_x_right =
|
||||
vld1q_f32(row_in_x + x + (x + 4 < output_buf_rect.xsize() ? 4 : 0));
|
||||
int16x4_t opsin_x16_times8_right =
|
||||
vqmovn_s32(vcvtq_n_s32_f32(opsin_x_right, 18));
|
||||
int16x8_t opsin_x16_times8 =
|
||||
vcombine_s16(opsin_x16_times8_left, opsin_x16_times8_right);
|
||||
const float* JXL_RESTRICT row_in_x = input[0];
|
||||
const float* JXL_RESTRICT row_in_y = input[1];
|
||||
const float* JXL_RESTRICT row_in_b = input[2];
|
||||
const float* JXL_RESTRICT row_in_a = input[3];
|
||||
for (size_t x = 0; x < xsize; x += 8) {
|
||||
// Normal ranges for xyb for in-gamut sRGB colors:
|
||||
// x: -0.015386 0.028100
|
||||
// y: 0.000000 0.845308
|
||||
// b: 0.000000 0.845308
|
||||
|
||||
float32x4_t opsin_y_left = vld1q_f32(row_in_y + x);
|
||||
int16x4_t opsin_y16_left = vqmovn_s32(vcvtq_n_s32_f32(opsin_y_left, 15));
|
||||
float32x4_t opsin_y_right =
|
||||
vld1q_f32(row_in_y + x + (x + 4 < output_buf_rect.xsize() ? 4 : 0));
|
||||
int16x4_t opsin_y16_right =
|
||||
vqmovn_s32(vcvtq_n_s32_f32(opsin_y_right, 15));
|
||||
int16x8_t opsin_y16 = vcombine_s16(opsin_y16_left, opsin_y16_right);
|
||||
// We actually want x * 8 to have some extra precision.
|
||||
// TODO(veluca): consider different approaches here, like vld1q_f32_x2.
|
||||
float32x4_t opsin_x_left = vld1q_f32(row_in_x + x);
|
||||
int16x4_t opsin_x16_times8_left =
|
||||
vqmovn_s32(vcvtq_n_s32_f32(opsin_x_left, 18));
|
||||
float32x4_t opsin_x_right =
|
||||
vld1q_f32(row_in_x + x + (x + 4 < xsize ? 4 : 0));
|
||||
int16x4_t opsin_x16_times8_right =
|
||||
vqmovn_s32(vcvtq_n_s32_f32(opsin_x_right, 18));
|
||||
int16x8_t opsin_x16_times8 =
|
||||
vcombine_s16(opsin_x16_times8_left, opsin_x16_times8_right);
|
||||
|
||||
float32x4_t opsin_b_left = vld1q_f32(row_in_b + x);
|
||||
int16x4_t opsin_b16_left = vqmovn_s32(vcvtq_n_s32_f32(opsin_b_left, 15));
|
||||
float32x4_t opsin_b_right =
|
||||
vld1q_f32(row_in_b + x + (x + 4 < output_buf_rect.xsize() ? 4 : 0));
|
||||
int16x4_t opsin_b16_right =
|
||||
vqmovn_s32(vcvtq_n_s32_f32(opsin_b_right, 15));
|
||||
int16x8_t opsin_b16 = vcombine_s16(opsin_b16_left, opsin_b16_right);
|
||||
float32x4_t opsin_y_left = vld1q_f32(row_in_y + x);
|
||||
int16x4_t opsin_y16_left = vqmovn_s32(vcvtq_n_s32_f32(opsin_y_left, 15));
|
||||
float32x4_t opsin_y_right =
|
||||
vld1q_f32(row_in_y + x + (x + 4 < xsize ? 4 : 0));
|
||||
int16x4_t opsin_y16_right = vqmovn_s32(vcvtq_n_s32_f32(opsin_y_right, 15));
|
||||
int16x8_t opsin_y16 = vcombine_s16(opsin_y16_left, opsin_y16_right);
|
||||
|
||||
int16x8_t neg_bias16 = vdupq_n_s16(-124); // -0.0037930732552754493
|
||||
int16x8_t neg_bias_cbrt16 = vdupq_n_s16(-5110); // -0.155954201
|
||||
int16x8_t neg_bias_half16 = vdupq_n_s16(-62);
|
||||
float32x4_t opsin_b_left = vld1q_f32(row_in_b + x);
|
||||
int16x4_t opsin_b16_left = vqmovn_s32(vcvtq_n_s32_f32(opsin_b_left, 15));
|
||||
float32x4_t opsin_b_right =
|
||||
vld1q_f32(row_in_b + x + (x + 4 < xsize ? 4 : 0));
|
||||
int16x4_t opsin_b16_right = vqmovn_s32(vcvtq_n_s32_f32(opsin_b_right, 15));
|
||||
int16x8_t opsin_b16 = vcombine_s16(opsin_b16_left, opsin_b16_right);
|
||||
|
||||
// Color space: XYB -> RGB
|
||||
// Compute ((y+x-bias_cbrt)^3-(y-x-bias_cbrt)^3)/2,
|
||||
// ((y+x-bias_cbrt)^3+(y-x-bias_cbrt)^3)/2+bias, (b-bias_cbrt)^3+bias.
|
||||
// Note that ignoring x2 in the formulas below (as x << y) results in
|
||||
// errors of at least 3 in the final sRGB values.
|
||||
int16x8_t opsin_yp16 = vqsubq_s16(opsin_y16, neg_bias_cbrt16);
|
||||
int16x8_t ysq16 = vqrdmulhq_s16(opsin_yp16, opsin_yp16);
|
||||
int16x8_t twentyfourx16 = vmulq_n_s16(opsin_x16_times8, 3);
|
||||
int16x8_t twentyfourxy16 = vqrdmulhq_s16(opsin_yp16, twentyfourx16);
|
||||
int16x8_t threexsq16 =
|
||||
vrshrq_n_s16(vqrdmulhq_s16(opsin_x16_times8, twentyfourx16), 6);
|
||||
int16x8_t neg_bias16 = vdupq_n_s16(-124); // -0.0037930732552754493
|
||||
int16x8_t neg_bias_cbrt16 = vdupq_n_s16(-5110); // -0.155954201
|
||||
int16x8_t neg_bias_half16 = vdupq_n_s16(-62);
|
||||
|
||||
// We can ignore x^3 here. Note that this is multiplied by 8.
|
||||
int16x8_t mixed_rmg16 = vqrdmulhq_s16(twentyfourxy16, opsin_yp16);
|
||||
// Color space: XYB -> RGB
|
||||
// Compute ((y+x-bias_cbrt)^3-(y-x-bias_cbrt)^3)/2,
|
||||
// ((y+x-bias_cbrt)^3+(y-x-bias_cbrt)^3)/2+bias, (b-bias_cbrt)^3+bias.
|
||||
// Note that ignoring x2 in the formulas below (as x << y) results in
|
||||
// errors of at least 3 in the final sRGB values.
|
||||
int16x8_t opsin_yp16 = vqsubq_s16(opsin_y16, neg_bias_cbrt16);
|
||||
int16x8_t ysq16 = vqrdmulhq_s16(opsin_yp16, opsin_yp16);
|
||||
int16x8_t twentyfourx16 = vmulq_n_s16(opsin_x16_times8, 3);
|
||||
int16x8_t twentyfourxy16 = vqrdmulhq_s16(opsin_yp16, twentyfourx16);
|
||||
int16x8_t threexsq16 =
|
||||
vrshrq_n_s16(vqrdmulhq_s16(opsin_x16_times8, twentyfourx16), 6);
|
||||
|
||||
int16x8_t mixed_rpg_sos_half = vhaddq_s16(ysq16, threexsq16);
|
||||
int16x8_t mixed_rpg16 = vhaddq_s16(
|
||||
vqrdmulhq_s16(opsin_yp16, mixed_rpg_sos_half), neg_bias_half16);
|
||||
// We can ignore x^3 here. Note that this is multiplied by 8.
|
||||
int16x8_t mixed_rmg16 = vqrdmulhq_s16(twentyfourxy16, opsin_yp16);
|
||||
|
||||
int16x8_t gamma_b16 = vqsubq_s16(opsin_b16, neg_bias_cbrt16);
|
||||
int16x8_t gamma_bsq16 = vqrdmulhq_s16(gamma_b16, gamma_b16);
|
||||
int16x8_t gamma_bcb16 = vqrdmulhq_s16(gamma_bsq16, gamma_b16);
|
||||
int16x8_t mixed_b16 = vqaddq_s16(gamma_bcb16, neg_bias16);
|
||||
// mixed_rpg and mixed_b are in 0-1 range.
|
||||
// mixed_rmg has a smaller range (-0.035 to 0.035 for valid sRGB). Note
|
||||
// that at this point it is already multiplied by 8.
|
||||
int16x8_t mixed_rpg_sos_half = vhaddq_s16(ysq16, threexsq16);
|
||||
int16x8_t mixed_rpg16 = vhaddq_s16(
|
||||
vqrdmulhq_s16(opsin_yp16, mixed_rpg_sos_half), neg_bias_half16);
|
||||
|
||||
// We multiply all the mixed values by 1/4 (i.e. shift them to 13-bit
|
||||
// fixed point) to ensure intermediate quantities are in range. Note that
|
||||
// r-g is not shifted, and was x8 before here; this corresponds to a x32
|
||||
// overall multiplicative factor and ensures that all the matrix constants
|
||||
// are in 0-1 range.
|
||||
// Similarly, mixed_rpg16 is already multiplied by 1/4 because of the two
|
||||
// vhadd + using neg_bias_half.
|
||||
mixed_b16 = vshrq_n_s16(mixed_b16, 2);
|
||||
int16x8_t gamma_b16 = vqsubq_s16(opsin_b16, neg_bias_cbrt16);
|
||||
int16x8_t gamma_bsq16 = vqrdmulhq_s16(gamma_b16, gamma_b16);
|
||||
int16x8_t gamma_bcb16 = vqrdmulhq_s16(gamma_bsq16, gamma_b16);
|
||||
int16x8_t mixed_b16 = vqaddq_s16(gamma_bcb16, neg_bias16);
|
||||
// mixed_rpg and mixed_b are in 0-1 range.
|
||||
// mixed_rmg has a smaller range (-0.035 to 0.035 for valid sRGB). Note
|
||||
// that at this point it is already multiplied by 8.
|
||||
|
||||
// Unmix (multiply by 3x3 inverse_matrix)
|
||||
// For increased precision, we use a matrix for converting from
|
||||
// ((mixed_r - mixed_g)/2, (mixed_r + mixed_g)/2, mixed_b) to rgb. This
|
||||
// avoids cancellation effects when computing (y+x)^3-(y-x)^3.
|
||||
// We compute mixed_rpg - mixed_b because the (1+c)*mixed_rpg - c *
|
||||
// mixed_b pattern is repeated frequently in the code below. This allows
|
||||
// us to save a multiply per channel, and removes the presence of
|
||||
// some constants above 1. Moreover, mixed_rmg - mixed_b is in (-1, 1)
|
||||
// range, so the subtraction is safe.
|
||||
// All the magic-looking constants here are derived by computing the
|
||||
// inverse opsin matrix for the transformation modified as described
|
||||
// above.
|
||||
// We multiply all the mixed values by 1/4 (i.e. shift them to 13-bit
|
||||
// fixed point) to ensure intermediate quantities are in range. Note that
|
||||
// r-g is not shifted, and was x8 before here; this corresponds to a x32
|
||||
// overall multiplicative factor and ensures that all the matrix constants
|
||||
// are in 0-1 range.
|
||||
// Similarly, mixed_rpg16 is already multiplied by 1/4 because of the two
|
||||
// vhadd + using neg_bias_half.
|
||||
mixed_b16 = vshrq_n_s16(mixed_b16, 2);
|
||||
|
||||
// Precomputation common to multiple color values.
|
||||
int16x8_t mixed_rpgmb16 = vqsubq_s16(mixed_rpg16, mixed_b16);
|
||||
int16x8_t mixed_rpgmb_times_016 = vqrdmulhq_n_s16(mixed_rpgmb16, 5394);
|
||||
int16x8_t mixed_rg16 = vqaddq_s16(mixed_rpgmb_times_016, mixed_rpg16);
|
||||
// Unmix (multiply by 3x3 inverse_matrix)
|
||||
// For increased precision, we use a matrix for converting from
|
||||
// ((mixed_r - mixed_g)/2, (mixed_r + mixed_g)/2, mixed_b) to rgb. This
|
||||
// avoids cancellation effects when computing (y+x)^3-(y-x)^3.
|
||||
// We compute mixed_rpg - mixed_b because the (1+c)*mixed_rpg - c *
|
||||
// mixed_b pattern is repeated frequently in the code below. This allows
|
||||
// us to save a multiply per channel, and removes the presence of
|
||||
// some constants above 1. Moreover, mixed_rmg - mixed_b is in (-1, 1)
|
||||
// range, so the subtraction is safe.
|
||||
// All the magic-looking constants here are derived by computing the
|
||||
// inverse opsin matrix for the transformation modified as described
|
||||
// above.
|
||||
|
||||
// R
|
||||
int16x8_t linear_r16 =
|
||||
vqaddq_s16(mixed_rg16, vqrdmulhq_n_s16(mixed_rmg16, 21400));
|
||||
// Precomputation common to multiple color values.
|
||||
int16x8_t mixed_rpgmb16 = vqsubq_s16(mixed_rpg16, mixed_b16);
|
||||
int16x8_t mixed_rpgmb_times_016 = vqrdmulhq_n_s16(mixed_rpgmb16, 5394);
|
||||
int16x8_t mixed_rg16 = vqaddq_s16(mixed_rpgmb_times_016, mixed_rpg16);
|
||||
|
||||
// G
|
||||
int16x8_t linear_g16 =
|
||||
vqaddq_s16(mixed_rg16, vqrdmulhq_n_s16(mixed_rmg16, -7857));
|
||||
// R
|
||||
int16x8_t linear_r16 =
|
||||
vqaddq_s16(mixed_rg16, vqrdmulhq_n_s16(mixed_rmg16, 21400));
|
||||
|
||||
// B
|
||||
int16x8_t linear_b16 = vqrdmulhq_n_s16(mixed_rpgmb16, -30996);
|
||||
linear_b16 = vqaddq_s16(linear_b16, mixed_b16);
|
||||
linear_b16 = vqaddq_s16(linear_b16, vqrdmulhq_n_s16(mixed_rmg16, -6525));
|
||||
// G
|
||||
int16x8_t linear_g16 =
|
||||
vqaddq_s16(mixed_rg16, vqrdmulhq_n_s16(mixed_rmg16, -7857));
|
||||
|
||||
// Apply SRGB transfer function.
|
||||
int16x8_t r = srgb_tf(linear_r16);
|
||||
int16x8_t g = srgb_tf(linear_g16);
|
||||
int16x8_t b = srgb_tf(linear_b16);
|
||||
// B
|
||||
int16x8_t linear_b16 = vqrdmulhq_n_s16(mixed_rpgmb16, -30996);
|
||||
linear_b16 = vqaddq_s16(linear_b16, mixed_b16);
|
||||
linear_b16 = vqaddq_s16(linear_b16, vqrdmulhq_n_s16(mixed_rmg16, -6525));
|
||||
|
||||
uint8x8_t r8 =
|
||||
vqmovun_s16(vrshrq_n_s16(vsubq_s16(r, vshrq_n_s16(r, 8)), 6));
|
||||
uint8x8_t g8 =
|
||||
vqmovun_s16(vrshrq_n_s16(vsubq_s16(g, vshrq_n_s16(g, 8)), 6));
|
||||
uint8x8_t b8 =
|
||||
vqmovun_s16(vrshrq_n_s16(vsubq_s16(b, vshrq_n_s16(b, 8)), 6));
|
||||
// Apply SRGB transfer function.
|
||||
int16x8_t r = srgb_tf(linear_r16);
|
||||
int16x8_t g = srgb_tf(linear_g16);
|
||||
int16x8_t b = srgb_tf(linear_b16);
|
||||
|
||||
size_t n = output_buf_rect.xsize() - x;
|
||||
if (is_rgba) {
|
||||
float32x4_t a_f32_left =
|
||||
row_in_a ? vld1q_f32(row_in_a + x) : vdupq_n_f32(1.0f);
|
||||
float32x4_t a_f32_right =
|
||||
row_in_a ? vld1q_f32(row_in_a + x +
|
||||
(x + 4 < output_buf_rect.xsize() ? 4 : 0))
|
||||
: vdupq_n_f32(1.0f);
|
||||
int16x4_t a16_left = vqmovn_s32(vcvtq_n_s32_f32(a_f32_left, 8));
|
||||
int16x4_t a16_right = vqmovn_s32(vcvtq_n_s32_f32(a_f32_right, 8));
|
||||
uint8x8_t a8 = vqmovun_s16(vcombine_s16(a16_left, a16_right));
|
||||
uint8_t* buf = output_buf + base_ptr + 4 * x;
|
||||
uint8x8x4_t data = {r8, g8, b8, a8};
|
||||
if (n >= 8) {
|
||||
vst4_u8(buf, data);
|
||||
} else {
|
||||
uint8_t tmp[8 * 4];
|
||||
vst4_u8(tmp, data);
|
||||
memcpy(buf, tmp, n * 4);
|
||||
}
|
||||
uint8x8_t r8 =
|
||||
vqmovun_s16(vrshrq_n_s16(vsubq_s16(r, vshrq_n_s16(r, 8)), 6));
|
||||
uint8x8_t g8 =
|
||||
vqmovun_s16(vrshrq_n_s16(vsubq_s16(g, vshrq_n_s16(g, 8)), 6));
|
||||
uint8x8_t b8 =
|
||||
vqmovun_s16(vrshrq_n_s16(vsubq_s16(b, vshrq_n_s16(b, 8)), 6));
|
||||
|
||||
size_t n = xsize - x;
|
||||
if (is_rgba) {
|
||||
float32x4_t a_f32_left =
|
||||
row_in_a ? vld1q_f32(row_in_a + x) : vdupq_n_f32(1.0f);
|
||||
float32x4_t a_f32_right =
|
||||
row_in_a ? vld1q_f32(row_in_a + x + (x + 4 < xsize ? 4 : 0))
|
||||
: vdupq_n_f32(1.0f);
|
||||
int16x4_t a16_left = vqmovn_s32(vcvtq_n_s32_f32(a_f32_left, 8));
|
||||
int16x4_t a16_right = vqmovn_s32(vcvtq_n_s32_f32(a_f32_right, 8));
|
||||
uint8x8_t a8 = vqmovun_s16(vcombine_s16(a16_left, a16_right));
|
||||
uint8_t* buf = output + 4 * x;
|
||||
uint8x8x4_t data = {r8, g8, b8, a8};
|
||||
if (n >= 8) {
|
||||
vst4_u8(buf, data);
|
||||
} else {
|
||||
uint8_t* buf = output_buf + base_ptr + 3 * x;
|
||||
uint8x8x3_t data = {r8, g8, b8};
|
||||
if (n >= 8) {
|
||||
vst3_u8(buf, data);
|
||||
} else {
|
||||
uint8_t tmp[8 * 3];
|
||||
vst3_u8(tmp, data);
|
||||
memcpy(buf, tmp, n * 3);
|
||||
}
|
||||
uint8_t tmp[8 * 4];
|
||||
vst4_u8(tmp, data);
|
||||
memcpy(buf, tmp, n * 4);
|
||||
}
|
||||
} else {
|
||||
uint8_t* buf = output + 3 * x;
|
||||
uint8x8x3_t data = {r8, g8, b8};
|
||||
if (n >= 8) {
|
||||
vst3_u8(buf, data);
|
||||
} else {
|
||||
uint8_t tmp[8 * 3];
|
||||
vst3_u8(tmp, data);
|
||||
memcpy(buf, tmp, n * 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)input;
|
||||
(void)input_rect;
|
||||
(void)output_buf_rect;
|
||||
(void)output_buf;
|
||||
(void)output;
|
||||
(void)is_rgba;
|
||||
(void)xsize;
|
||||
JXL_ABORT("Unreachable");
|
||||
#endif
|
||||
|
|
|
@ -180,14 +180,9 @@ HWY_EXPORT(HasFastXYBTosRGB8);
|
|||
bool HasFastXYBTosRGB8() { return HWY_DYNAMIC_DISPATCH(HasFastXYBTosRGB8)(); }
|
||||
|
||||
HWY_EXPORT(FastXYBTosRGB8);
|
||||
void FastXYBTosRGB8(const Image3F& input, const Rect& input_rect,
|
||||
const Rect& output_buf_rect, const ImageF* alpha,
|
||||
const Rect& alpha_rect, bool is_rgba,
|
||||
uint8_t* JXL_RESTRICT output_buf, size_t xsize,
|
||||
size_t output_stride) {
|
||||
return HWY_DYNAMIC_DISPATCH(FastXYBTosRGB8)(
|
||||
input, input_rect, output_buf_rect, alpha, alpha_rect, is_rgba,
|
||||
output_buf, xsize, output_stride);
|
||||
void FastXYBTosRGB8(const float* input[4], uint8_t* output, bool is_rgba,
|
||||
size_t xsize) {
|
||||
return HWY_DYNAMIC_DISPATCH(FastXYBTosRGB8)(input, output, is_rgba, xsize);
|
||||
}
|
||||
|
||||
void OpsinParams::Init(float intensity_target) {
|
||||
|
|
|
@ -65,11 +65,8 @@ void OpsinToLinear(const Image3F& opsin, const Rect& rect, ThreadPool* pool,
|
|||
void YcbcrToRgb(const Image3F& ycbcr, Image3F* rgb, const Rect& rect);
|
||||
|
||||
bool HasFastXYBTosRGB8();
|
||||
void FastXYBTosRGB8(const Image3F& input, const Rect& input_rect,
|
||||
const Rect& output_buf_rect, const ImageF* alpha,
|
||||
const Rect& alpha_rect, bool is_rgba,
|
||||
uint8_t* JXL_RESTRICT output_buf, size_t xsize,
|
||||
size_t output_stride);
|
||||
void FastXYBTosRGB8(const float* input[4], uint8_t* output, bool is_rgba,
|
||||
size_t xsize);
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
|
|
|
@ -850,8 +850,9 @@ void GetCurrentDimensions(const JxlDecoder* dec, size_t& xsize, size_t& ysize,
|
|||
xsize = dec->metadata.oriented_xsize(dec->keep_orientation || !oriented);
|
||||
ysize = dec->metadata.oriented_ysize(dec->keep_orientation || !oriented);
|
||||
if (!dec->coalescing) {
|
||||
xsize = dec->frame_header->ToFrameDimensions().xsize;
|
||||
ysize = dec->frame_header->ToFrameDimensions().ysize;
|
||||
const auto frame_dim = dec->frame_header->ToFrameDimensions();
|
||||
xsize = frame_dim.xsize_upsampled;
|
||||
ysize = frame_dim.ysize_upsampled;
|
||||
if (!dec->keep_orientation && oriented &&
|
||||
static_cast<int>(dec->metadata.m.GetOrientation()) > 4) {
|
||||
std::swap(xsize, ysize);
|
||||
|
@ -2671,9 +2672,11 @@ JxlDecoderStatus JxlDecoderGetFrameHeader(const JxlDecoder* dec,
|
|||
if (!dec->coalescing && dec->frame_header->custom_size_or_origin) {
|
||||
header->layer_info.crop_x0 = dec->frame_header->frame_origin.x0;
|
||||
header->layer_info.crop_y0 = dec->frame_header->frame_origin.y0;
|
||||
header->layer_info.have_crop = JXL_TRUE;
|
||||
} else {
|
||||
header->layer_info.crop_x0 = 0;
|
||||
header->layer_info.crop_y0 = 0;
|
||||
header->layer_info.have_crop = JXL_FALSE;
|
||||
}
|
||||
if (!dec->keep_orientation && !dec->coalescing) {
|
||||
// orient the crop offset
|
||||
|
|
|
@ -198,8 +198,8 @@ PaddedBytes CreateTestJXLCodestream(
|
|||
io.metadata.m.color_encoding = color_encoding;
|
||||
EXPECT_TRUE(ConvertFromExternal(
|
||||
pixels, xsize, ysize, color_encoding, /*has_alpha=*/include_alpha,
|
||||
/*alpha_is_premultiplied=*/false, bitdepth, JXL_BIG_ENDIAN,
|
||||
/*flipped_y=*/false, &pool, &io.Main(), /*float_in=*/false));
|
||||
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN,
|
||||
/*flipped_y=*/false, &pool, &io.Main(), /*float_in=*/false, /*align=*/0));
|
||||
jxl::PaddedBytes jpeg_data;
|
||||
if (jpeg_codestream != nullptr) {
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
|
@ -1224,7 +1224,8 @@ TEST_P(DecodeTestParam, PixelTest) {
|
|||
EXPECT_TRUE(ConvertFromExternal(
|
||||
bytes, config.xsize, config.ysize, color_encoding, config.include_alpha,
|
||||
/*alpha_is_premultiplied=*/false, 16, JXL_BIG_ENDIAN,
|
||||
/*flipped_y=*/false, nullptr, &io.Main(), /*float_in=*/false));
|
||||
/*flipped_y=*/false, nullptr, &io.Main(), /*float_in=*/false,
|
||||
/*align=*/0));
|
||||
|
||||
for (size_t i = 0; i < pixels.size(); i++) pixels[i] = 0;
|
||||
EXPECT_TRUE(ConvertToExternal(
|
||||
|
@ -1543,22 +1544,24 @@ TEST(DecodeTest, PixelTestWithICCProfileLossy) {
|
|||
EXPECT_TRUE(ConvertFromExternal(
|
||||
span0, xsize, ysize, color_encoding0,
|
||||
/*has_alpha=*/false, false, 16, format_orig.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr, &io0.Main(), /*float_in=*/false));
|
||||
/*flipped_y=*/false, /*pool=*/nullptr, &io0.Main(), /*float_in=*/false,
|
||||
/*align=*/0));
|
||||
|
||||
jxl::ColorEncoding color_encoding1;
|
||||
EXPECT_TRUE(color_encoding1.SetICC(std::move(icc)));
|
||||
jxl::Span<const uint8_t> span1(pixels2.data(), pixels2.size());
|
||||
jxl::CodecInOut io1;
|
||||
io1.SetSize(xsize, ysize);
|
||||
EXPECT_TRUE(ConvertFromExternal(
|
||||
span1, xsize, ysize, color_encoding1,
|
||||
/*has_alpha=*/false, false, 32, format.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr, &io1.Main(), /*float_in=*/true));
|
||||
EXPECT_TRUE(ConvertFromExternal(span1, xsize, ysize, color_encoding1,
|
||||
/*has_alpha=*/false, false, 32,
|
||||
format.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr,
|
||||
&io1.Main(), /*float_in=*/true, /*align=*/0));
|
||||
|
||||
jxl::ButteraugliParams ba;
|
||||
EXPECT_THAT(ButteraugliDistance(io0, io1, ba, jxl::GetJxlCms(),
|
||||
/*distmap=*/nullptr, nullptr),
|
||||
IsSlightlyBelow(0.75f));
|
||||
IsSlightlyBelow(0.77f));
|
||||
|
||||
JxlDecoderDestroy(dec);
|
||||
}
|
||||
|
@ -1596,11 +1599,11 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossy) {
|
|||
jxl::Span<const uint8_t> span0(pixels.data(), pixels.size());
|
||||
jxl::CodecInOut io0;
|
||||
io0.SetSize(xsize, ysize);
|
||||
EXPECT_TRUE(ConvertFromExternal(span0, xsize, ysize, color_encoding0,
|
||||
/*has_alpha=*/false, false, 16,
|
||||
format_orig.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr,
|
||||
&io0.Main(), /*float_in=*/false));
|
||||
EXPECT_TRUE(ConvertFromExternal(
|
||||
span0, xsize, ysize, color_encoding0,
|
||||
/*has_alpha=*/false, false, 16, format_orig.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr, &io0.Main(), /*float_in=*/false,
|
||||
/*align=*/0));
|
||||
|
||||
jxl::ColorEncoding color_encoding1 = jxl::ColorEncoding::SRGB(false);
|
||||
jxl::Span<const uint8_t> span1(pixels2.data(), pixels2.size());
|
||||
|
@ -1608,19 +1611,19 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossy) {
|
|||
if (channels == 4) {
|
||||
io1.metadata.m.SetAlphaBits(8);
|
||||
io1.SetSize(xsize, ysize);
|
||||
EXPECT_TRUE(ConvertFromExternal(span1, xsize, ysize, color_encoding1,
|
||||
/*has_alpha=*/true, false, 8,
|
||||
format.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr,
|
||||
&io1.Main(), /*float_in=*/false));
|
||||
EXPECT_TRUE(
|
||||
ConvertFromExternal(span1, xsize, ysize, color_encoding1,
|
||||
/*has_alpha=*/true, false, 8, format.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr,
|
||||
&io1.Main(), /*float_in=*/false, /*align=*/0));
|
||||
io1.metadata.m.SetAlphaBits(0);
|
||||
io1.Main().ClearExtraChannels();
|
||||
} else {
|
||||
EXPECT_TRUE(ConvertFromExternal(span1, xsize, ysize, color_encoding1,
|
||||
/*has_alpha=*/false, false, 8,
|
||||
format.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr,
|
||||
&io1.Main(), /*float_in=*/false));
|
||||
EXPECT_TRUE(
|
||||
ConvertFromExternal(span1, xsize, ysize, color_encoding1,
|
||||
/*has_alpha=*/false, false, 8, format.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr,
|
||||
&io1.Main(), /*float_in=*/false, /*align=*/0));
|
||||
}
|
||||
|
||||
jxl::ButteraugliParams ba;
|
||||
|
@ -1665,11 +1668,11 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossyNoise) {
|
|||
jxl::Span<const uint8_t> span0(pixels.data(), pixels.size());
|
||||
jxl::CodecInOut io0;
|
||||
io0.SetSize(xsize, ysize);
|
||||
EXPECT_TRUE(ConvertFromExternal(span0, xsize, ysize, color_encoding0,
|
||||
/*has_alpha=*/false, false, 16,
|
||||
format_orig.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr,
|
||||
&io0.Main(), /*float_in=*/false));
|
||||
EXPECT_TRUE(ConvertFromExternal(
|
||||
span0, xsize, ysize, color_encoding0,
|
||||
/*has_alpha=*/false, false, 16, format_orig.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr, &io0.Main(), /*float_in=*/false,
|
||||
/*align=*/0));
|
||||
|
||||
jxl::ColorEncoding color_encoding1 = jxl::ColorEncoding::SRGB(false);
|
||||
jxl::Span<const uint8_t> span1(pixels2.data(), pixels2.size());
|
||||
|
@ -1677,19 +1680,19 @@ TEST(DecodeTest, PixelTestOpaqueSrgbLossyNoise) {
|
|||
if (channels == 4) {
|
||||
io1.metadata.m.SetAlphaBits(8);
|
||||
io1.SetSize(xsize, ysize);
|
||||
EXPECT_TRUE(ConvertFromExternal(span1, xsize, ysize, color_encoding1,
|
||||
/*has_alpha=*/true, false, 8,
|
||||
format.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr,
|
||||
&io1.Main(), /*float_in=*/false));
|
||||
EXPECT_TRUE(
|
||||
ConvertFromExternal(span1, xsize, ysize, color_encoding1,
|
||||
/*has_alpha=*/true, false, 8, format.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr,
|
||||
&io1.Main(), /*float_in=*/false, /*align=*/0));
|
||||
io1.metadata.m.SetAlphaBits(0);
|
||||
io1.Main().ClearExtraChannels();
|
||||
} else {
|
||||
EXPECT_TRUE(ConvertFromExternal(span1, xsize, ysize, color_encoding1,
|
||||
/*has_alpha=*/false, false, 8,
|
||||
format.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr,
|
||||
&io1.Main(), /*float_in=*/false));
|
||||
EXPECT_TRUE(
|
||||
ConvertFromExternal(span1, xsize, ysize, color_encoding1,
|
||||
/*has_alpha=*/false, false, 8, format.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr,
|
||||
&io1.Main(), /*float_in=*/false, /*align=*/0));
|
||||
}
|
||||
|
||||
jxl::ButteraugliParams ba;
|
||||
|
@ -1728,7 +1731,6 @@ void TestPartialStream(bool reconstructible_jpeg) {
|
|||
std::vector<jxl::PaddedBytes> jpeg_codestreams(kCSBF_NUM_ENTRIES);
|
||||
for (size_t i = 0; i < kCSBF_NUM_ENTRIES; ++i) {
|
||||
CodeStreamBoxFormat add_container = (CodeStreamBoxFormat)i;
|
||||
|
||||
codestreams[i] = jxl::CreateTestJXLCodestream(
|
||||
jxl::Span<const uint8_t>(pixels.data(), pixels.size()), xsize, ysize,
|
||||
channels, cparams, add_container, JXL_ORIENT_IDENTITY,
|
||||
|
@ -1969,9 +1971,9 @@ TEST(DecodeTest, PreviewTest) {
|
|||
// smaller than 8x8, but jxl's ButteraugliDistance does not. Perhaps move
|
||||
// butteraugli's <8x8 handling from ButteraugliDiffmap to
|
||||
// ButteraugliComparator::Diffmap in butteraugli.cc.
|
||||
EXPECT_THAT(ButteraugliDistance(io0, io1, ba, jxl::GetJxlCms(),
|
||||
/*distmap=*/nullptr, nullptr),
|
||||
IsSlightlyBelow(0.6f));
|
||||
EXPECT_LE(ButteraugliDistance(io0, io1, ba, jxl::GetJxlCms(),
|
||||
/*distmap=*/nullptr, nullptr),
|
||||
0.6f);
|
||||
|
||||
JxlDecoderDestroy(dec);
|
||||
}
|
||||
|
@ -2034,7 +2036,7 @@ TEST(DecodeTest, AnimationTest) {
|
|||
ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*has_alpha=*/false,
|
||||
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
|
||||
JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle,
|
||||
/*float_in=*/false));
|
||||
/*float_in=*/false, /*align=*/0));
|
||||
bundle.duration = frame_durations[i];
|
||||
io.frames.push_back(std::move(bundle));
|
||||
}
|
||||
|
@ -2138,7 +2140,7 @@ TEST(DecodeTest, AnimationTestStreaming) {
|
|||
ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*has_alpha=*/false,
|
||||
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
|
||||
JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle,
|
||||
/*float_in=*/false));
|
||||
/*float_in=*/false, /*align=*/0));
|
||||
bundle.duration = frame_durations[i];
|
||||
io.frames.push_back(std::move(bundle));
|
||||
}
|
||||
|
@ -2363,7 +2365,7 @@ TEST(DecodeTest, SkipFrameTest) {
|
|||
ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*has_alpha=*/false,
|
||||
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
|
||||
JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle,
|
||||
/*float_in=*/false));
|
||||
/*float_in=*/false, /*align=*/0));
|
||||
bundle.duration = frame_durations[i];
|
||||
io.frames.push_back(std::move(bundle));
|
||||
}
|
||||
|
@ -2500,7 +2502,7 @@ TEST(DecodeTest, SkipFrameWithBlendingTest) {
|
|||
/*has_alpha=*/false,
|
||||
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
|
||||
JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr,
|
||||
&bundle_internal, /*float_in=*/false));
|
||||
&bundle_internal, /*float_in=*/false, /*align=*/0));
|
||||
bundle_internal.duration = 0;
|
||||
bundle_internal.use_for_next_frame = true;
|
||||
io.frames.push_back(std::move(bundle_internal));
|
||||
|
@ -2516,7 +2518,7 @@ TEST(DecodeTest, SkipFrameWithBlendingTest) {
|
|||
jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*has_alpha=*/false,
|
||||
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
|
||||
JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle,
|
||||
/*float_in=*/false));
|
||||
/*float_in=*/false, /*align=*/0));
|
||||
bundle.duration = frame_durations[i];
|
||||
// Create some variation in which frames depend on which.
|
||||
if (i != 3 && i != 9 && i != 10) {
|
||||
|
@ -2727,7 +2729,7 @@ TEST(DecodeTest, SkipFrameWithAlphaBlendingTest) {
|
|||
/*has_alpha=*/true,
|
||||
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
|
||||
JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr,
|
||||
&bundle_internal, /*float_in=*/false));
|
||||
&bundle_internal, /*float_in=*/false, /*align=*/0));
|
||||
bundle_internal.duration = 0;
|
||||
bundle_internal.use_for_next_frame = true;
|
||||
bundle_internal.origin = {13, 17};
|
||||
|
@ -2749,7 +2751,7 @@ TEST(DecodeTest, SkipFrameWithAlphaBlendingTest) {
|
|||
/*has_alpha=*/true,
|
||||
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
|
||||
JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle,
|
||||
/*float_in=*/false));
|
||||
/*float_in=*/false, /*align=*/0));
|
||||
bundle.duration = 5 + i;
|
||||
frame_durations_nc.push_back(5 + i);
|
||||
frame_durations_c.push_back(5 + i);
|
||||
|
@ -2981,135 +2983,145 @@ TEST(DecodeTest, SkipFrameWithAlphaBlendingTest) {
|
|||
}
|
||||
|
||||
TEST(DecodeTest, OrientedCroppedFrameTest) {
|
||||
size_t xsize = 90, ysize = 120;
|
||||
JxlPixelFormat format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
|
||||
const auto test = [](bool keep_orientation, uint32_t orientation,
|
||||
uint32_t resampling) {
|
||||
size_t xsize = 90, ysize = 120;
|
||||
JxlPixelFormat format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};
|
||||
size_t oxsize = (!keep_orientation && orientation > 4 ? ysize : xsize);
|
||||
size_t oysize = (!keep_orientation && orientation > 4 ? xsize : ysize);
|
||||
jxl::CodecInOut io;
|
||||
io.SetSize(xsize, ysize);
|
||||
io.metadata.m.SetUintSamples(16);
|
||||
io.metadata.m.color_encoding = jxl::ColorEncoding::SRGB(false);
|
||||
io.metadata.m.orientation = orientation;
|
||||
io.frames.clear();
|
||||
io.SetSize(xsize, ysize);
|
||||
|
||||
for (bool keep_orientation : {true, false}) {
|
||||
for (uint32_t orientation = 1; orientation <= 8; orientation++) {
|
||||
size_t oxsize = (!keep_orientation && orientation > 4 ? ysize : xsize);
|
||||
size_t oysize = (!keep_orientation && orientation > 4 ? xsize : ysize);
|
||||
jxl::CodecInOut io;
|
||||
io.SetSize(xsize, ysize);
|
||||
io.metadata.m.SetUintSamples(16);
|
||||
io.metadata.m.color_encoding = jxl::ColorEncoding::SRGB(false);
|
||||
io.metadata.m.orientation = orientation;
|
||||
io.frames.clear();
|
||||
io.SetSize(xsize, ysize);
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
size_t cropxsize = 1 + xsize * 2 / (i + 1);
|
||||
size_t cropysize = 1 + ysize * 3 / (i + 2);
|
||||
int cropx0 = i * 3 - 8;
|
||||
int cropy0 = i * 4 - 7;
|
||||
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
size_t cropxsize = 1 + xsize * 2 / (i + 1);
|
||||
size_t cropysize = 1 + ysize * 3 / (i + 2);
|
||||
int cropx0 = i * 3 - 8;
|
||||
int cropy0 = i * 4 - 7;
|
||||
std::vector<uint8_t> frame =
|
||||
jxl::test::GetSomeTestImage(cropxsize, cropysize, 4, i * 2);
|
||||
jxl::ImageBundle bundle(&io.metadata.m);
|
||||
EXPECT_TRUE(ConvertFromExternal(
|
||||
jxl::Span<const uint8_t>(frame.data(), frame.size()), cropxsize,
|
||||
cropysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false),
|
||||
/*has_alpha=*/true,
|
||||
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
|
||||
JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle,
|
||||
/*float_in=*/false, /*align=*/0));
|
||||
bundle.origin = {cropx0, cropy0};
|
||||
bundle.use_for_next_frame = true;
|
||||
io.frames.push_back(std::move(bundle));
|
||||
}
|
||||
|
||||
std::vector<uint8_t> frame =
|
||||
jxl::test::GetSomeTestImage(cropxsize, cropysize, 4, i * 2);
|
||||
jxl::ImageBundle bundle(&io.metadata.m);
|
||||
EXPECT_TRUE(ConvertFromExternal(
|
||||
jxl::Span<const uint8_t>(frame.data(), frame.size()), cropxsize,
|
||||
cropysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false),
|
||||
/*has_alpha=*/true,
|
||||
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16,
|
||||
JXL_BIG_ENDIAN, /*flipped_y=*/false, /*pool=*/nullptr, &bundle,
|
||||
/*float_in=*/false));
|
||||
bundle.origin = {cropx0, cropy0};
|
||||
bundle.use_for_next_frame = true;
|
||||
io.frames.push_back(std::move(bundle));
|
||||
}
|
||||
jxl::CompressParams cparams;
|
||||
cparams
|
||||
.SetLossless(); // Lossless to verify pixels exactly after roundtrip.
|
||||
cparams.speed_tier = jxl::SpeedTier::kThunder;
|
||||
cparams.resampling = resampling;
|
||||
jxl::AuxOut aux_out;
|
||||
jxl::PaddedBytes compressed;
|
||||
jxl::PassesEncoderState enc_state;
|
||||
EXPECT_TRUE(jxl::EncodeFile(cparams, &io, &enc_state, &compressed,
|
||||
jxl::GetJxlCms(), &aux_out, nullptr));
|
||||
|
||||
jxl::CompressParams cparams;
|
||||
cparams
|
||||
.SetLossless(); // Lossless to verify pixels exactly after roundtrip.
|
||||
cparams.speed_tier = jxl::SpeedTier::kThunder;
|
||||
jxl::AuxOut aux_out;
|
||||
jxl::PaddedBytes compressed;
|
||||
jxl::PassesEncoderState enc_state;
|
||||
EXPECT_TRUE(jxl::EncodeFile(cparams, &io, &enc_state, &compressed,
|
||||
jxl::GetJxlCms(), &aux_out, nullptr));
|
||||
// 0 is merged frame as decoded with coalescing enabled (default)
|
||||
// 1-3 are non-coalesced frames as decoded with coalescing disabled
|
||||
// 4 is the manually merged frame
|
||||
std::vector<uint8_t> frames[5];
|
||||
frames[4].resize(xsize * ysize * 8, 0);
|
||||
|
||||
// 0 is merged frame as decoded with coalescing enabled (default)
|
||||
// 1-3 are non-coalesced frames as decoded with coalescing disabled
|
||||
// 4 is the manually merged frame
|
||||
std::vector<uint8_t> frames[5];
|
||||
frames[4].resize(xsize * ysize * 8, 0);
|
||||
|
||||
// try both with and without coalescing
|
||||
for (auto coalescing : {JXL_TRUE, JXL_FALSE}) {
|
||||
// Independently decode all frames without any skipping, to create the
|
||||
// expected blended frames, for the actual tests below to compare with.
|
||||
{
|
||||
JxlDecoder* dec = JxlDecoderCreate(NULL);
|
||||
const uint8_t* next_in = compressed.data();
|
||||
size_t avail_in = compressed.size();
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetCoalescing(dec, coalescing));
|
||||
// try both with and without coalescing
|
||||
for (auto coalescing : {JXL_TRUE, JXL_FALSE}) {
|
||||
// Independently decode all frames without any skipping, to create the
|
||||
// expected blended frames, for the actual tests below to compare with.
|
||||
{
|
||||
JxlDecoder* dec = JxlDecoderCreate(NULL);
|
||||
const uint8_t* next_in = compressed.data();
|
||||
size_t avail_in = compressed.size();
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetCoalescing(dec, coalescing));
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderSetKeepOrientation(dec, keep_orientation));
|
||||
void* runner = JxlThreadParallelRunnerCreate(
|
||||
NULL, JxlThreadParallelRunnerDefaultNumWorkerThreads());
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetParallelRunner(
|
||||
dec, JxlThreadParallelRunner, runner));
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderSubscribeEvents(dec, JXL_DEC_FULL_IMAGE));
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in));
|
||||
for (size_t i = (coalescing ? 0 : 1); i < (coalescing ? 1 : 4); ++i) {
|
||||
EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
|
||||
JxlFrameHeader frame_header;
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderSetKeepOrientation(dec, keep_orientation));
|
||||
void* runner = JxlThreadParallelRunnerCreate(
|
||||
NULL, JxlThreadParallelRunnerDefaultNumWorkerThreads());
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetParallelRunner(
|
||||
dec, JxlThreadParallelRunner, runner));
|
||||
JxlDecoderGetFrameHeader(dec, &frame_header));
|
||||
size_t buffer_size;
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderSubscribeEvents(dec, JXL_DEC_FULL_IMAGE));
|
||||
JxlDecoderImageOutBufferSize(dec, &format, &buffer_size));
|
||||
if (coalescing) {
|
||||
EXPECT_EQ(xsize * ysize * 8, buffer_size);
|
||||
} else {
|
||||
EXPECT_EQ(frame_header.layer_info.xsize *
|
||||
frame_header.layer_info.ysize * 8,
|
||||
buffer_size);
|
||||
}
|
||||
frames[i].resize(buffer_size);
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderSetInput(dec, next_in, avail_in));
|
||||
for (size_t i = (coalescing ? 0 : 1); i < (coalescing ? 1 : 4); ++i) {
|
||||
EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER,
|
||||
JxlDecoderProcessInput(dec));
|
||||
JxlFrameHeader frame_header;
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderGetFrameHeader(dec, &frame_header));
|
||||
size_t buffer_size;
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderImageOutBufferSize(dec, &format, &buffer_size));
|
||||
if (coalescing)
|
||||
EXPECT_EQ(xsize * ysize * 8, buffer_size);
|
||||
else
|
||||
EXPECT_EQ(frame_header.layer_info.xsize *
|
||||
frame_header.layer_info.ysize * 8,
|
||||
buffer_size);
|
||||
frames[i].resize(buffer_size);
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS,
|
||||
JxlDecoderSetImageOutBuffer(
|
||||
dec, &format, frames[i].data(), frames[i].size()));
|
||||
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
|
||||
EXPECT_EQ(frame_header.layer_info.blend_info.blendmode,
|
||||
JXL_BLEND_REPLACE);
|
||||
if (coalescing) {
|
||||
EXPECT_EQ(frame_header.layer_info.xsize, oxsize);
|
||||
EXPECT_EQ(frame_header.layer_info.ysize, oysize);
|
||||
EXPECT_EQ(frame_header.layer_info.crop_x0, 0);
|
||||
EXPECT_EQ(frame_header.layer_info.crop_y0, 0);
|
||||
} else {
|
||||
// manually merge this layer
|
||||
int x0 = frame_header.layer_info.crop_x0;
|
||||
int y0 = frame_header.layer_info.crop_y0;
|
||||
int w = frame_header.layer_info.xsize;
|
||||
int h = frame_header.layer_info.ysize;
|
||||
for (int y = 0; y < static_cast<int>(oysize); y++) {
|
||||
if (y < y0 || y >= y0 + h) continue;
|
||||
// pointers do whole 16-bit RGBA pixels at a time
|
||||
uint64_t* row_merged = static_cast<uint64_t*>(
|
||||
(void*)(frames[4].data() + y * oxsize * 8));
|
||||
uint64_t* row_layer = static_cast<uint64_t*>(
|
||||
(void*)(frames[i].data() + (y - y0) * w * 8));
|
||||
for (int x = 0; x < static_cast<int>(oxsize); x++) {
|
||||
if (x < x0 || x >= x0 + w) continue;
|
||||
row_merged[x] = row_layer[x - x0];
|
||||
}
|
||||
JxlDecoderSetImageOutBuffer(dec, &format, frames[i].data(),
|
||||
frames[i].size()));
|
||||
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
|
||||
EXPECT_EQ(frame_header.layer_info.blend_info.blendmode,
|
||||
JXL_BLEND_REPLACE);
|
||||
if (coalescing) {
|
||||
EXPECT_EQ(frame_header.layer_info.xsize, oxsize);
|
||||
EXPECT_EQ(frame_header.layer_info.ysize, oysize);
|
||||
EXPECT_EQ(frame_header.layer_info.crop_x0, 0);
|
||||
EXPECT_EQ(frame_header.layer_info.crop_y0, 0);
|
||||
} else {
|
||||
// manually merge this layer
|
||||
int x0 = frame_header.layer_info.crop_x0;
|
||||
int y0 = frame_header.layer_info.crop_y0;
|
||||
int w = frame_header.layer_info.xsize;
|
||||
int h = frame_header.layer_info.ysize;
|
||||
for (int y = 0; y < static_cast<int>(oysize); y++) {
|
||||
if (y < y0 || y >= y0 + h) continue;
|
||||
// pointers do whole 16-bit RGBA pixels at a time
|
||||
uint64_t* row_merged = static_cast<uint64_t*>(
|
||||
(void*)(frames[4].data() + y * oxsize * 8));
|
||||
uint64_t* row_layer = static_cast<uint64_t*>(
|
||||
(void*)(frames[i].data() + (y - y0) * w * 8));
|
||||
for (int x = 0; x < static_cast<int>(oxsize); x++) {
|
||||
if (x < x0 || x >= x0 + w) continue;
|
||||
row_merged[x] = row_layer[x - x0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// After all frames were decoded, JxlDecoderProcessInput should return
|
||||
// success to indicate all is done.
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
|
||||
JxlThreadParallelRunnerDestroy(runner);
|
||||
JxlDecoderDestroy(dec);
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(0u, jxl::test::ComparePixels(frames[0].data(), frames[4].data(),
|
||||
oxsize, oysize, format, format));
|
||||
// After all frames were decoded, JxlDecoderProcessInput should return
|
||||
// success to indicate all is done.
|
||||
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
|
||||
JxlThreadParallelRunnerDestroy(runner);
|
||||
JxlDecoderDestroy(dec);
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(0u, jxl::test::ComparePixels(frames[0].data(), frames[4].data(),
|
||||
oxsize, oysize, format, format));
|
||||
};
|
||||
|
||||
for (bool keep_orientation : {true, false}) {
|
||||
for (uint32_t orientation = 1; orientation <= 8; orientation++) {
|
||||
for (uint32_t resampling : {1, 2, 4, 8}) {
|
||||
SCOPED_TRACE(testing::Message()
|
||||
<< "keep_orientation: " << keep_orientation << ", "
|
||||
<< "orientation: " << orientation << ", "
|
||||
<< "resampling: " << resampling);
|
||||
test(keep_orientation, orientation, resampling);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -392,20 +392,33 @@ uint32_t ComputeBestMethod(
|
|||
HistogramParams::ANSHistogramStrategy ans_histogram_strategy) {
|
||||
size_t method = 0;
|
||||
float fcost = ComputeHistoAndDataCost(histogram, alphabet_size, 0);
|
||||
for (uint32_t shift = 0; shift <= ANS_LOG_TAB_SIZE;
|
||||
ans_histogram_strategy != HistogramParams::ANSHistogramStrategy::kPrecise
|
||||
? shift += 2
|
||||
: shift++) {
|
||||
auto try_shift = [&](size_t shift) {
|
||||
float c = ComputeHistoAndDataCost(histogram, alphabet_size, shift + 1);
|
||||
if (c < fcost) {
|
||||
method = shift + 1;
|
||||
fcost = c;
|
||||
} else if (ans_histogram_strategy ==
|
||||
HistogramParams::ANSHistogramStrategy::kFast) {
|
||||
// do not be as precise if estimating cost.
|
||||
}
|
||||
};
|
||||
switch (ans_histogram_strategy) {
|
||||
case HistogramParams::ANSHistogramStrategy::kPrecise: {
|
||||
for (uint32_t shift = 0; shift <= ANS_LOG_TAB_SIZE; shift++) {
|
||||
try_shift(shift);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
case HistogramParams::ANSHistogramStrategy::kApproximate: {
|
||||
for (uint32_t shift = 0; shift <= ANS_LOG_TAB_SIZE; shift += 2) {
|
||||
try_shift(shift);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HistogramParams::ANSHistogramStrategy::kFast: {
|
||||
try_shift(0);
|
||||
try_shift(ANS_LOG_TAB_SIZE / 2);
|
||||
try_shift(ANS_LOG_TAB_SIZE);
|
||||
break;
|
||||
}
|
||||
};
|
||||
*cost = fcost;
|
||||
return method;
|
||||
}
|
||||
|
|
|
@ -27,16 +27,14 @@
|
|||
|
||||
namespace jxl {
|
||||
|
||||
uint32_t ComputeUsedOrders(const SpeedTier speed,
|
||||
const AcStrategyImage& ac_strategy,
|
||||
const Rect& rect) {
|
||||
// Use default orders for small images.
|
||||
if (ac_strategy.xsize() < 5 && ac_strategy.ysize() < 5) return 0;
|
||||
|
||||
std::pair<uint32_t, uint32_t> ComputeUsedOrders(
|
||||
const SpeedTier speed, const AcStrategyImage& ac_strategy,
|
||||
const Rect& rect) {
|
||||
// Only uses DCT8 = 0, so bitfield = 1.
|
||||
if (speed >= SpeedTier::kFalcon) return 1;
|
||||
if (speed >= SpeedTier::kFalcon) return {1, 1};
|
||||
|
||||
uint32_t ret = 0;
|
||||
uint32_t ret_customize = 0;
|
||||
size_t xsize_blocks = rect.xsize();
|
||||
size_t ysize_blocks = rect.ysize();
|
||||
// TODO(veluca): precompute when doing DCT.
|
||||
|
@ -45,19 +43,22 @@ uint32_t ComputeUsedOrders(const SpeedTier speed,
|
|||
for (size_t bx = 0; bx < xsize_blocks; ++bx) {
|
||||
int ord = kStrategyOrder[acs_row[bx].RawStrategy()];
|
||||
// Do not customize coefficient orders for blocks bigger than 32x32.
|
||||
ret |= 1u << ord;
|
||||
if (ord > 6) {
|
||||
continue;
|
||||
}
|
||||
ret |= 1u << ord;
|
||||
ret_customize |= 1u << ord;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
// Use default orders for small images.
|
||||
if (ac_strategy.xsize() < 5 && ac_strategy.ysize() < 5) return {ret, 0};
|
||||
return {ret, ret_customize};
|
||||
}
|
||||
|
||||
void ComputeCoeffOrder(SpeedTier speed, const ACImage& acs,
|
||||
const AcStrategyImage& ac_strategy,
|
||||
const FrameDimensions& frame_dim, uint32_t& used_orders,
|
||||
coeff_order_t* JXL_RESTRICT order) {
|
||||
uint16_t used_acs, coeff_order_t* JXL_RESTRICT order) {
|
||||
std::vector<int32_t> num_zeros(kCoeffOrderMaxSize);
|
||||
// If compressing at high speed and only using 8x8 DCTs, only consider a
|
||||
// subset of blocks.
|
||||
|
@ -144,6 +145,8 @@ void ComputeCoeffOrder(SpeedTier speed, const ACImage& acs,
|
|||
};
|
||||
auto mem = hwy::AllocateAligned<PosAndCount>(AcStrategy::kMaxCoeffArea);
|
||||
|
||||
std::vector<coeff_order_t> natural_order_buffer;
|
||||
|
||||
uint16_t computed = 0;
|
||||
for (uint8_t o = 0; o < AcStrategy::kNumValidStrategies; ++o) {
|
||||
uint8_t ord = kStrategyOrder[o];
|
||||
|
@ -151,17 +154,24 @@ void ComputeCoeffOrder(SpeedTier speed, const ACImage& acs,
|
|||
computed |= 1 << ord;
|
||||
AcStrategy acs = AcStrategy::FromRawStrategy(o);
|
||||
size_t sz = kDCTBlockSize * acs.covered_blocks_x() * acs.covered_blocks_y();
|
||||
|
||||
// Do nothing for transforms that don't appear.
|
||||
if ((1 << ord) & ~used_acs) continue;
|
||||
|
||||
if (natural_order_buffer.size() < sz) natural_order_buffer.resize(sz);
|
||||
acs.ComputeNaturalCoeffOrder(natural_order_buffer.data());
|
||||
|
||||
// Ensure natural coefficient order is not permuted if the order is
|
||||
// not transmitted.
|
||||
if ((1 << ord) & ~used_orders) {
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
size_t offset = CoeffOrderOffset(ord, c);
|
||||
JXL_DASSERT(CoeffOrderOffset(ord, c + 1) - offset == sz);
|
||||
SetDefaultOrder(AcStrategy::FromRawStrategy(o), &order[offset]);
|
||||
memcpy(&order[offset], natural_order_buffer.data(),
|
||||
sz * sizeof(*order));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const coeff_order_t* natural_coeff_order = acs.NaturalCoeffOrder();
|
||||
|
||||
bool is_nondefault = false;
|
||||
for (uint8_t c = 0; c < 3; c++) {
|
||||
|
@ -171,7 +181,7 @@ void ComputeCoeffOrder(SpeedTier speed, const ACImage& acs,
|
|||
JXL_DASSERT(CoeffOrderOffset(ord, c + 1) - offset == sz);
|
||||
float inv_sqrt_sz = 1.0f / std::sqrt(sz);
|
||||
for (size_t i = 0; i < sz; ++i) {
|
||||
size_t pos = natural_coeff_order[i];
|
||||
size_t pos = natural_order_buffer[i];
|
||||
pos_and_val[i].pos = pos;
|
||||
// We don't care for the exact number -> quantize number of zeros,
|
||||
// to get less permuted order.
|
||||
|
@ -188,7 +198,7 @@ void ComputeCoeffOrder(SpeedTier speed, const ACImage& acs,
|
|||
// Grab indices.
|
||||
for (size_t i = 0; i < sz; ++i) {
|
||||
order[offset + i] = pos_and_val[i].pos;
|
||||
is_nondefault |= natural_coeff_order[i] != pos_and_val[i].pos;
|
||||
is_nondefault |= natural_order_buffer[i] != pos_and_val[i].pos;
|
||||
}
|
||||
}
|
||||
if (!is_nondefault) {
|
||||
|
@ -232,12 +242,12 @@ void EncodePermutation(const coeff_order_t* JXL_RESTRICT order, size_t skip,
|
|||
|
||||
namespace {
|
||||
void EncodeCoeffOrder(const coeff_order_t* JXL_RESTRICT order, AcStrategy acs,
|
||||
std::vector<Token>* tokens, coeff_order_t* order_zigzag) {
|
||||
std::vector<Token>* tokens, coeff_order_t* order_zigzag,
|
||||
std::vector<coeff_order_t>& natural_order_lut) {
|
||||
const size_t llf = acs.covered_blocks_x() * acs.covered_blocks_y();
|
||||
const size_t size = kDCTBlockSize * llf;
|
||||
const coeff_order_t* natural_coeff_order_lut = acs.NaturalCoeffOrderLut();
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
order_zigzag[i] = natural_coeff_order_lut[order[i]];
|
||||
order_zigzag[i] = natural_order_lut[order[i]];
|
||||
}
|
||||
TokenizePermutation(order_zigzag, llf, size, tokens);
|
||||
}
|
||||
|
@ -250,15 +260,20 @@ void EncodeCoeffOrders(uint16_t used_orders,
|
|||
auto mem = hwy::AllocateAligned<coeff_order_t>(AcStrategy::kMaxCoeffArea);
|
||||
uint16_t computed = 0;
|
||||
std::vector<std::vector<Token>> tokens(1);
|
||||
std::vector<coeff_order_t> natural_order_lut;
|
||||
for (uint8_t o = 0; o < AcStrategy::kNumValidStrategies; ++o) {
|
||||
uint8_t ord = kStrategyOrder[o];
|
||||
if (computed & (1 << ord)) continue;
|
||||
computed |= 1 << ord;
|
||||
if ((used_orders & (1 << ord)) == 0) continue;
|
||||
AcStrategy acs = AcStrategy::FromRawStrategy(o);
|
||||
const size_t llf = acs.covered_blocks_x() * acs.covered_blocks_y();
|
||||
const size_t size = kDCTBlockSize * llf;
|
||||
if (natural_order_lut.size() < size) natural_order_lut.resize(size);
|
||||
acs.ComputeNaturalCoeffOrderLut(natural_order_lut.data());
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
EncodeCoeffOrder(&order[CoeffOrderOffset(ord, c)], acs, &tokens[0],
|
||||
mem.get());
|
||||
mem.get(), natural_order_lut);
|
||||
}
|
||||
}
|
||||
// Do not write anything if no order is used.
|
||||
|
|
|
@ -24,8 +24,9 @@
|
|||
namespace jxl {
|
||||
|
||||
// Orders that are actually used in part of image. `rect` is in block units.
|
||||
uint32_t ComputeUsedOrders(SpeedTier speed, const AcStrategyImage& ac_strategy,
|
||||
const Rect& rect);
|
||||
// Returns {orders that are used, orders that might be made non-default}.
|
||||
std::pair<uint32_t, uint32_t> ComputeUsedOrders(
|
||||
SpeedTier speed, const AcStrategyImage& ac_strategy, const Rect& rect);
|
||||
|
||||
// Modify zig-zag order, so that DCT bands with more zeros go later.
|
||||
// Order of DCT bands with same number of zeros is untouched, so
|
||||
|
@ -33,7 +34,7 @@ uint32_t ComputeUsedOrders(SpeedTier speed, const AcStrategyImage& ac_strategy,
|
|||
void ComputeCoeffOrder(SpeedTier speed, const ACImage& acs,
|
||||
const AcStrategyImage& ac_strategy,
|
||||
const FrameDimensions& frame_dim, uint32_t& used_orders,
|
||||
coeff_order_t* JXL_RESTRICT order);
|
||||
uint16_t used_acs, coeff_order_t* JXL_RESTRICT order);
|
||||
|
||||
void EncodeCoeffOrders(uint16_t used_orders,
|
||||
const coeff_order_t* JXL_RESTRICT order,
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "jxl/types.h"
|
||||
#include "lib/jxl/alpha.h"
|
||||
#include "lib/jxl/base/byte_order.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
#include "lib/jxl/color_management.h"
|
||||
#include "lib/jxl/common.h"
|
||||
|
||||
|
@ -108,7 +109,7 @@ Status PixelFormatToExternal(const JxlPixelFormat& pixel_format,
|
|||
Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
|
||||
size_t ysize, size_t bits_per_sample,
|
||||
JxlEndianness endianness, ThreadPool* pool,
|
||||
ImageF* channel, bool float_in) {
|
||||
ImageF* channel, bool float_in, size_t align) {
|
||||
// TODO(firsching): Avoid code duplication with the function below.
|
||||
if (bits_per_sample < 1 || bits_per_sample > 32) {
|
||||
return JXL_FAILURE("Invalid bits_per_sample value.");
|
||||
|
@ -124,8 +125,12 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
|
|||
// bits_per_sample > 1.
|
||||
const size_t bytes_per_pixel = DivCeil(bits_per_sample, jxl::kBitsPerByte);
|
||||
|
||||
const size_t row_size = xsize * bytes_per_pixel;
|
||||
if (ysize && bytes.size() / ysize < row_size) {
|
||||
const size_t last_row_size = xsize * bytes_per_pixel;
|
||||
const size_t row_size =
|
||||
(align > 1 ? jxl::DivCeil(last_row_size, align) * align : last_row_size);
|
||||
const size_t bytes_to_read = row_size * (ysize - 1) + last_row_size;
|
||||
if (xsize == 0 || ysize == 0) return JXL_FAILURE("Empty image");
|
||||
if (bytes.size() < bytes_to_read) {
|
||||
return JXL_FAILURE("Buffer size is too small");
|
||||
}
|
||||
JXL_ASSERT(channel->xsize() == xsize);
|
||||
|
@ -210,7 +215,7 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
|
|||
bool has_alpha, bool alpha_is_premultiplied,
|
||||
size_t bits_per_sample, JxlEndianness endianness,
|
||||
bool flipped_y, ThreadPool* pool, ImageBundle* ib,
|
||||
bool float_in) {
|
||||
bool float_in, size_t align) {
|
||||
if (bits_per_sample < 1 || bits_per_sample > 32) {
|
||||
return JXL_FAILURE("Invalid bits_per_sample value.");
|
||||
}
|
||||
|
@ -232,11 +237,17 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
|
|||
return JXL_FAILURE("not supported, try bits_per_sample=32");
|
||||
}
|
||||
|
||||
const size_t row_size = xsize * bytes_per_pixel;
|
||||
if (ysize && bytes.size() / ysize < row_size) {
|
||||
return JXL_FAILURE("Buffer size is too small");
|
||||
const size_t last_row_size = xsize * bytes_per_pixel;
|
||||
const size_t row_size =
|
||||
(align > 1 ? jxl::DivCeil(last_row_size, align) * align : last_row_size);
|
||||
const size_t bytes_to_read = row_size * (ysize - 1) + last_row_size;
|
||||
if (xsize == 0 || ysize == 0) return JXL_FAILURE("Empty image");
|
||||
if (bytes.size() < bytes_to_read) {
|
||||
return JXL_FAILURE(
|
||||
"Buffer size is too small: expected at least %" PRIuS
|
||||
" bytes (= %" PRIuS " * %" PRIuS " * %" PRIuS "), got %" PRIuS " bytes",
|
||||
bytes_to_read, xsize, ysize, bytes_per_pixel, bytes.size());
|
||||
}
|
||||
|
||||
const bool little_endian =
|
||||
endianness == JXL_LITTLE_ENDIAN ||
|
||||
(endianness == JXL_NATIVE_ENDIAN && IsLittleEndian());
|
||||
|
@ -419,8 +430,8 @@ Status BufferToImageF(const JxlPixelFormat& pixel_format, size_t xsize,
|
|||
|
||||
JXL_RETURN_IF_ERROR(ConvertFromExternal(
|
||||
jxl::Span<const uint8_t>(static_cast<const uint8_t*>(buffer), size),
|
||||
xsize, ysize, bitdepth, pixel_format.endianness, pool, channel,
|
||||
float_in));
|
||||
xsize, ysize, bitdepth, pixel_format.endianness, pool, channel, float_in,
|
||||
pixel_format.align));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -441,7 +452,7 @@ Status BufferToImageBundle(const JxlPixelFormat& pixel_format, uint32_t xsize,
|
|||
/*has_alpha=*/pixel_format.num_channels == 2 ||
|
||||
pixel_format.num_channels == 4,
|
||||
/*alpha_is_premultiplied=*/false, bitdepth, pixel_format.endianness,
|
||||
/*flipped_y=*/false, pool, ib, float_in));
|
||||
/*flipped_y=*/false, pool, ib, float_in, pixel_format.align));
|
||||
ib->VerifyMetadata();
|
||||
|
||||
return true;
|
||||
|
|
|
@ -21,20 +21,10 @@
|
|||
#include "lib/jxl/image_bundle.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Return the size in bytes of a given xsize, channels and bits_per_sample
|
||||
// interleaved image.
|
||||
constexpr size_t RowSize(size_t xsize, size_t channels,
|
||||
size_t bits_per_sample) {
|
||||
return bits_per_sample == 1
|
||||
? DivCeil(xsize, kBitsPerByte)
|
||||
: xsize * channels * DivCeil(bits_per_sample, kBitsPerByte);
|
||||
}
|
||||
|
||||
Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
|
||||
size_t ysize, size_t bits_per_sample,
|
||||
JxlEndianness endianness, ThreadPool* pool,
|
||||
ImageF* channel, bool float_in);
|
||||
ImageF* channel, bool float_in, size_t align);
|
||||
|
||||
// Convert an interleaved pixel buffer to the internal ImageBundle
|
||||
// representation. This is the opposite of ConvertToExternal().
|
||||
|
@ -43,7 +33,7 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
|
|||
bool has_alpha, bool alpha_is_premultiplied,
|
||||
size_t bits_per_sample, JxlEndianness endianness,
|
||||
bool flipped_y, ThreadPool* pool, ImageBundle* ib,
|
||||
bool float_in);
|
||||
bool float_in, size_t align);
|
||||
Status BufferToImageF(const JxlPixelFormat& pixel_format, size_t xsize,
|
||||
size_t ysize, const void* buffer, size_t size,
|
||||
ThreadPool* pool, ImageF* channel);
|
||||
|
|
|
@ -32,7 +32,7 @@ void BM_EncExternalImage_ConvertImageRGBA(benchmark::State& state) {
|
|||
/*alpha_is_premultiplied=*/false,
|
||||
/*bits_per_sample=*/8, JXL_NATIVE_ENDIAN,
|
||||
/*flipped_y=*/false,
|
||||
/*pool=*/nullptr, &ib, /*float_in=*/false));
|
||||
/*pool=*/nullptr, &ib, /*float_in=*/false, /*align=*/0));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,18 +30,18 @@ TEST(ExternalImageTest, InvalidSize) {
|
|||
Span<const uint8_t>(buf, 10), /*xsize=*/10, /*ysize=*/100,
|
||||
/*c_current=*/ColorEncoding::SRGB(), /*has_alpha=*/true,
|
||||
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN,
|
||||
/*flipped_y=*/false, nullptr, &ib, /*float_in=*/false));
|
||||
/*flipped_y=*/false, nullptr, &ib, /*float_in=*/false, /*align=*/0));
|
||||
EXPECT_FALSE(ConvertFromExternal(
|
||||
Span<const uint8_t>(buf, sizeof(buf) - 1), /*xsize=*/10, /*ysize=*/100,
|
||||
/*c_current=*/ColorEncoding::SRGB(), /*has_alpha=*/true,
|
||||
/*alpha_is_premultiplied=*/false, /*bits_per_sample=*/16, JXL_BIG_ENDIAN,
|
||||
/*flipped_y=*/false, nullptr, &ib, /*float_in=*/false));
|
||||
/*flipped_y=*/false, nullptr, &ib, /*float_in=*/false, /*align=*/0));
|
||||
EXPECT_TRUE(ConvertFromExternal(
|
||||
Span<const uint8_t>(buf, sizeof(buf)), /*xsize=*/10,
|
||||
/*ysize=*/100, /*c_current=*/ColorEncoding::SRGB(),
|
||||
/*has_alpha=*/true, /*alpha_is_premultiplied=*/false,
|
||||
/*bits_per_sample=*/16, JXL_BIG_ENDIAN,
|
||||
/*flipped_y=*/false, nullptr, &ib, /*float_in=*/false));
|
||||
/*flipped_y=*/false, nullptr, &ib, /*float_in=*/false, /*align=*/0));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -61,7 +61,7 @@ TEST(ExternalImageTest, AlphaMissing) {
|
|||
/*c_current=*/ColorEncoding::SRGB(),
|
||||
/*has_alpha=*/true, /*alpha_is_premultiplied=*/false,
|
||||
/*bits_per_sample=*/8, JXL_BIG_ENDIAN,
|
||||
/*flipped_y=*/false, nullptr, &ib, /*float_in=*/false));
|
||||
/*flipped_y=*/false, nullptr, &ib, /*float_in=*/false, /*align=*/0));
|
||||
EXPECT_FALSE(ib.HasAlpha());
|
||||
}
|
||||
|
||||
|
|
|
@ -994,19 +994,20 @@ class LossyFrameEncoder {
|
|||
private:
|
||||
void ComputeAllCoeffOrders(const FrameDimensions& frame_dim) {
|
||||
PROFILER_FUNC;
|
||||
// No coefficient reordering in Falcon or faster.
|
||||
auto used_orders_info = ComputeUsedOrders(
|
||||
enc_state_->cparams.speed_tier, enc_state_->shared.ac_strategy,
|
||||
Rect(enc_state_->shared.raw_quant_field));
|
||||
enc_state_->used_orders.clear();
|
||||
enc_state_->used_orders.resize(
|
||||
enc_state_->progressive_splitter.GetNumPasses());
|
||||
enc_state_->progressive_splitter.GetNumPasses(),
|
||||
used_orders_info.second);
|
||||
for (size_t i = 0; i < enc_state_->progressive_splitter.GetNumPasses();
|
||||
i++) {
|
||||
// No coefficient reordering in Falcon or faster.
|
||||
if (enc_state_->cparams.speed_tier < SpeedTier::kFalcon) {
|
||||
enc_state_->used_orders[i] = ComputeUsedOrders(
|
||||
enc_state_->cparams.speed_tier, enc_state_->shared.ac_strategy,
|
||||
Rect(enc_state_->shared.raw_quant_field));
|
||||
}
|
||||
ComputeCoeffOrder(
|
||||
enc_state_->cparams.speed_tier, *enc_state_->coeffs[i],
|
||||
enc_state_->shared.ac_strategy, frame_dim, enc_state_->used_orders[i],
|
||||
used_orders_info.first,
|
||||
&enc_state_->shared
|
||||
.coeff_orders[i * enc_state_->shared.coeff_order_size]);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "jxl/encode.h"
|
||||
|
||||
#include "enc_color_management.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "jxl/decode.h"
|
||||
#include "jxl/decode_cxx.h"
|
||||
|
@ -206,7 +207,11 @@ void VerifyFrameEncoding(size_t xsize, size_t ysize, JxlEncoder* enc,
|
|||
|
||||
EXPECT_LE(
|
||||
ComputeDistance2(input_io.Main(), decoded_io.Main(), jxl::GetJxlCms()),
|
||||
#if JXL_HIGH_PRECISION
|
||||
1.8);
|
||||
#else
|
||||
4.8);
|
||||
#endif
|
||||
}
|
||||
|
||||
void VerifyFrameEncoding(JxlEncoder* enc,
|
||||
|
|
|
@ -217,9 +217,12 @@ HWY_NOINLINE void TestFastXYB() {
|
|||
Image3F xyb(kChunk * kChunk, kChunk);
|
||||
std::vector<uint8_t> roundtrip(kChunk * kChunk * kChunk * 3);
|
||||
ToXYB(ib, nullptr, &xyb, GetJxlCms());
|
||||
jxl::HWY_NAMESPACE::FastXYBTosRGB8(
|
||||
xyb, Rect(xyb), Rect(xyb), nullptr, Rect(), /*is_rgba=*/false,
|
||||
roundtrip.data(), xyb.xsize(), xyb.xsize() * 3);
|
||||
for (int y = 0; y < kChunk; y++) {
|
||||
const float* xyba[4] = {xyb.PlaneRow(0, y), xyb.PlaneRow(1, y),
|
||||
xyb.PlaneRow(2, y), nullptr};
|
||||
jxl::HWY_NAMESPACE::FastXYBTosRGB8(
|
||||
xyba, roundtrip.data() + 3 * xyb.xsize() * y, false, xyb.xsize());
|
||||
}
|
||||
for (int ir = 0; ir < kChunk; ir++) {
|
||||
for (int ig = 0; ig < kChunk; ig++) {
|
||||
for (int ib = 0; ib < kChunk; ib++) {
|
||||
|
|
|
@ -234,6 +234,13 @@ Status ExtraChannelInfo::VisitFields(Visitor* JXL_RESTRICT visitor) {
|
|||
JXL_QUIET_RETURN_IF_ERROR(visitor->U32(Val(1), Bits(2), BitsOffset(4, 3),
|
||||
BitsOffset(8, 19), 1, &cfa_channel));
|
||||
}
|
||||
|
||||
if (type == ExtraChannel::kUnknown ||
|
||||
(int(ExtraChannel::kReserved0) <= int(type) &&
|
||||
int(type) <= int(ExtraChannel::kReserved7))) {
|
||||
return JXL_FAILURE("Unknown extra channel (bits %u, shift %u, name '%s')\n",
|
||||
bit_depth.bits_per_sample, dim_shift, name.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -245,7 +245,7 @@ TEST(JxlTest, RoundtripResample2MT) {
|
|||
// file size.
|
||||
EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 64500u);
|
||||
EXPECT_THAT(ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()),
|
||||
IsSlightlyBelow(300));
|
||||
IsSlightlyBelow(320));
|
||||
}
|
||||
|
||||
// Roundtrip the image using a parallel runner that executes single-threaded but
|
||||
|
@ -273,6 +273,30 @@ TEST(JxlTest, RoundtripOutOfOrderProcessing) {
|
|||
/*distmap=*/nullptr, &pool));
|
||||
}
|
||||
|
||||
TEST(JxlTest, RoundtripOutOfOrderProcessingBorder) {
|
||||
FakeParallelRunner fake_pool(/*order_seed=*/47, /*num_threads=*/8);
|
||||
ThreadPool pool(&JxlFakeParallelRunner, &fake_pool);
|
||||
const PaddedBytes orig =
|
||||
ReadTestData("imagecompression.info/flower_foveon.png");
|
||||
CodecInOut io;
|
||||
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
|
||||
// Image size is selected so that the block border needed is larger than the
|
||||
// amount of pixels available on the next block.
|
||||
io.ShrinkTo(513, 515);
|
||||
|
||||
CompressParams cparams;
|
||||
// Force epf so we end up needing a lot of border.
|
||||
cparams.epf = 3;
|
||||
cparams.resampling = 2;
|
||||
|
||||
DecompressParams dparams;
|
||||
CodecInOut io2;
|
||||
Roundtrip(&io, cparams, dparams, &pool, &io2);
|
||||
|
||||
EXPECT_GE(2.8, ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
|
||||
/*distmap=*/nullptr, &pool));
|
||||
}
|
||||
|
||||
TEST(JxlTest, RoundtripResample4) {
|
||||
ThreadPool* pool = nullptr;
|
||||
const PaddedBytes orig =
|
||||
|
@ -571,7 +595,7 @@ TEST(JxlTest, RoundtripSmallPatchesAlpha) {
|
|||
EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 2000u);
|
||||
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
|
||||
/*distmap=*/nullptr, pool),
|
||||
IsSlightlyBelow(0.22f));
|
||||
IsSlightlyBelow(0.24f));
|
||||
}
|
||||
|
||||
TEST(JxlTest, RoundtripSmallPatches) {
|
||||
|
@ -599,7 +623,7 @@ TEST(JxlTest, RoundtripSmallPatches) {
|
|||
EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 2000u);
|
||||
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
|
||||
/*distmap=*/nullptr, pool),
|
||||
IsSlightlyBelow(0.22f));
|
||||
IsSlightlyBelow(0.24f));
|
||||
}
|
||||
|
||||
// Test header encoding of original bits per sample
|
||||
|
@ -835,7 +859,7 @@ TEST(JxlTest, RoundtripAlphaResampling) {
|
|||
|
||||
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
|
||||
/*distmap=*/nullptr, pool),
|
||||
IsSlightlyBelow(4.6));
|
||||
IsSlightlyBelow(4.7));
|
||||
}
|
||||
|
||||
TEST(JxlTest, RoundtripAlphaResamplingOnlyAlpha) {
|
||||
|
@ -867,7 +891,7 @@ TEST(JxlTest, RoundtripAlphaResamplingOnlyAlpha) {
|
|||
|
||||
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
|
||||
/*distmap=*/nullptr, pool),
|
||||
IsSlightlyBelow(1.8));
|
||||
IsSlightlyBelow(1.85));
|
||||
}
|
||||
|
||||
TEST(JxlTest, RoundtripAlphaNonMultipleOf8) {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <queue>
|
||||
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
#include "lib/jxl/base/scope_guard.h"
|
||||
#include "lib/jxl/modular/encoding/context_predict.h"
|
||||
#include "lib/jxl/modular/options.h"
|
||||
|
||||
|
@ -196,6 +197,39 @@ Status DecodeModularChannelMAANS(BitReader *br, ANSSymbolReader *reader,
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (predictor == Predictor::Gradient && offset == 0 &&
|
||||
multiplier == 1 && reader->HuffRleOnly()) {
|
||||
JXL_DEBUG_V(8, "Gradient RLE (fjxl) very fast track.");
|
||||
uint32_t run = 0;
|
||||
uint32_t v = 0;
|
||||
pixel_type_w sv = 0;
|
||||
for (size_t y = 0; y < channel.h; y++) {
|
||||
pixel_type *JXL_RESTRICT r = channel.Row(y);
|
||||
const pixel_type *JXL_RESTRICT rtop = (y ? channel.Row(y - 1) : r - 1);
|
||||
const pixel_type *JXL_RESTRICT rtopleft =
|
||||
(y ? channel.Row(y - 1) - 1 : r - 1);
|
||||
pixel_type_w guess = (y ? rtop[0] : 0);
|
||||
if (run == 0) {
|
||||
reader->ReadHybridUintClusteredHuffRleOnly(ctx_id, br, &v, &run);
|
||||
sv = UnpackSigned(v);
|
||||
} else {
|
||||
run--;
|
||||
}
|
||||
r[0] = sv + guess;
|
||||
for (size_t x = 1; x < channel.w; x++) {
|
||||
pixel_type left = r[x - 1];
|
||||
pixel_type top = rtop[x];
|
||||
pixel_type topleft = rtopleft[x];
|
||||
pixel_type_w guess = ClampedGradient(top, left, topleft);
|
||||
if (!run) {
|
||||
reader->ReadHybridUintClusteredHuffRleOnly(ctx_id, br, &v, &run);
|
||||
sv = UnpackSigned(v);
|
||||
} else {
|
||||
run--;
|
||||
}
|
||||
r[x] = sv + guess;
|
||||
}
|
||||
}
|
||||
} else if (predictor == Predictor::Gradient && offset == 0 &&
|
||||
multiplier == 1) {
|
||||
JXL_DEBUG_V(8, "Gradient very fast track.");
|
||||
|
@ -387,7 +421,19 @@ Status ModularDecode(BitReader *br, Image &image, GroupHeader &header,
|
|||
if (image.channel.empty()) return true;
|
||||
|
||||
// decode transforms
|
||||
JXL_RETURN_IF_ERROR(Bundle::Read(br, &header));
|
||||
Status status = Bundle::Read(br, &header);
|
||||
if (!allow_truncated_group) JXL_RETURN_IF_ERROR(status);
|
||||
if (status.IsFatalError()) return status;
|
||||
if (!br->AllReadsWithinBounds()) {
|
||||
// Don't do/undo transforms if header is incomplete.
|
||||
header.transforms.clear();
|
||||
image.transform = header.transforms;
|
||||
for (size_t c = 0; c < image.channel.size(); c++) {
|
||||
ZeroFillImage(&image.channel[c].plane);
|
||||
}
|
||||
return Status(StatusCode::kNotEnoughBytes);
|
||||
}
|
||||
|
||||
JXL_DEBUG_V(3, "Image data underwent %" PRIuS " transformations: ",
|
||||
header.transforms.size());
|
||||
image.transform = header.transforms;
|
||||
|
@ -397,10 +443,7 @@ Status ModularDecode(BitReader *br, Image &image, GroupHeader &header,
|
|||
if (image.error) {
|
||||
return JXL_FAILURE("Corrupt file. Aborting.");
|
||||
}
|
||||
if (br->AllReadsWithinBounds()) {
|
||||
// Only check if the transforms list is complete.
|
||||
JXL_RETURN_IF_ERROR(ValidateChannelDimensions(image, *options));
|
||||
}
|
||||
JXL_RETURN_IF_ERROR(ValidateChannelDimensions(image, *options));
|
||||
|
||||
size_t nb_channels = image.channel.size();
|
||||
|
||||
|
@ -422,6 +465,15 @@ Status ModularDecode(BitReader *br, Image &image, GroupHeader &header,
|
|||
}
|
||||
if (num_chans == 0) return true;
|
||||
|
||||
size_t next_channel = 0;
|
||||
auto scope_guard = MakeScopeGuard([&]() {
|
||||
// Do not do anything if truncated groups are not allowed.
|
||||
if (!allow_truncated_group) return;
|
||||
for (size_t c = next_channel; c < nb_channels; c++) {
|
||||
ZeroFillImage(&image.channel[c].plane);
|
||||
}
|
||||
});
|
||||
|
||||
// Read tree.
|
||||
Tree tree_storage;
|
||||
std::vector<uint8_t> context_map_storage;
|
||||
|
@ -463,26 +515,29 @@ Status ModularDecode(BitReader *br, Image &image, GroupHeader &header,
|
|||
|
||||
// Read channels
|
||||
ANSSymbolReader reader(code, br, distance_multiplier);
|
||||
for (size_t i = 0; i < nb_channels; i++) {
|
||||
Channel &channel = image.channel[i];
|
||||
for (; next_channel < nb_channels; next_channel++) {
|
||||
Channel &channel = image.channel[next_channel];
|
||||
if (!channel.w || !channel.h) {
|
||||
continue; // skip empty channels
|
||||
}
|
||||
if (i >= image.nb_meta_channels && (channel.w > options->max_chan_size ||
|
||||
channel.h > options->max_chan_size)) {
|
||||
if (next_channel >= image.nb_meta_channels &&
|
||||
(channel.w > options->max_chan_size ||
|
||||
channel.h > options->max_chan_size)) {
|
||||
break;
|
||||
}
|
||||
JXL_RETURN_IF_ERROR(DecodeModularChannelMAANS(br, &reader, *context_map,
|
||||
*tree, header.wp_header, i,
|
||||
group_id, &image));
|
||||
JXL_RETURN_IF_ERROR(DecodeModularChannelMAANS(
|
||||
br, &reader, *context_map, *tree, header.wp_header, next_channel,
|
||||
group_id, &image));
|
||||
// Truncated group.
|
||||
if (!br->AllReadsWithinBounds()) {
|
||||
if (!allow_truncated_group) return JXL_FAILURE("Truncated input");
|
||||
ZeroFillImage(&channel.plane);
|
||||
while (++i < nb_channels) ZeroFillImage(&image.channel[i].plane);
|
||||
return Status(StatusCode::kNotEnoughBytes);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure no zero-filling happens even if next_channel < nb_channels.
|
||||
scope_guard.Disarm();
|
||||
|
||||
if (!reader.CheckANSFinalState()) {
|
||||
return JXL_FAILURE("ANS decode final state failed");
|
||||
}
|
||||
|
|
|
@ -68,6 +68,9 @@ Status Transform::MetaApply(Image &input) {
|
|||
"Transform: kPalette, begin_c=%" PRIu32 ", num_c=%" PRIu32
|
||||
", nb_colors=%" PRIu32 ", nb_deltas=%" PRIu32,
|
||||
begin_c, num_c, nb_colors, nb_deltas);
|
||||
if (input.bitdepth > 24) {
|
||||
return JXL_FAILURE("Palette is not allowed for bitdepth > 24");
|
||||
}
|
||||
return MetaPalette(input, begin_c, begin_c + num_c - 1, nb_colors,
|
||||
nb_deltas, lossy_palette);
|
||||
default:
|
||||
|
|
|
@ -42,14 +42,14 @@ Status InitializePassesSharedState(const FrameHeader& frame_header,
|
|||
}
|
||||
|
||||
shared->quant_dc = ImageB(frame_dim.xsize_blocks, frame_dim.ysize_blocks);
|
||||
if (!(frame_header.flags & FrameHeader::kUseDcFrame) || encoder) {
|
||||
shared->dc_storage =
|
||||
Image3F(frame_dim.xsize_blocks, frame_dim.ysize_blocks);
|
||||
} else {
|
||||
|
||||
bool use_dc_frame = !!(frame_header.flags & FrameHeader::kUseDcFrame);
|
||||
if (!encoder && use_dc_frame) {
|
||||
if (frame_header.dc_level == 4) {
|
||||
return JXL_FAILURE("Invalid DC level for kUseDcFrame: %u",
|
||||
frame_header.dc_level);
|
||||
}
|
||||
shared->dc_storage = Image3F();
|
||||
shared->dc = &shared->dc_frames[frame_header.dc_level];
|
||||
if (shared->dc->xsize() == 0) {
|
||||
return JXL_FAILURE(
|
||||
|
@ -58,10 +58,12 @@ Status InitializePassesSharedState(const FrameHeader& frame_header,
|
|||
frame_header.dc_level, frame_header.dc_level + 1);
|
||||
}
|
||||
ZeroFillImage(&shared->quant_dc);
|
||||
} else {
|
||||
shared->dc_storage =
|
||||
Image3F(frame_dim.xsize_blocks, frame_dim.ysize_blocks);
|
||||
shared->dc = &shared->dc_storage;
|
||||
}
|
||||
|
||||
shared->dc_storage = Image3F(frame_dim.xsize_blocks, frame_dim.ysize_blocks);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,6 @@ std::unique_ptr<RenderPipeline> RenderPipeline::Builder::Finalize(
|
|||
res->frame_dimensions_ = frame_dimensions;
|
||||
res->uses_noise_ = uses_noise_;
|
||||
res->group_completed_passes_.resize(frame_dimensions.num_groups);
|
||||
res->num_passes_ = num_passes_;
|
||||
res->channel_shifts_.resize(stages_.size());
|
||||
res->channel_shifts_[0].resize(num_c_);
|
||||
for (size_t i = 1; i < stages_.size(); i++) {
|
||||
|
|
|
@ -51,11 +51,7 @@ class RenderPipeline {
|
|||
public:
|
||||
class Builder {
|
||||
public:
|
||||
explicit Builder(size_t num_c, size_t num_passes)
|
||||
: num_c_(num_c), num_passes_(num_passes) {
|
||||
JXL_ASSERT(num_c > 0);
|
||||
JXL_ASSERT(num_passes > 0);
|
||||
}
|
||||
explicit Builder(size_t num_c) : num_c_(num_c) { JXL_ASSERT(num_c > 0); }
|
||||
|
||||
// Adds a stage to the pipeline. Must be called at least once; the last
|
||||
// added stage cannot have kInOut channels.
|
||||
|
@ -77,7 +73,6 @@ class RenderPipeline {
|
|||
private:
|
||||
std::vector<std::unique_ptr<RenderPipelineStage>> stages_;
|
||||
size_t num_c_;
|
||||
size_t num_passes_;
|
||||
bool use_simple_implementation_ = false;
|
||||
bool uses_noise_ = false;
|
||||
};
|
||||
|
@ -86,6 +81,13 @@ class RenderPipeline {
|
|||
|
||||
virtual ~RenderPipeline() = default;
|
||||
|
||||
Status IsInitialized() const {
|
||||
for (const auto& stage : stages_) {
|
||||
JXL_RETURN_IF_ERROR(stage->IsInitialized());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allocates storage to run with `num` threads.
|
||||
void PrepareForThreads(size_t num);
|
||||
|
||||
|
@ -95,9 +97,9 @@ class RenderPipeline {
|
|||
// different threads, provided that a different `thread_id` is given.
|
||||
RenderPipelineInput GetInputBuffers(size_t group_id, size_t thread_id);
|
||||
|
||||
bool ReceivedAllInput() const {
|
||||
size_t PassesWithAllInput() const {
|
||||
return *std::min_element(group_completed_passes_.begin(),
|
||||
group_completed_passes_.end()) == num_passes_;
|
||||
group_completed_passes_.end());
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -114,8 +116,6 @@ class RenderPipeline {
|
|||
// Indexed by thread_id
|
||||
std::vector<CacheAlignedUniquePtr> temp_buffers_;
|
||||
|
||||
size_t num_passes_;
|
||||
|
||||
friend class RenderPipelineInput;
|
||||
|
||||
private:
|
||||
|
|
|
@ -81,6 +81,8 @@ class RenderPipelineStage {
|
|||
virtual ~RenderPipelineStage() = default;
|
||||
|
||||
protected:
|
||||
virtual Status IsInitialized() const { return true; }
|
||||
|
||||
// Processes one row of input, producing the appropriate number of rows of
|
||||
// output. Input/output rows can be obtained by calls to
|
||||
// `GetInputRow`/`GetOutputRow`. `xsize+2*xextra` represents the total number
|
||||
|
@ -121,11 +123,11 @@ class RenderPipelineStage {
|
|||
float* GetOutputRow(const RowInfo& output_rows, size_t c,
|
||||
size_t offset) const {
|
||||
JXL_DASSERT(GetChannelMode(c) == RenderPipelineChannelMode::kInOut);
|
||||
JXL_DASSERT(offset <= 1 << settings_.shift_y);
|
||||
JXL_DASSERT(offset <= 1ul << settings_.shift_y);
|
||||
return output_rows[c][offset] + kRenderPipelineXOffset;
|
||||
}
|
||||
|
||||
const Settings settings_;
|
||||
Settings settings_;
|
||||
friend class RenderPipeline;
|
||||
friend class SimpleRenderPipeline;
|
||||
friend class LowMemoryRenderPipeline;
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace jxl {
|
|||
namespace {
|
||||
|
||||
TEST(RenderPipelineTest, Build) {
|
||||
RenderPipeline::Builder builder(/*num_c=*/1, /*num_passes=*/1);
|
||||
RenderPipeline::Builder builder(/*num_c=*/1);
|
||||
builder.AddStage(jxl::make_unique<UpsampleXSlowStage>());
|
||||
builder.AddStage(jxl::make_unique<UpsampleYSlowStage>());
|
||||
builder.AddStage(jxl::make_unique<Check0FinalStage>());
|
||||
|
@ -39,7 +39,7 @@ TEST(RenderPipelineTest, Build) {
|
|||
}
|
||||
|
||||
TEST(RenderPipelineTest, CallAllGroups) {
|
||||
RenderPipeline::Builder builder(/*num_c=*/1, /*num_passes=*/1);
|
||||
RenderPipeline::Builder builder(/*num_c=*/1);
|
||||
builder.AddStage(jxl::make_unique<UpsampleXSlowStage>());
|
||||
builder.AddStage(jxl::make_unique<UpsampleYSlowStage>());
|
||||
builder.AddStage(jxl::make_unique<Check0FinalStage>());
|
||||
|
@ -58,7 +58,7 @@ TEST(RenderPipelineTest, CallAllGroups) {
|
|||
input_buffers.Done();
|
||||
}
|
||||
|
||||
EXPECT_TRUE(pipeline->ReceivedAllInput());
|
||||
EXPECT_TRUE(pipeline->PassesWithAllInput() == 1);
|
||||
}
|
||||
|
||||
struct RenderPipelineTestInputSettings {
|
||||
|
@ -71,6 +71,8 @@ struct RenderPipelineTestInputSettings {
|
|||
// Short name for the encoder settings.
|
||||
std::string cparams_descr;
|
||||
|
||||
bool add_spot_color = false;
|
||||
|
||||
Splines splines;
|
||||
};
|
||||
|
||||
|
@ -94,6 +96,31 @@ TEST_P(RenderPipelineTestParam, PipelineTest) {
|
|||
}
|
||||
io.ShrinkTo(config.xsize, config.ysize);
|
||||
|
||||
if (config.add_spot_color) {
|
||||
jxl::ImageF spot(config.xsize, config.ysize);
|
||||
jxl::ZeroFillImage(&spot);
|
||||
|
||||
for (size_t y = 0; y < config.ysize; y++) {
|
||||
float* JXL_RESTRICT row = spot.Row(y);
|
||||
for (size_t x = 0; x < config.xsize; x++) {
|
||||
row[x] = ((x ^ y) & 255) * (1.f / 255.f);
|
||||
}
|
||||
}
|
||||
ExtraChannelInfo info;
|
||||
info.bit_depth.bits_per_sample = 8;
|
||||
info.dim_shift = 0;
|
||||
info.type = jxl::ExtraChannel::kSpotColor;
|
||||
info.spot_color[0] = 0.5f;
|
||||
info.spot_color[1] = 0.2f;
|
||||
info.spot_color[2] = 1.f;
|
||||
info.spot_color[3] = 0.5f;
|
||||
|
||||
io.metadata.m.extra_channel_info.push_back(info);
|
||||
std::vector<jxl::ImageF> ec;
|
||||
ec.push_back(std::move(spot));
|
||||
io.frames[0].SetExtraChannels(std::move(ec));
|
||||
}
|
||||
|
||||
PaddedBytes compressed;
|
||||
|
||||
PassesEncoderState enc_state;
|
||||
|
@ -103,6 +130,8 @@ TEST_P(RenderPipelineTestParam, PipelineTest) {
|
|||
|
||||
DecompressParams dparams;
|
||||
|
||||
dparams.render_spotcolors = true;
|
||||
|
||||
CodecInOut io_default;
|
||||
ASSERT_TRUE(DecodeFile(dparams, compressed, &io_default, &pool));
|
||||
CodecInOut io_slow_pipeline;
|
||||
|
@ -111,13 +140,19 @@ TEST_P(RenderPipelineTestParam, PipelineTest) {
|
|||
|
||||
ASSERT_EQ(io_default.frames.size(), io_slow_pipeline.frames.size());
|
||||
for (size_t i = 0; i < io_default.frames.size(); i++) {
|
||||
#if JXL_HIGH_PRECISION
|
||||
constexpr float kMaxError = 1e-5;
|
||||
#else
|
||||
constexpr float kMaxError = 1e-4;
|
||||
#endif
|
||||
VerifyRelativeError(*io_default.frames[i].color(),
|
||||
*io_slow_pipeline.frames[i].color(), 1e-5, 1e-5);
|
||||
*io_slow_pipeline.frames[i].color(), kMaxError,
|
||||
kMaxError);
|
||||
for (size_t ec = 0; ec < io_default.frames[i].extra_channels().size();
|
||||
ec++) {
|
||||
VerifyRelativeError(io_default.frames[i].extra_channels()[ec],
|
||||
io_slow_pipeline.frames[i].extra_channels()[ec], 1e-5,
|
||||
1e-5);
|
||||
io_slow_pipeline.frames[i].extra_channels()[ec],
|
||||
kMaxError, kMaxError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,11 +181,15 @@ Splines CreateTestSplines() {
|
|||
std::vector<RenderPipelineTestInputSettings> GeneratePipelineTests() {
|
||||
std::vector<RenderPipelineTestInputSettings> all_tests;
|
||||
|
||||
for (size_t size : {128, 256, 258, 777}) {
|
||||
std::pair<size_t, size_t> sizes[] = {
|
||||
{128, 128}, {256, 256}, {258, 258}, {533, 401}, {777, 777},
|
||||
};
|
||||
|
||||
for (auto size : sizes) {
|
||||
RenderPipelineTestInputSettings settings;
|
||||
settings.input_path = "imagecompression.info/flower_foveon.png";
|
||||
settings.xsize = size;
|
||||
settings.ysize = size;
|
||||
settings.xsize = size.first;
|
||||
settings.ysize = size.second;
|
||||
|
||||
// Base settings.
|
||||
settings.cparams.butteraugli_distance = 1.0;
|
||||
|
@ -285,6 +324,15 @@ std::vector<RenderPipelineTestInputSettings> GeneratePipelineTests() {
|
|||
all_tests.push_back(s);
|
||||
}
|
||||
|
||||
{
|
||||
auto s = settings;
|
||||
s.input_path = "wide-gamut-tests/R2020-sRGB-blue.png";
|
||||
s.cparams_descr = "AlphaVarDCTUpsamplingEPF";
|
||||
s.cparams.epf = 1;
|
||||
s.cparams.ec_resampling = 2;
|
||||
all_tests.push_back(s);
|
||||
}
|
||||
|
||||
{
|
||||
auto s = settings;
|
||||
s.cparams.modular_mode = true;
|
||||
|
@ -301,6 +349,13 @@ std::vector<RenderPipelineTestInputSettings> GeneratePipelineTests() {
|
|||
s.cparams.ec_resampling = 2;
|
||||
all_tests.push_back(s);
|
||||
}
|
||||
|
||||
{
|
||||
auto s = settings;
|
||||
s.cparams_descr = "SpotColor";
|
||||
s.add_spot_color = true;
|
||||
all_tests.push_back(s);
|
||||
}
|
||||
}
|
||||
|
||||
#if JPEGXL_ENABLE_TRANSCODE_JPEG
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "lib/jxl/render_pipeline/simple_render_pipeline.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
void SimpleRenderPipeline::PrepareForThreadsInternal(size_t num) {
|
||||
if (!channel_data_.empty()) {
|
||||
return;
|
||||
|
@ -25,45 +26,52 @@ void SimpleRenderPipeline::PrepareForThreadsInternal(size_t num) {
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<ImageF*, Rect>> SimpleRenderPipeline::PrepareBuffers(
|
||||
size_t group_id, size_t thread_id) {
|
||||
std::vector<std::pair<ImageF*, Rect>> ret;
|
||||
Rect SimpleRenderPipeline::MakeChannelRect(size_t group_id, size_t channel,
|
||||
bool is_color) {
|
||||
size_t base_color_shift =
|
||||
CeilLog2Nonzero(frame_dimensions_.xsize_upsampled_padded /
|
||||
frame_dimensions_.xsize_padded);
|
||||
|
||||
const size_t gx = group_id % frame_dimensions_.xsize_groups;
|
||||
const size_t gy = group_id / frame_dimensions_.xsize_groups;
|
||||
size_t xgroupdim = (frame_dimensions_.group_dim << base_color_shift) >>
|
||||
channel_shifts_[0][channel].first;
|
||||
size_t ygroupdim = (frame_dimensions_.group_dim << base_color_shift) >>
|
||||
channel_shifts_[0][channel].second;
|
||||
return Rect(kRenderPipelineXOffset + gx * xgroupdim,
|
||||
kRenderPipelineXOffset + gy * ygroupdim, xgroupdim, ygroupdim,
|
||||
kRenderPipelineXOffset +
|
||||
DivCeil(frame_dimensions_.GetUpsampledXSize(is_color),
|
||||
1 << channel_shifts_[0][channel].first),
|
||||
kRenderPipelineXOffset +
|
||||
DivCeil(frame_dimensions_.GetUpsampledYSize(is_color),
|
||||
1 << channel_shifts_[0][channel].second));
|
||||
}
|
||||
|
||||
std::vector<std::pair<ImageF*, Rect>> SimpleRenderPipeline::PrepareBuffers(
|
||||
size_t group_id, size_t thread_id) {
|
||||
std::vector<std::pair<ImageF*, Rect>> ret;
|
||||
for (size_t c = 0; c < channel_data_.size(); c++) {
|
||||
const size_t gx = group_id % frame_dimensions_.xsize_groups;
|
||||
const size_t gy = group_id / frame_dimensions_.xsize_groups;
|
||||
size_t xgroupdim = (frame_dimensions_.group_dim << base_color_shift) >>
|
||||
channel_shifts_[0][c].first;
|
||||
size_t ygroupdim = (frame_dimensions_.group_dim << base_color_shift) >>
|
||||
channel_shifts_[0][c].second;
|
||||
bool is_color_c =
|
||||
c < 3 || (uses_noise_ && c >= channel_shifts_[0].size() - 3);
|
||||
const Rect rect(kRenderPipelineXOffset + gx * xgroupdim,
|
||||
kRenderPipelineXOffset + gy * ygroupdim, xgroupdim,
|
||||
ygroupdim,
|
||||
kRenderPipelineXOffset +
|
||||
DivCeil(frame_dimensions_.GetUpsampledXSize(is_color_c),
|
||||
1 << channel_shifts_[0][c].first),
|
||||
kRenderPipelineXOffset +
|
||||
DivCeil(frame_dimensions_.GetUpsampledYSize(is_color_c),
|
||||
1 << channel_shifts_[0][c].second));
|
||||
ret.emplace_back(&channel_data_[c], rect);
|
||||
ret.emplace_back(&channel_data_[c],
|
||||
MakeChannelRect(group_id, c, is_color_c));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SimpleRenderPipeline::ProcessBuffers(size_t group_id, size_t thread_id) {
|
||||
if (!ReceivedAllInput()) return;
|
||||
if (PassesWithAllInput() <= processed_passes_) return;
|
||||
processed_passes_++;
|
||||
|
||||
for (const auto& ch : channel_data_) {
|
||||
(void)ch;
|
||||
for (size_t c = 0; c < channel_data_.size(); c++) {
|
||||
Rect r = MakeChannelRect(group_id, c, false);
|
||||
(void)r;
|
||||
JXL_CHECK_IMAGE_INITIALIZED(
|
||||
ch, Rect(kRenderPipelineXOffset, kRenderPipelineXOffset,
|
||||
ch.xsize() - 2 * kRenderPipelineXOffset,
|
||||
ch.ysize() - 2 * kRenderPipelineXOffset));
|
||||
channel_data_[c], Rect(kRenderPipelineXOffset, kRenderPipelineXOffset,
|
||||
r.xsize(), r.ysize()));
|
||||
}
|
||||
|
||||
for (size_t stage_id = 0; stage_id < stages_.size(); stage_id++) {
|
||||
const auto& stage = stages_[stage_id];
|
||||
// Prepare buffers for kInOut channels.
|
||||
|
@ -109,14 +117,14 @@ void SimpleRenderPipeline::ProcessBuffers(size_t group_id, size_t thread_id) {
|
|||
}
|
||||
}
|
||||
// Vertical mirroring.
|
||||
for (int iy = 0; iy < static_cast<int>(stage->settings_.border_y); iy++) {
|
||||
memcpy(get_row(c, -iy - 1) - stage->settings_.border_x,
|
||||
get_row(c, iy) - stage->settings_.border_x,
|
||||
for (int y = 0; y < static_cast<int>(stage->settings_.border_y); y++) {
|
||||
memcpy(get_row(c, -y - 1) - stage->settings_.border_x,
|
||||
get_row(c, y) - stage->settings_.border_x,
|
||||
sizeof(float) *
|
||||
(input_sizes[c].first + 2 * stage->settings_.border_x));
|
||||
memcpy(
|
||||
get_row(c, input_sizes[c].second + iy) - stage->settings_.border_x,
|
||||
get_row(c, input_sizes[c].second - iy - 1) -
|
||||
get_row(c, input_sizes[c].second + y) - stage->settings_.border_x,
|
||||
get_row(c, input_sizes[c].second - y - 1) -
|
||||
stage->settings_.border_x,
|
||||
sizeof(float) *
|
||||
(input_sizes[c].first + 2 * stage->settings_.border_x));
|
||||
|
@ -145,6 +153,9 @@ void SimpleRenderPipeline::ProcessBuffers(size_t group_id, size_t thread_id) {
|
|||
for (size_t y = 0; y < ysize; y++) {
|
||||
// Prepare input rows.
|
||||
for (size_t c = 0; c < channel_data_.size(); c++) {
|
||||
if (stage->GetChannelMode(c) == RenderPipelineChannelMode::kIgnored) {
|
||||
continue;
|
||||
}
|
||||
input_rows[c].resize(2 * border_y + 1);
|
||||
for (int iy = -border_y; iy <= border_y; iy++) {
|
||||
input_rows[c][iy + border_y] =
|
||||
|
@ -174,12 +185,12 @@ void SimpleRenderPipeline::ProcessBuffers(size_t group_id, size_t thread_id) {
|
|||
}
|
||||
channel_data_[c] = std::move(new_channels[c]);
|
||||
}
|
||||
for (const auto& ch : channel_data_) {
|
||||
(void)ch;
|
||||
for (size_t c = 0; c < channel_data_.size(); c++) {
|
||||
Rect r = MakeChannelRect(group_id, c, false);
|
||||
(void)r;
|
||||
JXL_CHECK_IMAGE_INITIALIZED(
|
||||
ch, Rect(kRenderPipelineXOffset, kRenderPipelineXOffset,
|
||||
ch.xsize() - 2 * kRenderPipelineXOffset,
|
||||
ch.ysize() - 2 * kRenderPipelineXOffset));
|
||||
channel_data_[c], Rect(kRenderPipelineXOffset, kRenderPipelineXOffset,
|
||||
r.xsize(), r.ysize()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,10 @@ class SimpleRenderPipeline : public RenderPipeline {
|
|||
// Full frame buffers. Both X and Y dimensions are padded by
|
||||
// kRenderPipelineXOffset.
|
||||
std::vector<ImageF> channel_data_;
|
||||
size_t processed_passes_ = 0;
|
||||
|
||||
private:
|
||||
Rect MakeChannelRect(size_t group_id, size_t channel, bool is_color);
|
||||
};
|
||||
|
||||
} // namespace jxl
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jxl/render_pipeline/stage_blending.h"
|
||||
|
||||
#undef HWY_TARGET_INCLUDE
|
||||
#define HWY_TARGET_INCLUDE "lib/jxl/render_pipeline/stage_blending.cc"
|
||||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
#include "lib/jxl/blending.h"
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jxl {
|
||||
namespace HWY_NAMESPACE {
|
||||
|
||||
class BlendingStage : public RenderPipelineStage {
|
||||
public:
|
||||
explicit BlendingStage(const PassesDecoderState* dec_state,
|
||||
const ColorEncoding& frame_color_encoding)
|
||||
: RenderPipelineStage(RenderPipelineStage::Settings()),
|
||||
state_(*dec_state->shared) {
|
||||
image_xsize_ = state_.frame_header.nonserialized_metadata->xsize();
|
||||
image_ysize_ = state_.frame_header.nonserialized_metadata->ysize();
|
||||
extra_channel_info_ =
|
||||
&state_.frame_header.nonserialized_metadata->m.extra_channel_info;
|
||||
info_ = state_.frame_header.blending_info;
|
||||
const std::vector<BlendingInfo>& ec_info =
|
||||
state_.frame_header.extra_channel_blending_info;
|
||||
ImageBundle& bg = *state_.reference_frames[info_.source].frame;
|
||||
bg_ = &bg;
|
||||
if (bg.xsize() == 0 && bg.ysize() == 0) {
|
||||
// there is no background, assume it to be all zeroes
|
||||
ImageBundle empty(&state_.metadata->m);
|
||||
Image3F color(image_xsize_, image_ysize_);
|
||||
ZeroFillImage(&color);
|
||||
empty.SetFromImage(std::move(color), frame_color_encoding);
|
||||
if (!ec_info.empty()) {
|
||||
std::vector<ImageF> ec;
|
||||
for (size_t i = 0; i < ec_info.size(); ++i) {
|
||||
ImageF eci(image_xsize_, image_ysize_);
|
||||
ZeroFillImage(&eci);
|
||||
ec.push_back(std::move(eci));
|
||||
}
|
||||
empty.SetExtraChannels(std::move(ec));
|
||||
}
|
||||
bg = std::move(empty);
|
||||
} else if (state_.reference_frames[info_.source].ib_is_in_xyb) {
|
||||
initialized_ = JXL_FAILURE(
|
||||
"Trying to blend XYB reference frame %i and non-XYB frame",
|
||||
info_.source);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bg.xsize() < image_xsize_ || bg.ysize() < image_ysize_ ||
|
||||
bg.origin.x0 != 0 || bg.origin.y0 != 0) {
|
||||
initialized_ = JXL_FAILURE("Trying to use a %" PRIuS "x%" PRIuS
|
||||
" crop as a background",
|
||||
bg.xsize(), bg.ysize());
|
||||
return;
|
||||
}
|
||||
if (state_.metadata->m.xyb_encoded) {
|
||||
if (!dec_state->output_encoding_info.color_encoding_is_original) {
|
||||
initialized_ = JXL_FAILURE("Blending in unsupported color space");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
blending_info_.resize(ec_info.size() + 1);
|
||||
auto make_blending = [&](const BlendingInfo& info, PatchBlending* pb) {
|
||||
pb->alpha_channel = info.alpha_channel;
|
||||
pb->clamp = info.clamp;
|
||||
switch (info.mode) {
|
||||
case BlendMode::kReplace: {
|
||||
pb->mode = PatchBlendMode::kReplace;
|
||||
break;
|
||||
}
|
||||
case BlendMode::kAdd: {
|
||||
pb->mode = PatchBlendMode::kAdd;
|
||||
break;
|
||||
}
|
||||
case BlendMode::kMul: {
|
||||
pb->mode = PatchBlendMode::kMul;
|
||||
break;
|
||||
}
|
||||
case BlendMode::kBlend: {
|
||||
pb->mode = PatchBlendMode::kBlendAbove;
|
||||
break;
|
||||
}
|
||||
case BlendMode::kAlphaWeightedAdd: {
|
||||
pb->mode = PatchBlendMode::kAlphaWeightedAddAbove;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
JXL_ABORT("Invalid blend mode"); // should have failed to decode
|
||||
}
|
||||
}
|
||||
};
|
||||
make_blending(info_, &blending_info_[0]);
|
||||
for (size_t i = 0; i < ec_info.size(); i++) {
|
||||
make_blending(ec_info[i], &blending_info_[1 + i]);
|
||||
}
|
||||
}
|
||||
|
||||
Status IsInitialized() const override { return initialized_; }
|
||||
|
||||
void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
|
||||
size_t xextra, size_t xsize, size_t xpos, size_t ypos,
|
||||
float* JXL_RESTRICT temp) const final {
|
||||
PROFILER_ZONE("Blend");
|
||||
JXL_ASSERT(initialized_);
|
||||
const FrameOrigin& frame_origin = state_.frame_header.frame_origin;
|
||||
ssize_t bg_xpos = frame_origin.x0 + static_cast<ssize_t>(xpos);
|
||||
ssize_t bg_ypos = frame_origin.y0 + static_cast<ssize_t>(ypos);
|
||||
int offset = 0;
|
||||
if (bg_xpos + static_cast<ssize_t>(xsize) <= 0 ||
|
||||
frame_origin.x0 >= static_cast<ssize_t>(image_xsize_) || bg_ypos < 0 ||
|
||||
bg_ypos >= static_cast<ssize_t>(image_ysize_)) {
|
||||
return;
|
||||
}
|
||||
if (bg_xpos < 0) {
|
||||
xpos -= bg_xpos;
|
||||
offset -= bg_xpos;
|
||||
xsize += bg_xpos;
|
||||
bg_xpos = 0;
|
||||
}
|
||||
if (bg_xpos + xsize > image_xsize_) {
|
||||
xsize =
|
||||
std::max<ssize_t>(0, static_cast<ssize_t>(image_xsize_) - bg_xpos);
|
||||
}
|
||||
std::vector<const float*> bg_row_ptrs_(input_rows.size());
|
||||
std::vector<float*> fg_row_ptrs_(input_rows.size());
|
||||
for (size_t c = 0; c < input_rows.size(); ++c) {
|
||||
bg_row_ptrs_[c] =
|
||||
(c < 3 ? bg_->color()->ConstPlaneRow(c, bg_ypos)
|
||||
: bg_->extra_channels()[c - 3].ConstRow(bg_ypos)) +
|
||||
bg_xpos;
|
||||
fg_row_ptrs_[c] = GetInputRow(input_rows, c, offset);
|
||||
}
|
||||
PerformBlending(bg_row_ptrs_.data(), fg_row_ptrs_.data(),
|
||||
fg_row_ptrs_.data(), 0, xsize, blending_info_[0],
|
||||
blending_info_.data() + 1, *extra_channel_info_);
|
||||
}
|
||||
|
||||
RenderPipelineChannelMode GetChannelMode(size_t c) const final {
|
||||
return RenderPipelineChannelMode::kInPlace;
|
||||
}
|
||||
|
||||
private:
|
||||
const PassesSharedState& state_;
|
||||
BlendingInfo info_;
|
||||
ImageBundle* bg_;
|
||||
Status initialized_ = true;
|
||||
size_t image_xsize_;
|
||||
size_t image_ysize_;
|
||||
std::vector<PatchBlending> blending_info_;
|
||||
const std::vector<ExtraChannelInfo>* extra_channel_info_;
|
||||
};
|
||||
|
||||
std::unique_ptr<RenderPipelineStage> GetBlendingStage(
|
||||
const PassesDecoderState* dec_state,
|
||||
const ColorEncoding& frame_color_encoding) {
|
||||
return jxl::make_unique<BlendingStage>(dec_state, frame_color_encoding);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jxl
|
||||
HWY_AFTER_NAMESPACE();
|
||||
|
||||
#if HWY_ONCE
|
||||
namespace jxl {
|
||||
|
||||
HWY_EXPORT(GetBlendingStage);
|
||||
|
||||
std::unique_ptr<RenderPipelineStage> GetBlendingStage(
|
||||
const PassesDecoderState* dec_state,
|
||||
const ColorEncoding& frame_color_encoding) {
|
||||
return HWY_DYNAMIC_DISPATCH(GetBlendingStage)(dec_state,
|
||||
frame_color_encoding);
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
#endif
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JXL_RENDER_PIPELINE_STAGE_BLENDING_H_
|
||||
#define LIB_JXL_RENDER_PIPELINE_STAGE_BLENDING_H_
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "lib/jxl/dec_cache.h"
|
||||
#include "lib/jxl/render_pipeline/render_pipeline_stage.h"
|
||||
#include "lib/jxl/splines.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Applies blending if applicable.
|
||||
std::unique_ptr<RenderPipelineStage> GetBlendingStage(
|
||||
const PassesDecoderState* dec_state,
|
||||
const ColorEncoding& frame_color_encoding);
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_JXL_RENDER_PIPELINE_STAGE_BLENDING_H_
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jxl/render_pipeline/stage_spot.h"
|
||||
|
||||
namespace jxl {
|
||||
class SpotColorStage : public RenderPipelineStage {
|
||||
public:
|
||||
explicit SpotColorStage(size_t spot_c, const float* spot_color)
|
||||
: RenderPipelineStage(RenderPipelineStage::Settings()),
|
||||
spot_c_(spot_c),
|
||||
spot_color_(spot_color) {
|
||||
JXL_ASSERT(spot_c_ >= 3);
|
||||
}
|
||||
|
||||
void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
|
||||
size_t xextra, size_t xsize, size_t xpos, size_t ypos,
|
||||
float* JXL_RESTRICT temp) const final {
|
||||
// TODO(veluca): add SIMD.
|
||||
PROFILER_ZONE("RenderSpotColors");
|
||||
float scale = spot_color_[3];
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
float* JXL_RESTRICT p = GetInputRow(input_rows, c, 0);
|
||||
const float* JXL_RESTRICT s = GetInputRow(input_rows, spot_c_, 0);
|
||||
for (ssize_t x = -xextra; x < ssize_t(xsize + xextra); x++) {
|
||||
float mix = scale * s[x];
|
||||
p[x] = mix * spot_color_[c] + (1.0f - mix) * p[x];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RenderPipelineChannelMode GetChannelMode(size_t c) const final {
|
||||
return c < 3 ? RenderPipelineChannelMode::kInPlace
|
||||
: c == spot_c_ ? RenderPipelineChannelMode::kInput
|
||||
: RenderPipelineChannelMode::kIgnored;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t spot_c_;
|
||||
const float* spot_color_;
|
||||
};
|
||||
|
||||
std::unique_ptr<RenderPipelineStage> GetSpotColorStage(
|
||||
size_t spot_c, const float* spot_color) {
|
||||
return jxl::make_unique<SpotColorStage>(spot_c, spot_color);
|
||||
}
|
||||
|
||||
} // namespace jxl
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JXL_RENDER_PIPELINE_STAGE_SPOT_H_
|
||||
#define LIB_JXL_RENDER_PIPELINE_STAGE_SPOT_H_
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "lib/jxl/render_pipeline/render_pipeline_stage.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Render the spot color channels.
|
||||
std::unique_ptr<RenderPipelineStage> GetSpotColorStage(size_t spot_c,
|
||||
const float* spot_color);
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_JXL_RENDER_PIPELINE_STAGE_SPOT_H_
|
|
@ -0,0 +1,317 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jxl/render_pipeline/stage_write.h"
|
||||
|
||||
#include "lib/jxl/common.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
|
||||
#undef HWY_TARGET_INCLUDE
|
||||
#define HWY_TARGET_INCLUDE "lib/jxl/render_pipeline/stage_write.cc"
|
||||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jxl {
|
||||
namespace HWY_NAMESPACE {
|
||||
|
||||
template <typename D, typename V>
|
||||
void StoreRGBA(D d, V r, V g, V b, V a, bool alpha, size_t n, size_t extra,
|
||||
uint8_t* buf) {
|
||||
#if HWY_TARGET == HWY_SCALAR
|
||||
buf[0] = r.raw;
|
||||
buf[1] = g.raw;
|
||||
buf[2] = b.raw;
|
||||
if (alpha) {
|
||||
buf[3] = a.raw;
|
||||
}
|
||||
#elif HWY_TARGET == HWY_NEON
|
||||
if (alpha) {
|
||||
uint8x8x4_t data = {r.raw, g.raw, b.raw, a.raw};
|
||||
if (extra >= 8) {
|
||||
vst4_u8(buf, data);
|
||||
} else {
|
||||
uint8_t tmp[8 * 4];
|
||||
vst4_u8(tmp, data);
|
||||
memcpy(buf, tmp, n * 4);
|
||||
}
|
||||
} else {
|
||||
uint8x8x3_t data = {r.raw, g.raw, b.raw};
|
||||
if (extra >= 8) {
|
||||
vst3_u8(buf, data);
|
||||
} else {
|
||||
uint8_t tmp[8 * 3];
|
||||
vst3_u8(tmp, data);
|
||||
memcpy(buf, tmp, n * 3);
|
||||
}
|
||||
}
|
||||
#else
|
||||
// TODO(veluca): implement this for x86.
|
||||
size_t mul = alpha ? 4 : 3;
|
||||
HWY_ALIGN uint8_t bytes[16];
|
||||
Store(r, d, bytes);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
buf[mul * i] = bytes[i];
|
||||
}
|
||||
Store(g, d, bytes);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
buf[mul * i + 1] = bytes[i];
|
||||
}
|
||||
Store(b, d, bytes);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
buf[mul * i + 2] = bytes[i];
|
||||
}
|
||||
if (alpha) {
|
||||
Store(a, d, bytes);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
buf[4 * i + 3] = bytes[i];
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
class WriteToU8Stage : public RenderPipelineStage {
|
||||
public:
|
||||
WriteToU8Stage(uint8_t* rgb, size_t stride, size_t width, size_t height,
|
||||
bool rgba, bool has_alpha, size_t alpha_c)
|
||||
: RenderPipelineStage(RenderPipelineStage::Settings()),
|
||||
rgb_(rgb),
|
||||
stride_(stride),
|
||||
width_(width),
|
||||
height_(height),
|
||||
rgba_(rgba),
|
||||
has_alpha_(has_alpha),
|
||||
alpha_c_(alpha_c) {}
|
||||
|
||||
void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
|
||||
size_t xextra, size_t xsize, size_t xpos, size_t ypos,
|
||||
float* JXL_RESTRICT temp) const final {
|
||||
if (ypos >= height_) return;
|
||||
size_t bytes = rgba_ ? 4 : 3;
|
||||
const float* JXL_RESTRICT row_in_r = GetInputRow(input_rows, 0, 0);
|
||||
const float* JXL_RESTRICT row_in_g = GetInputRow(input_rows, 1, 0);
|
||||
const float* JXL_RESTRICT row_in_b = GetInputRow(input_rows, 2, 0);
|
||||
const float* JXL_RESTRICT row_in_a =
|
||||
has_alpha_ ? GetInputRow(input_rows, alpha_c_, 0) : nullptr;
|
||||
size_t base_ptr = ypos * stride_ + bytes * (xpos - xextra);
|
||||
using D = HWY_CAPPED(float, 4);
|
||||
const D d;
|
||||
D::Rebind<uint32_t> du;
|
||||
auto zero = Zero(d);
|
||||
auto one = Set(d, 1.0f);
|
||||
auto mul = Set(d, 255.0f);
|
||||
|
||||
ssize_t x0 = -RoundUpTo(xextra, Lanes(d));
|
||||
ssize_t x1 = RoundUpTo(xsize + xextra, Lanes(d));
|
||||
|
||||
for (ssize_t x = x0; x < x1; x += Lanes(d)) {
|
||||
auto rf = Clamp(zero, Load(d, row_in_r + x), one) * mul;
|
||||
auto gf = Clamp(zero, Load(d, row_in_g + x), one) * mul;
|
||||
auto bf = Clamp(zero, Load(d, row_in_b + x), one) * mul;
|
||||
auto af = row_in_a ? Clamp(zero, Load(d, row_in_a + x), one) * mul
|
||||
: Set(d, 255.0f);
|
||||
auto r8 = U8FromU32(BitCast(du, NearestInt(rf)));
|
||||
auto g8 = U8FromU32(BitCast(du, NearestInt(gf)));
|
||||
auto b8 = U8FromU32(BitCast(du, NearestInt(bf)));
|
||||
auto a8 = U8FromU32(BitCast(du, NearestInt(af)));
|
||||
size_t n = width_ - xpos - x;
|
||||
if (JXL_LIKELY(n >= Lanes(d))) {
|
||||
StoreRGBA(D::Rebind<uint8_t>(), r8, g8, b8, a8, rgba_, Lanes(d), n,
|
||||
rgb_ + base_ptr + bytes * x);
|
||||
} else {
|
||||
StoreRGBA(D::Rebind<uint8_t>(), r8, g8, b8, a8, rgba_, n, n,
|
||||
rgb_ + base_ptr + bytes * x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RenderPipelineChannelMode GetChannelMode(size_t c) const final {
|
||||
return c < 3 || (has_alpha_ && c == alpha_c_)
|
||||
? RenderPipelineChannelMode::kInput
|
||||
: RenderPipelineChannelMode::kIgnored;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t* rgb_;
|
||||
size_t stride_;
|
||||
size_t width_;
|
||||
size_t height_;
|
||||
bool rgba_;
|
||||
bool has_alpha_;
|
||||
size_t alpha_c_;
|
||||
std::vector<float> opaque_alpha_;
|
||||
};
|
||||
|
||||
std::unique_ptr<RenderPipelineStage> GetWriteToU8Stage(
|
||||
uint8_t* rgb, size_t stride, size_t width, size_t height, bool rgba,
|
||||
bool has_alpha, size_t alpha_c) {
|
||||
return jxl::make_unique<WriteToU8Stage>(rgb, stride, width, height, rgba,
|
||||
has_alpha, alpha_c);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jxl
|
||||
HWY_AFTER_NAMESPACE();
|
||||
|
||||
#if HWY_ONCE
|
||||
|
||||
namespace jxl {
|
||||
|
||||
HWY_EXPORT(GetWriteToU8Stage);
|
||||
|
||||
namespace {
|
||||
class WriteToImageBundleStage : public RenderPipelineStage {
|
||||
public:
|
||||
explicit WriteToImageBundleStage(ImageBundle* image_bundle)
|
||||
: RenderPipelineStage(RenderPipelineStage::Settings()),
|
||||
image_bundle_(image_bundle) {}
|
||||
|
||||
void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
|
||||
size_t xextra, size_t xsize, size_t xpos, size_t ypos,
|
||||
float* JXL_RESTRICT temp) const final {
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
memcpy(image_bundle_->color()->PlaneRow(c, ypos) + xpos - xextra,
|
||||
GetInputRow(input_rows, c, 0) - xextra,
|
||||
sizeof(float) * (xsize + 2 * xextra));
|
||||
}
|
||||
for (size_t ec = 0; ec < image_bundle_->extra_channels().size(); ec++) {
|
||||
JXL_ASSERT(ec < image_bundle_->extra_channels().size());
|
||||
JXL_ASSERT(image_bundle_->extra_channels()[ec].xsize() <=
|
||||
xpos + xsize + xextra);
|
||||
memcpy(image_bundle_->extra_channels()[ec].Row(ypos) + xpos - xextra,
|
||||
GetInputRow(input_rows, 3 + ec, 0) - xextra,
|
||||
sizeof(float) * (xsize + 2 * xextra));
|
||||
}
|
||||
}
|
||||
|
||||
RenderPipelineChannelMode GetChannelMode(size_t c) const final {
|
||||
return c < 3 + image_bundle_->extra_channels().size()
|
||||
? RenderPipelineChannelMode::kInput
|
||||
: RenderPipelineChannelMode::kIgnored;
|
||||
}
|
||||
|
||||
private:
|
||||
ImageBundle* image_bundle_;
|
||||
};
|
||||
|
||||
class WriteToImage3FStage : public RenderPipelineStage {
|
||||
public:
|
||||
explicit WriteToImage3FStage(Image3F* image)
|
||||
: RenderPipelineStage(RenderPipelineStage::Settings()), image_(image) {}
|
||||
|
||||
void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
|
||||
size_t xextra, size_t xsize, size_t xpos, size_t ypos,
|
||||
float* JXL_RESTRICT temp) const final {
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
memcpy(image_->PlaneRow(c, ypos) + xpos - xextra,
|
||||
GetInputRow(input_rows, c, 0) - xextra,
|
||||
sizeof(float) * (xsize + 2 * xextra));
|
||||
}
|
||||
}
|
||||
|
||||
RenderPipelineChannelMode GetChannelMode(size_t c) const final {
|
||||
return c < 3 ? RenderPipelineChannelMode::kInput
|
||||
: RenderPipelineChannelMode::kIgnored;
|
||||
}
|
||||
|
||||
private:
|
||||
Image3F* image_;
|
||||
};
|
||||
|
||||
class WriteToPixelCallbackStage : public RenderPipelineStage {
|
||||
public:
|
||||
WriteToPixelCallbackStage(
|
||||
const std::function<void(const float*, size_t, size_t, size_t)>&
|
||||
pixel_callback,
|
||||
size_t width, size_t height, bool rgba, bool has_alpha, size_t alpha_c)
|
||||
: RenderPipelineStage(RenderPipelineStage::Settings()),
|
||||
pixel_callback_(pixel_callback),
|
||||
width_(width),
|
||||
height_(height),
|
||||
rgba_(rgba),
|
||||
has_alpha_(has_alpha),
|
||||
alpha_c_(alpha_c),
|
||||
opaque_alpha_(kMaxPixelsPerCall, 1.0f) {
|
||||
settings_.temp_buffer_size = kMaxPixelsPerCall * (rgba_ ? 4 : 3);
|
||||
}
|
||||
|
||||
void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
|
||||
size_t xextra, size_t xsize, size_t xpos, size_t ypos,
|
||||
float* JXL_RESTRICT temp) const final {
|
||||
if (ypos >= height_) return;
|
||||
const float* line_buffers[4];
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
line_buffers[c] = GetInputRow(input_rows, c, 0);
|
||||
}
|
||||
if (has_alpha_) {
|
||||
line_buffers[3] = GetInputRow(input_rows, alpha_c_, 0);
|
||||
} else {
|
||||
line_buffers[3] = opaque_alpha_.data();
|
||||
}
|
||||
// TODO(veluca): SIMD.
|
||||
ssize_t limit = std::min(xextra + xsize, width_ - xpos);
|
||||
for (ssize_t x0 = -xextra; x0 < limit; x0 += kMaxPixelsPerCall) {
|
||||
size_t j = 0;
|
||||
size_t ix = 0;
|
||||
for (; ix < kMaxPixelsPerCall && ssize_t(ix) + x0 < limit; ix++) {
|
||||
temp[j++] = line_buffers[0][x0 + ix];
|
||||
temp[j++] = line_buffers[1][x0 + ix];
|
||||
temp[j++] = line_buffers[2][x0 + ix];
|
||||
if (rgba_) {
|
||||
temp[j++] = line_buffers[3][x0 + ix];
|
||||
}
|
||||
}
|
||||
pixel_callback_(temp, xpos + x0, ypos, ix);
|
||||
}
|
||||
}
|
||||
|
||||
RenderPipelineChannelMode GetChannelMode(size_t c) const final {
|
||||
return c < 3 || (has_alpha_ && c == alpha_c_)
|
||||
? RenderPipelineChannelMode::kInput
|
||||
: RenderPipelineChannelMode::kIgnored;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr size_t kMaxPixelsPerCall = 1024;
|
||||
const std::function<void(const float*, size_t, size_t, size_t)>&
|
||||
pixel_callback_;
|
||||
size_t width_;
|
||||
size_t height_;
|
||||
bool rgba_;
|
||||
bool has_alpha_;
|
||||
size_t alpha_c_;
|
||||
std::vector<float> opaque_alpha_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<RenderPipelineStage> GetWriteToImageBundleStage(
|
||||
ImageBundle* image_bundle) {
|
||||
return jxl::make_unique<WriteToImageBundleStage>(image_bundle);
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderPipelineStage> GetWriteToImage3FStage(Image3F* image) {
|
||||
return jxl::make_unique<WriteToImage3FStage>(image);
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderPipelineStage> GetWriteToU8Stage(
|
||||
uint8_t* rgb, size_t stride, size_t width, size_t height, bool rgba,
|
||||
bool has_alpha, size_t alpha_c) {
|
||||
return HWY_DYNAMIC_DISPATCH(GetWriteToU8Stage)(rgb, stride, width, height,
|
||||
rgba, has_alpha, alpha_c);
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderPipelineStage> GetWriteToPixelCallbackStage(
|
||||
const std::function<void(const float*, size_t, size_t, size_t)>&
|
||||
pixel_callback,
|
||||
size_t width, size_t height, bool rgba, bool has_alpha, size_t alpha_c) {
|
||||
return jxl::make_unique<WriteToPixelCallbackStage>(
|
||||
pixel_callback, width, height, rgba, has_alpha, alpha_c);
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JXL_RENDER_PIPELINE_STAGE_WRITE_H_
|
||||
#define LIB_JXL_RENDER_PIPELINE_STAGE_WRITE_H_
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
#include "lib/jxl/render_pipeline/render_pipeline_stage.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
std::unique_ptr<RenderPipelineStage> GetWriteToImageBundleStage(
|
||||
ImageBundle* image_bundle);
|
||||
|
||||
// Gets a stage to write color channels to an Image3F.
|
||||
std::unique_ptr<RenderPipelineStage> GetWriteToImage3FStage(Image3F* image);
|
||||
|
||||
// Gets a stage to write to a uint8 buffer.
|
||||
std::unique_ptr<RenderPipelineStage> GetWriteToU8Stage(
|
||||
uint8_t* rgb, size_t stride, size_t width, size_t height, bool rgba,
|
||||
bool has_alpha, size_t alpha_c);
|
||||
|
||||
// Gets a stage to write to a pixel callback.
|
||||
std::unique_ptr<RenderPipelineStage> GetWriteToPixelCallbackStage(
|
||||
const std::function<void(const float*, size_t, size_t, size_t)>&
|
||||
pixel_callback,
|
||||
size_t width, size_t height, bool rgba, bool has_alpha, size_t alpha_c);
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_JXL_RENDER_PIPELINE_STAGE_WRITE_H_
|
|
@ -1,82 +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/render_pipeline/stage_write_to_ib.h"
|
||||
|
||||
#include "lib/jxl/common.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
namespace {
|
||||
class WriteToImageBundleStage : public RenderPipelineStage {
|
||||
public:
|
||||
explicit WriteToImageBundleStage(ImageBundle* image_bundle)
|
||||
: RenderPipelineStage(RenderPipelineStage::Settings()),
|
||||
image_bundle_(image_bundle) {}
|
||||
|
||||
void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
|
||||
size_t xextra, size_t xsize, size_t xpos, size_t ypos,
|
||||
float* JXL_RESTRICT temp) const final {
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
memcpy(image_bundle_->color()->PlaneRow(c, ypos) + xpos - xextra,
|
||||
GetInputRow(input_rows, c, 0) - xextra,
|
||||
sizeof(float) * (xsize + 2 * xextra));
|
||||
}
|
||||
for (size_t ec = 0; ec < image_bundle_->extra_channels().size(); ec++) {
|
||||
JXL_ASSERT(ec < image_bundle_->extra_channels().size());
|
||||
JXL_ASSERT(image_bundle_->extra_channels()[ec].xsize() <=
|
||||
xpos + xsize + xextra);
|
||||
memcpy(image_bundle_->extra_channels()[ec].Row(ypos) + xpos - xextra,
|
||||
GetInputRow(input_rows, 3 + ec, 0) - xextra,
|
||||
sizeof(float) * (xsize + 2 * xextra));
|
||||
}
|
||||
}
|
||||
|
||||
RenderPipelineChannelMode GetChannelMode(size_t c) const final {
|
||||
return c < 3 + image_bundle_->extra_channels().size()
|
||||
? RenderPipelineChannelMode::kInput
|
||||
: RenderPipelineChannelMode::kIgnored;
|
||||
}
|
||||
|
||||
private:
|
||||
ImageBundle* image_bundle_;
|
||||
};
|
||||
|
||||
class WriteToImage3FStage : public RenderPipelineStage {
|
||||
public:
|
||||
explicit WriteToImage3FStage(Image3F* image)
|
||||
: RenderPipelineStage(RenderPipelineStage::Settings()), image_(image) {}
|
||||
|
||||
void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
|
||||
size_t xextra, size_t xsize, size_t xpos, size_t ypos,
|
||||
float* JXL_RESTRICT temp) const final {
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
memcpy(image_->PlaneRow(c, ypos) + xpos - xextra,
|
||||
GetInputRow(input_rows, c, 0) - xextra,
|
||||
sizeof(float) * (xsize + 2 * xextra));
|
||||
}
|
||||
}
|
||||
|
||||
RenderPipelineChannelMode GetChannelMode(size_t c) const final {
|
||||
return c < 3 ? RenderPipelineChannelMode::kInput
|
||||
: RenderPipelineChannelMode::kIgnored;
|
||||
}
|
||||
|
||||
private:
|
||||
Image3F* image_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<RenderPipelineStage> GetWriteToImageBundleStage(
|
||||
ImageBundle* image_bundle) {
|
||||
return jxl::make_unique<WriteToImageBundleStage>(image_bundle);
|
||||
}
|
||||
|
||||
std::unique_ptr<RenderPipelineStage> GetWriteToImage3FStage(Image3F* image) {
|
||||
return jxl::make_unique<WriteToImage3FStage>(image);
|
||||
}
|
||||
} // namespace jxl
|
|
@ -1,21 +0,0 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JXL_RENDER_PIPELINE_STAGE_WRITE_TO_IB_H_
|
||||
#define LIB_JXL_RENDER_PIPELINE_STAGE_WRITE_TO_IB_H_
|
||||
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
#include "lib/jxl/render_pipeline/render_pipeline_stage.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
std::unique_ptr<RenderPipelineStage> GetWriteToImageBundleStage(
|
||||
ImageBundle* image_bundle);
|
||||
|
||||
// Gets a stage to write color channels to an Image3F.
|
||||
std::unique_ptr<RenderPipelineStage> GetWriteToImage3FStage(Image3F* image);
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_JXL_RENDER_PIPELINE_STAGE_WRITE_TO_IB_H_
|
|
@ -210,5 +210,60 @@ std::unique_ptr<RenderPipelineStage> GetXYBStage(
|
|||
return HWY_DYNAMIC_DISPATCH(GetXYBStage)(output_encoding_info);
|
||||
}
|
||||
|
||||
namespace {
|
||||
class FastXYBStage : public RenderPipelineStage {
|
||||
public:
|
||||
FastXYBStage(uint8_t* rgb, size_t stride, size_t width, size_t height,
|
||||
bool rgba, bool has_alpha, size_t alpha_c)
|
||||
: RenderPipelineStage(RenderPipelineStage::Settings()),
|
||||
rgb_(rgb),
|
||||
stride_(stride),
|
||||
width_(width),
|
||||
height_(height),
|
||||
rgba_(rgba),
|
||||
has_alpha_(has_alpha),
|
||||
alpha_c_(alpha_c) {}
|
||||
|
||||
void ProcessRow(const RowInfo& input_rows, const RowInfo& output_rows,
|
||||
size_t xextra, size_t xsize, size_t xpos, size_t ypos,
|
||||
float* JXL_RESTRICT temp) const final {
|
||||
if (ypos >= height_) return;
|
||||
JXL_ASSERT(xextra == 0);
|
||||
const float* xyba[4] = {
|
||||
GetInputRow(input_rows, 0, 0), GetInputRow(input_rows, 1, 0),
|
||||
GetInputRow(input_rows, 2, 0),
|
||||
has_alpha_ ? GetInputRow(input_rows, alpha_c_, 0) : nullptr};
|
||||
uint8_t* out_buf = rgb_ + stride_ * ypos + (rgba_ ? 4 : 3) * xpos;
|
||||
FastXYBTosRGB8(xyba, out_buf, rgba_,
|
||||
xsize + xpos <= width_ ? xsize : width_ - xpos);
|
||||
}
|
||||
|
||||
RenderPipelineChannelMode GetChannelMode(size_t c) const final {
|
||||
return c < 3 || (has_alpha_ && c == alpha_c_)
|
||||
? RenderPipelineChannelMode::kInput
|
||||
: RenderPipelineChannelMode::kIgnored;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t* rgb_;
|
||||
size_t stride_;
|
||||
size_t width_;
|
||||
size_t height_;
|
||||
bool rgba_;
|
||||
bool has_alpha_;
|
||||
size_t alpha_c_;
|
||||
std::vector<float> opaque_alpha_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<RenderPipelineStage> GetFastXYBTosRGB8Stage(
|
||||
uint8_t* rgb, size_t stride, size_t width, size_t height, bool rgba,
|
||||
bool has_alpha, size_t alpha_c) {
|
||||
JXL_ASSERT(HasFastXYBTosRGB8());
|
||||
return make_unique<FastXYBStage>(rgb, stride, width, height, rgba, has_alpha,
|
||||
alpha_c);
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
#endif
|
||||
|
|
|
@ -21,6 +21,12 @@ namespace jxl {
|
|||
// Converts the color channels from XYB to the specified output encoding.
|
||||
std::unique_ptr<RenderPipelineStage> GetXYBStage(
|
||||
const OutputEncodingInfo& output_encoding_info);
|
||||
|
||||
// Gets a stage to convert with fixed point arithmetic from XYB to sRGB8 and
|
||||
// write to a uint8 buffer.
|
||||
std::unique_ptr<RenderPipelineStage> GetFastXYBTosRGB8Stage(
|
||||
uint8_t* rgb, size_t stride, size_t width, size_t height, bool rgba,
|
||||
bool has_alpha, size_t alpha_c);
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_JXL_RENDER_PIPELINE_STAGE_XYB_H_
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "lib/jxl/render_pipeline/render_pipeline_stage.h"
|
||||
|
||||
namespace jxl {
|
||||
|
|
|
@ -98,12 +98,13 @@ jxl::CodecInOut ConvertTestImage(const std::vector<uint8_t>& buf,
|
|||
} else {
|
||||
color_encoding = jxl::ColorEncoding::SRGB(is_gray);
|
||||
}
|
||||
EXPECT_TRUE(ConvertFromExternal(
|
||||
jxl::Span<const uint8_t>(buf.data(), buf.size()), xsize, ysize,
|
||||
color_encoding, has_alpha,
|
||||
/*alpha_is_premultiplied=*/false,
|
||||
/*bits_per_sample=*/bitdepth, pixel_format.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr, &io.Main(), float_in));
|
||||
EXPECT_TRUE(
|
||||
ConvertFromExternal(jxl::Span<const uint8_t>(buf.data(), buf.size()),
|
||||
xsize, ysize, color_encoding, has_alpha,
|
||||
/*alpha_is_premultiplied=*/false,
|
||||
/*bits_per_sample=*/bitdepth, pixel_format.endianness,
|
||||
/*flipped_y=*/false, /*pool=*/nullptr, &io.Main(),
|
||||
float_in, /*align=*/0));
|
||||
return io;
|
||||
}
|
||||
|
||||
|
@ -239,7 +240,8 @@ void VerifyRoundtripCompression(
|
|||
extra_channel_bytes.size()),
|
||||
xsize, ysize, basic_info.bits_per_sample,
|
||||
input_pixel_format.endianness, /*pool=*/nullptr, &alpha_channel,
|
||||
/*float_in=*/input_pixel_format.data_type == JXL_TYPE_FLOAT),
|
||||
/*float_in=*/input_pixel_format.data_type == JXL_TYPE_FLOAT,
|
||||
/*align=*/0),
|
||||
true);
|
||||
|
||||
original_io.metadata.m.SetAlphaBits(basic_info.bits_per_sample);
|
||||
|
|
|
@ -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_JXL_SIZE_CONSTRAINTS_H_
|
||||
#define LIB_JXL_SIZE_CONSTRAINTS_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace jxl {
|
||||
|
||||
struct SizeConstraints {
|
||||
// Upper limit on pixel dimensions/area, enforced by VerifyDimensions
|
||||
// (called from decoders). Fuzzers set smaller values to limit memory use.
|
||||
uint32_t dec_max_xsize = 0xFFFFFFFFu;
|
||||
uint32_t dec_max_ysize = 0xFFFFFFFFu;
|
||||
uint64_t dec_max_pixels = 0xFFFFFFFFu; // Might be up to ~0ull
|
||||
};
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_JXL_SIZE_CONSTRAINTS_H_
|
|
@ -51,7 +51,7 @@ float ContinuousIDCT(const float dct[32], const float t) {
|
|||
auto cos_arg = LoadU(df, kMultipliers + i) * tandhalf;
|
||||
auto cos = FastCosf(df, cos_arg);
|
||||
auto local_res = LoadU(df, dct + i) * cos;
|
||||
result = MulAdd(Set(df, square_root<2>::value), local_res, result);
|
||||
result = MulAdd(Set(df, kSqrt2), local_res, result);
|
||||
}
|
||||
return GetLane(SumOfLanes(df, result));
|
||||
}
|
||||
|
@ -215,17 +215,19 @@ namespace {
|
|||
constexpr size_t kMaxNumControlPoints = 1u << 20u;
|
||||
constexpr size_t kMaxNumControlPointsPerPixelRatio = 2;
|
||||
|
||||
// X, Y, B, sigma.
|
||||
float ColorQuantizationWeight(const int32_t adjustment, const int channel,
|
||||
const int i) {
|
||||
const float multiplier = adjustment >= 0 ? 1.f + .125f * adjustment
|
||||
: 1.f / (1.f + .125f * -adjustment);
|
||||
|
||||
static constexpr float kChannelWeight[] = {0.0042f, 0.075f, 0.07f, .3333f};
|
||||
|
||||
return multiplier / kChannelWeight[channel];
|
||||
float AdjustedQuant(const int32_t adjustment) {
|
||||
return (adjustment >= 0) ? (1.f + .125f * adjustment)
|
||||
: 1.f / (1.f - .125f * adjustment);
|
||||
}
|
||||
|
||||
float InvAdjustedQuant(const int32_t adjustment) {
|
||||
return (adjustment >= 0) ? 1.f / (1.f + .125f * adjustment)
|
||||
: (1.f - .125f * adjustment);
|
||||
}
|
||||
|
||||
// X, Y, B, sigma.
|
||||
static constexpr float kChannelWeight[] = {0.0042f, 0.075f, 0.07f, .3333f};
|
||||
|
||||
Status DecodeAllStartingPoints(std::vector<Spline::Point>* const points,
|
||||
BitReader* const br, ANSSymbolReader* reader,
|
||||
const std::vector<uint8_t>& context_map,
|
||||
|
@ -272,12 +274,16 @@ Vector operator-(const Spline::Point& a, const Spline::Point& b) {
|
|||
return {a.x - b.x, a.y - b.y};
|
||||
}
|
||||
|
||||
std::vector<Spline::Point> DrawCentripetalCatmullRomSpline(
|
||||
std::vector<Spline::Point> points) {
|
||||
if (points.size() <= 1) return points;
|
||||
// TODO(eustas): avoid making a copy of "points".
|
||||
void DrawCentripetalCatmullRomSpline(std::vector<Spline::Point> points,
|
||||
std::vector<Spline::Point>& result) {
|
||||
if (points.empty()) return;
|
||||
if (points.size() == 1) {
|
||||
result.push_back(points[0]);
|
||||
return;
|
||||
}
|
||||
// Number of points to compute between each control point.
|
||||
static constexpr int kNumPoints = 16;
|
||||
std::vector<Spline::Point> result;
|
||||
result.reserve((points.size() - 1) * kNumPoints + 1);
|
||||
points.insert(points.begin(), points[0] + (points[0] - points[1]));
|
||||
points.push_back(points[points.size() - 1] +
|
||||
|
@ -287,33 +293,38 @@ std::vector<Spline::Point> DrawCentripetalCatmullRomSpline(
|
|||
// 4 of them are used, and we draw from p[1] to p[2].
|
||||
const Spline::Point* const p = &points[start];
|
||||
result.push_back(p[1]);
|
||||
float t[4] = {0};
|
||||
for (int k = 1; k < 4; ++k) {
|
||||
t[k] = std::sqrt(hypotf(p[k].x - p[k - 1].x, p[k].y - p[k - 1].y)) +
|
||||
t[k - 1];
|
||||
float d[3];
|
||||
float t[4];
|
||||
t[0] = 0;
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
// TODO(eustas): for each segment delta is calculated 3 times...
|
||||
// TODO(eustas): restrict d[k] with reasonable limit and spec it.
|
||||
d[k] = std::sqrt(hypotf(p[k + 1].x - p[k].x, p[k + 1].y - p[k].y));
|
||||
t[k + 1] = t[k] + d[k];
|
||||
}
|
||||
for (int i = 1; i < kNumPoints; ++i) {
|
||||
const float tt =
|
||||
t[1] + (static_cast<float>(i) / kNumPoints) * (t[2] - t[1]);
|
||||
const float tt = d[0] + (static_cast<float>(i) / kNumPoints) * d[1];
|
||||
Spline::Point a[3];
|
||||
for (int k = 0; k < 3; ++k) {
|
||||
a[k] = p[k] + ((tt - t[k]) / (t[k + 1] - t[k])) * (p[k + 1] - p[k]);
|
||||
// TODO(eustas): reciprocal multiplication would be faster.
|
||||
a[k] = p[k] + ((tt - t[k]) / d[k]) * (p[k + 1] - p[k]);
|
||||
}
|
||||
Spline::Point b[2];
|
||||
for (int k = 0; k < 2; ++k) {
|
||||
b[k] = a[k] + ((tt - t[k]) / (t[k + 2] - t[k])) * (a[k + 1] - a[k]);
|
||||
b[k] = a[k] + ((tt - t[k]) / (d[k] + d[k + 1])) * (a[k + 1] - a[k]);
|
||||
}
|
||||
result.push_back(b[0] + ((tt - t[1]) / (t[2] - t[1])) * (b[1] - b[0]));
|
||||
result.push_back(b[0] + ((tt - t[1]) / d[1]) * (b[1] - b[0]));
|
||||
}
|
||||
}
|
||||
result.push_back(points[points.size() - 2]);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Move along the line segments defined by `points`, `kDesiredRenderingDistance`
|
||||
// pixels at a time, and call `functor` with each point and the actual distance
|
||||
// to the previous point (which will always be kDesiredRenderingDistance except
|
||||
// possibly for the very last point).
|
||||
// TODO(eustas): this method always adds the last point, but never the first
|
||||
// (unless those are one); I believe both ends matter.
|
||||
template <typename Points, typename Functor>
|
||||
bool ForEachEquallySpacedPoint(const Points& points, const Functor& functor) {
|
||||
JXL_ASSERT(!points.empty());
|
||||
|
@ -352,7 +363,7 @@ bool ForEachEquallySpacedPoint(const Points& points, const Functor& functor) {
|
|||
|
||||
QuantizedSpline::QuantizedSpline(const Spline& original,
|
||||
const int32_t quantization_adjustment,
|
||||
const float ytox, const float ytob) {
|
||||
const float y_to_x, const float y_to_b) {
|
||||
JXL_ASSERT(!original.control_points.empty());
|
||||
control_points_.reserve(original.control_points.size() - 1);
|
||||
const Spline::Point& starting_point = original.control_points.front();
|
||||
|
@ -373,28 +384,34 @@ QuantizedSpline::QuantizedSpline(const Spline& original,
|
|||
previous_y = new_y;
|
||||
}
|
||||
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
float factor = c == 0 ? ytox : c == 1 ? 0 : ytob;
|
||||
const auto to_int = [](float v) -> int {
|
||||
return static_cast<int>(roundf(v));
|
||||
};
|
||||
|
||||
const auto quant = AdjustedQuant(quantization_adjustment);
|
||||
const auto inv_quant = InvAdjustedQuant(quantization_adjustment);
|
||||
for (int c : {1, 0, 2}) {
|
||||
float factor = (c == 0) ? y_to_x : (c == 1) ? 0 : y_to_b;
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
const float coefficient =
|
||||
original.color_dct[c][i] -
|
||||
factor * color_dct_[1][i] /
|
||||
ColorQuantizationWeight(quantization_adjustment, 1, i);
|
||||
color_dct_[c][i] = static_cast<int>(
|
||||
roundf(coefficient *
|
||||
ColorQuantizationWeight(quantization_adjustment, c, i)));
|
||||
const float dct_factor = (i == 0) ? kSqrt2 : 1.0f;
|
||||
const float inv_dct_factor = (i == 0) ? kSqrt0_5 : 1.0f;
|
||||
auto restored_y =
|
||||
color_dct_[1][i] * inv_dct_factor * kChannelWeight[1] * inv_quant;
|
||||
auto decorellated = original.color_dct[c][i] - factor * restored_y;
|
||||
color_dct_[c][i] =
|
||||
to_int(decorellated * dct_factor * quant / kChannelWeight[c]);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
sigma_dct_[i] = static_cast<int>(
|
||||
roundf(original.sigma_dct[i] *
|
||||
ColorQuantizationWeight(quantization_adjustment, 3, i)));
|
||||
const float dct_factor = (i == 0) ? kSqrt2 : 1.0f;
|
||||
sigma_dct_[i] =
|
||||
to_int(original.sigma_dct[i] * dct_factor * quant / kChannelWeight[3]);
|
||||
}
|
||||
}
|
||||
|
||||
Status QuantizedSpline::Dequantize(const Spline::Point& starting_point,
|
||||
const int32_t quantization_adjustment,
|
||||
const float ytox, const float ytob,
|
||||
const float y_to_x, const float y_to_b,
|
||||
Spline& result) const {
|
||||
result.control_points.clear();
|
||||
result.control_points.reserve(control_points_.size() + 1);
|
||||
|
@ -426,21 +443,22 @@ Status QuantizedSpline::Dequantize(const Spline::Point& starting_point,
|
|||
static_cast<float>(current_x), static_cast<float>(current_y)});
|
||||
}
|
||||
|
||||
const auto inv_quant = InvAdjustedQuant(quantization_adjustment);
|
||||
for (int c = 0; c < 3; ++c) {
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
const float inv_dct_factor = (i == 0) ? kSqrt0_5 : 1.0f;
|
||||
result.color_dct[c][i] =
|
||||
color_dct_[c][i] * (i == 0 ? 1.0f / square_root<2>::value : 1.0f) /
|
||||
ColorQuantizationWeight(quantization_adjustment, c, i);
|
||||
color_dct_[c][i] * inv_dct_factor * kChannelWeight[c] * inv_quant;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
result.color_dct[0][i] += ytox * result.color_dct[1][i];
|
||||
result.color_dct[2][i] += ytob * result.color_dct[1][i];
|
||||
result.color_dct[0][i] += y_to_x * result.color_dct[1][i];
|
||||
result.color_dct[2][i] += y_to_b * result.color_dct[1][i];
|
||||
}
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
const float inv_dct_factor = (i == 0) ? kSqrt0_5 : 1.0f;
|
||||
result.sigma_dct[i] =
|
||||
sigma_dct_[i] * (i == 0 ? 1.0f / square_root<2>::value : 1.0f) /
|
||||
ColorQuantizationWeight(quantization_adjustment, 3, i);
|
||||
sigma_dct_[i] * inv_dct_factor * kChannelWeight[3] * inv_quant;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -567,6 +585,7 @@ Status Splines::InitializeDrawCache(const size_t image_xsize,
|
|||
size_t px_limit = (pixel_limit < static_cast<float>(kHardPixelLimit))
|
||||
? static_cast<size_t>(pixel_limit)
|
||||
: kHardPixelLimit;
|
||||
std::vector<Spline::Point> intermediate_points;
|
||||
for (size_t i = 0; i < splines_.size(); ++i) {
|
||||
JXL_RETURN_IF_ERROR(
|
||||
splines_[i].Dequantize(starting_points_[i], quantization_adjustment_,
|
||||
|
@ -574,6 +593,8 @@ Status Splines::InitializeDrawCache(const size_t image_xsize,
|
|||
if (std::adjacent_find(spline.control_points.begin(),
|
||||
spline.control_points.end()) !=
|
||||
spline.control_points.end()) {
|
||||
// Otherwise division by zero might occur. Once control points coincide,
|
||||
// the direction of curve is undefined...
|
||||
return JXL_FAILURE(
|
||||
"identical successive control points in spline %" PRIuS, i);
|
||||
}
|
||||
|
@ -583,9 +604,9 @@ Status Splines::InitializeDrawCache(const size_t image_xsize,
|
|||
points_to_draw.emplace_back(point, multiplier);
|
||||
return (points_to_draw.size() <= px_limit);
|
||||
};
|
||||
if (!ForEachEquallySpacedPoint(
|
||||
DrawCentripetalCatmullRomSpline(spline.control_points),
|
||||
add_point)) {
|
||||
intermediate_points.clear();
|
||||
DrawCentripetalCatmullRomSpline(spline.control_points, intermediate_points);
|
||||
if (!ForEachEquallySpacedPoint(intermediate_points, add_point)) {
|
||||
return JXL_FAILURE("Too many pixels covered with splines");
|
||||
}
|
||||
const float arc_length =
|
||||
|
|
|
@ -277,17 +277,23 @@ TEST(SplinesTest, DuplicatePoints) {
|
|||
|
||||
TEST(SplinesTest, Drawing) {
|
||||
CodecInOut io_expected;
|
||||
const PaddedBytes orig = ReadTestData("jxl/splines.png");
|
||||
const PaddedBytes orig = ReadTestData("jxl/splines.pfm");
|
||||
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io_expected,
|
||||
/*pool=*/nullptr));
|
||||
|
||||
std::vector<Spline::Point> control_points{{9, 54}, {118, 159}, {97, 3},
|
||||
{10, 40}, {150, 25}, {120, 300}};
|
||||
// Use values that survive quant/decorellation roundtrip.
|
||||
const Spline spline{
|
||||
control_points,
|
||||
/*color_dct=*/
|
||||
{{0.03125f, 0.00625f, 0.003125f}, {1.f, 0.321875f}, {1.f, 0.24375f}},
|
||||
/*sigma_dct=*/{0.3125f, 0.f, 0.f, 0.0625f}};
|
||||
{{0.4989345073699951171875000f, 0.4997999966144561767578125f},
|
||||
{0.4772970676422119140625000f, 0.f, 0.5250000357627868652343750f},
|
||||
{-0.0176776945590972900390625f, 0.4900000095367431640625000f,
|
||||
0.5250000357627868652343750f}},
|
||||
/*sigma_dct=*/
|
||||
{0.9427147507667541503906250f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f,
|
||||
0.6665999889373779296875000f}};
|
||||
std::vector<Spline> spline_data = {spline};
|
||||
std::vector<QuantizedSpline> quantized_splines;
|
||||
std::vector<Spline::Point> starting_points;
|
||||
|
@ -304,12 +310,8 @@ TEST(SplinesTest, Drawing) {
|
|||
ASSERT_TRUE(splines.InitializeDrawCache(image.xsize(), image.ysize(), *cmap));
|
||||
splines.AddTo(&image, Rect(image), Rect(image));
|
||||
|
||||
OpsinParams opsin_params{};
|
||||
opsin_params.Init(kDefaultIntensityTarget);
|
||||
(void)OpsinToLinearInplace(&image, /*pool=*/nullptr, opsin_params);
|
||||
|
||||
CodecInOut io_actual;
|
||||
io_actual.SetFromImage(CopyImage(image), ColorEncoding::LinearSRGB());
|
||||
io_actual.SetFromImage(CopyImage(image), ColorEncoding::SRGB());
|
||||
ASSERT_TRUE(
|
||||
io_actual.TransformTo(io_expected.Main().c_current(), GetJxlCms()));
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче