Bug 1767432 - Update libjxl to 192ddd r=tnikkel

Differential Revision: https://phabricator.services.mozilla.com/D145332
This commit is contained in:
Kagami Sascha Rosylight 2022-05-04 00:22:14 +00:00
parent 733cea14b9
commit 0d3b3ecabe
134 changed files with 2394 additions and 3059 deletions

View File

@ -8,6 +8,9 @@
#define JXL_EXPORT_H #define JXL_EXPORT_H
#define JXL_EXPORT #define JXL_EXPORT
#define JXL_DEPRECATED [[deprecated]]
// TODO: go back to [[deprecated]]
// https://github.com/libjxl/libjxl/issues/1388
#define JXL_DEPRECATED __attribute__((__deprecated__))
#endif /* JXL_EXPORT_H */ #endif /* JXL_EXPORT_H */

View File

@ -19,7 +19,6 @@ SOURCES += [
"/third_party/jpeg-xl/lib/jxl/base/data_parallel.cc", "/third_party/jpeg-xl/lib/jxl/base/data_parallel.cc",
"/third_party/jpeg-xl/lib/jxl/base/padded_bytes.cc", "/third_party/jpeg-xl/lib/jxl/base/padded_bytes.cc",
"/third_party/jpeg-xl/lib/jxl/base/random.cc", "/third_party/jpeg-xl/lib/jxl/base/random.cc",
"/third_party/jpeg-xl/lib/jxl/base/status.cc",
"/third_party/jpeg-xl/lib/jxl/blending.cc", "/third_party/jpeg-xl/lib/jxl/blending.cc",
"/third_party/jpeg-xl/lib/jxl/box_content_decoder.cc", "/third_party/jpeg-xl/lib/jxl/box_content_decoder.cc",
"/third_party/jpeg-xl/lib/jxl/chroma_from_luma.cc", "/third_party/jpeg-xl/lib/jxl/chroma_from_luma.cc",
@ -46,6 +45,7 @@ SOURCES += [
"/third_party/jpeg-xl/lib/jxl/enc_bit_writer.cc", "/third_party/jpeg-xl/lib/jxl/enc_bit_writer.cc",
"/third_party/jpeg-xl/lib/jxl/entropy_coder.cc", "/third_party/jpeg-xl/lib/jxl/entropy_coder.cc",
"/third_party/jpeg-xl/lib/jxl/epf.cc", "/third_party/jpeg-xl/lib/jxl/epf.cc",
"/third_party/jpeg-xl/lib/jxl/exif.cc",
"/third_party/jpeg-xl/lib/jxl/fast_dct.cc", "/third_party/jpeg-xl/lib/jxl/fast_dct.cc",
"/third_party/jpeg-xl/lib/jxl/fields.cc", "/third_party/jpeg-xl/lib/jxl/fields.cc",
"/third_party/jpeg-xl/lib/jxl/frame_header.cc", "/third_party/jpeg-xl/lib/jxl/frame_header.cc",

View File

@ -46,7 +46,7 @@ jobs:
# Build scalar-only hwy instructions. # Build scalar-only hwy instructions.
- name: scalar - name: scalar
mode: release mode: release
cxxflags: -DHWY_DISABLED_TARGETS=~HWY_SCALAR cxxflags: -DHWY_COMPILE_ONLY_SCALAR
# Disabling optional features to speed up msan build a little bit. # Disabling optional features to speed up msan build a little bit.
- name: msan - name: msan
skip_install: true skip_install: true
@ -59,8 +59,6 @@ jobs:
apt_pkgs: gcovr apt_pkgs: gcovr
# Coverage builds require a bit more RAM. # Coverage builds require a bit more RAM.
env_test_stack_size: 2048 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 # Build with support for decoding to JPEG bytes disabled. Produces a
# smaller build if only decoding to pixels is needed. # smaller build if only decoding to pixels is needed.
- name: release-nojpeg - name: release-nojpeg
@ -149,6 +147,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-${{ steps.git-env.outputs.parent }}-${{ matrix.name }} ${{ runner.os }}-${{ steps.git-env.outputs.parent }}-${{ matrix.name }}
- name: Build - name: Build
if: matrix.name != 'coverage' || env.WILL_RUN_TESTS == 'true'
run: | run: |
mkdir -p ${CCACHE_DIR} mkdir -p ${CCACHE_DIR}
echo "max_size = 200M" > ${CCACHE_DIR}/ccache.conf echo "max_size = 200M" > ${CCACHE_DIR}/ccache.conf
@ -216,10 +215,10 @@ jobs:
files: build/coverage.xml files: build/coverage.xml
- name: Fast benchmark ${{ matrix.mode }} - name: Fast benchmark ${{ matrix.mode }}
if: | if: |
github.event_name == 'push' || matrix.name != 'coverage' && (github.event_name == 'push' ||
(github.event_name == 'pull_request' && ( (github.event_name == 'pull_request' && (
matrix.test_in_pr || matrix.test_in_pr ||
contains(github.event.pull_request.labels.*.names, 'CI:full'))) contains(github.event.pull_request.labels.*.names, 'CI:full'))))
run: | run: |
STORE_IMAGES=0 ./ci.sh fast_benchmark STORE_IMAGES=0 ./ci.sh fast_benchmark
# Run gbench once, just to make sure it runs, not for actual benchmarking. # Run gbench once, just to make sure it runs, not for actual benchmarking.
@ -310,7 +309,6 @@ jobs:
# Build dependencies # Build dependencies
cmake cmake
doxygen doxygen
libgtest-dev:${{ matrix.arch }}
ninja-build ninja-build
pkg-config pkg-config
qemu-user-static qemu-user-static

View File

@ -98,8 +98,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
# 'main_level10' currently fails name: [main_level5, main_level10]
name: [main_level5]
steps: steps:
- name: Install deps - name: Install deps
run: | run: |

View File

@ -146,6 +146,11 @@ jobs:
cd "git-${git_version}" cd "git-${git_version}"
make prefix=/usr -j4 install make prefix=/usr -j4 install
- name: Set git safe dir
run: |
export GIT_CEILING_DIRECTORIES=/__w # only work before git v2.35.2
git config --global --add safe.directory /__w/libjxl/libjxl
- name: Checkout the source - name: Checkout the source
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:

View File

@ -25,16 +25,20 @@ Daniel Novomeský <dnovomesky@gmail.com>
David Burnett <vargolsoft@gmail.com> David Burnett <vargolsoft@gmail.com>
Dirk Lemstra <dirk@lemstra.org> Dirk Lemstra <dirk@lemstra.org>
Don Olmstead <don.j.olmstead@gmail.com> Don Olmstead <don.j.olmstead@gmail.com>
Heiko Becker <heirecka@exherbo.org>
Jon Sneyers <jon@cloudinary.com> Jon Sneyers <jon@cloudinary.com>
Kleis Auke Wolthuizen <github@kleisauke.nl> Kleis Auke Wolthuizen <github@kleisauke.nl>
L. E. Segovia
Leo Izen <leo.izen@gmail.com> Leo Izen <leo.izen@gmail.com>
Lovell Fuller Lovell Fuller
Marcin Konicki <ahwayakchih@gmail.com> Marcin Konicki <ahwayakchih@gmail.com>
Martin Strunz
Mathieu Malaterre <mathieu.malaterre@gmail.com> Mathieu Malaterre <mathieu.malaterre@gmail.com>
Misaki Kasumi <misakikasumi@outlook.com> Misaki Kasumi <misakikasumi@outlook.com>
Petr Diblík Petr Diblík
Pieter Wuille Pieter Wuille
Samuel Leong <wvvwvvvvwvvw@gmail.com> Samuel Leong <wvvwvvvvwvvw@gmail.com>
Stephan T. Lavavej <stl@nuwen.net>
Vincent Torri <vincent.torri@gmail.com> Vincent Torri <vincent.torri@gmail.com>
xiota xiota
Yonatan Nebenzhal <yonatan.nebenzhl@gmail.com> Yonatan Nebenzhal <yonatan.nebenzhl@gmail.com>

View File

@ -23,6 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
the non-coalesced case. the non-coalesced case.
- decoder API: new function `JxlDecoderGetExtraChannelBlendInfo` to get - decoder API: new function `JxlDecoderGetExtraChannelBlendInfo` to get
the blending information for extra channels in the non-coalesced case. the blending information for extra channels in the non-coalesced case.
- decoder API: new function `JxlDecoderSetMultithreadedImageOutCallback`,
allowing output callbacks to receive more information about the number of
threads on which they are running.
- encoder API: added ability to set several encoder options to frames using - encoder API: added ability to set several encoder options to frames using
`JxlEncoderFrameSettingsSetOption` `JxlEncoderFrameSettingsSetOption`
- encoder API: new functions `JxlEncoderSetFrameHeader` and - encoder API: new functions `JxlEncoderSetFrameHeader` and

View File

@ -191,6 +191,15 @@ endif() # JPEGXL_STATIC
set(THREADS_PREFER_PTHREAD_FLAG YES) set(THREADS_PREFER_PTHREAD_FLAG YES)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
# These settings are important to drive check_cxx_source_compiles
# See CMP0067 (min cmake version is 3.10 anyway)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
# Atomics
find_package(Atomics REQUIRED)
if(JPEGXL_STATIC) if(JPEGXL_STATIC)
if (MINGW) if (MINGW)
# In MINGW libstdc++ uses pthreads directly. When building statically a # In MINGW libstdc++ uses pthreads directly. When building statically a
@ -298,10 +307,6 @@ endif () # !MSVC
include(GNUInstallDirs) include(GNUInstallDirs)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
add_subdirectory(third_party) add_subdirectory(third_party)
# Copy the JXL license file to the output build directory. # Copy the JXL license file to the output build directory.
@ -311,6 +316,10 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/LICENSE"
# Enable tests regardless of where they are defined. # Enable tests regardless of where they are defined.
enable_testing() enable_testing()
include(CTest) include(CTest)
# Specify default location of `testdata`:
if(NOT DEFINED JPEGXL_TEST_DATA_PATH)
set(JPEGXL_TEST_DATA_PATH "${PROJECT_SOURCE_DIR}/third_party/testdata")
endif()
# Libraries. # Libraries.
add_subdirectory(lib) add_subdirectory(lib)
@ -419,7 +428,7 @@ if (ASCIIDOC_PY_FOUND)
list(APPEND MANPAGES "${PAGE}.1") list(APPEND MANPAGES "${PAGE}.1")
endforeach() endforeach()
add_custom_target(manpages ALL DEPENDS ${MANPAGES}) add_custom_target(manpages ALL DEPENDS ${MANPAGES})
install(FILES ${MANPAGE_FILES} DESTINATION share/man/man1) install(FILES ${MANPAGE_FILES} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
endif() # ASCIIDOC_PY_FOUND endif() # ASCIIDOC_PY_FOUND
else() else()
message(WARNING "asciidoc was not found, the man pages will not be installed.") message(WARNING "asciidoc was not found, the man pages will not be installed.")

View File

@ -905,7 +905,7 @@ run_benchmark() {
local benchmark_args=( local benchmark_args=(
--input "${src_img_dir}/*.png" --input "${src_img_dir}/*.png"
--codec=jpeg:yuv420:q85,webp:q80,jxl:fast:d1,jxl:fast:d1:downsampling=8,jxl:fast:d4,jxl:fast:d4:downsampling=8,jxl:cheetah:m,jxl:m:cheetah:P6,jxl:m:falcon:q80 --codec=jpeg:yuv420:q85,webp:q80,jxl:d1:6,jxl:d1:6:downsampling=8,jxl:d5:6,jxl:d5:6:downsampling=8,jxl:m:d0:2,jxl:m:d0:3,jxl:m:d2:2
--output_dir "${output_dir}" --output_dir "${output_dir}"
--noprofiler --show_progress --noprofiler --show_progress
--num_threads="${num_threads}" --num_threads="${num_threads}"
@ -1020,11 +1020,11 @@ cmd_arm_benchmark() {
) )
local images=( local images=(
"third_party/testdata/imagecompression.info/flower_foveon.png" "third_party/testdata/third_party/imagecompression.info/flower_foveon.png"
) )
local jpg_images=( local jpg_images=(
"third_party/testdata/imagecompression.info/flower_foveon.png.im_q85_420.jpg" "third_party/testdata/third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg"
) )
if [[ "${SKIP_CPUSET:-}" == "1" ]]; then if [[ "${SKIP_CPUSET:-}" == "1" ]]; then

View File

@ -0,0 +1,53 @@
# Original issue:
# * https://gitlab.kitware.com/cmake/cmake/-/issues/23021#note_1098733
#
# For reference:
# * https://gcc.gnu.org/wiki/Atomic/GCCMM
#
# riscv64 specific:
# * https://lists.debian.org/debian-riscv/2022/01/msg00009.html
#
# ATOMICS_FOUND - system has c++ atomics
# ATOMICS_LIBRARIES - libraries needed to use c++ atomics
include(CheckCXXSourceCompiles)
# RISC-V only has 32-bit and 64-bit atomic instructions. GCC is supposed
# to convert smaller atomics to those larger ones via masking and
# shifting like LLVM, but its a known bug that it does not. This means
# anything that wants to use atomics on 1-byte or 2-byte types needs
# -latomic, but not 4-byte or 8-byte (though it does no harm).
set(atomic_code
"
#include <atomic>
#include <cstdint>
std::atomic<uint8_t> n8 (0); // riscv64
std::atomic<uint64_t> n64 (0); // armel, mipsel, powerpc
int main() {
++n8;
++n64;
return 0;
}")
check_cxx_source_compiles("${atomic_code}" ATOMICS_LOCK_FREE_INSTRUCTIONS)
if(ATOMICS_LOCK_FREE_INSTRUCTIONS)
set(ATOMICS_FOUND TRUE)
set(ATOMICS_LIBRARIES)
else()
set(CMAKE_REQUIRED_LIBRARIES "-latomic")
check_cxx_source_compiles("${atomic_code}" ATOMICS_IN_LIBRARY)
set(CMAKE_REQUIRED_LIBRARIES)
if(ATOMICS_IN_LIBRARY)
set(ATOMICS_LIBRARY atomic)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Atomics DEFAULT_MSG ATOMICS_LIBRARY)
set(ATOMICS_LIBRARIES ${ATOMICS_LIBRARY})
unset(ATOMICS_LIBRARY)
else()
if(Atomics_FIND_REQUIRED)
message(FATAL_ERROR "Neither lock free instructions nor -latomic found.")
endif()
endif()
endif()
unset(atomic_code)

View File

@ -38,7 +38,7 @@ License: BSD-3-clause
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Files: third_party/testdata/imagecompression.info/* Files: third_party/testdata/third_party/imagecompression.info/*
Copyright: their respective owners. Copyright: their respective owners.
License: License without any prohibitive copyright restrictions. License: License without any prohibitive copyright restrictions.
See https://imagecompression.info/test_images/ for details. See https://imagecompression.info/test_images/ for details.
@ -58,7 +58,7 @@ License: License without any prohibitive copyright restrictions.
sell the material. sell the material.
4. This notice may not be removed or altered from any distribution. 4. This notice may not be removed or altered from any distribution.
Files: third_party/testdata/pngsuite/* Files: third_party/testdata/third_party/pngsuite/*
Copyright: Willem van Schaik, 1996, 2011 Copyright: Willem van Schaik, 1996, 2011
License: PngSuite License License: PngSuite License
See http://www.schaik.com/pngsuite/ for details. See http://www.schaik.com/pngsuite/ for details.
@ -74,7 +74,7 @@ Files: third_party/testdata/raw.pixls/*
Copyright: their respective owners listed in https://www.wesaturate.com/ Copyright: their respective owners listed in https://www.wesaturate.com/
License: CC0-1.0 License: CC0-1.0
Files: third_party/testdata/wide-gamut-tests/ Files: third_party/testdata/third_party/wide-gamut-tests/
Copyright: github.com/codelogic/wide-gamut-tests authors. Copyright: github.com/codelogic/wide-gamut-tests authors.
License: Apache-2.0 License: Apache-2.0

View File

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

View File

@ -25,9 +25,6 @@ target_link_libraries(decode_progressive PkgConfig::Jxl PkgConfig::JxlThreads)
add_executable(encode_oneshot encode_oneshot.cc) add_executable(encode_oneshot encode_oneshot.cc)
target_link_libraries(encode_oneshot PkgConfig::Jxl PkgConfig::JxlThreads) target_link_libraries(encode_oneshot PkgConfig::Jxl PkgConfig::JxlThreads)
add_executable(jxlinfo jxlinfo.c)
target_link_libraries(jxlinfo PkgConfig::Jxl)
# Building a static binary with the static libjxl dependencies. How to load # Building a static binary with the static libjxl dependencies. How to load
# static library configs from pkg-config and how to build static binaries # static library configs from pkg-config and how to build static binaries

View File

@ -9,13 +9,3 @@ add_executable(decode_progressive ${CMAKE_CURRENT_LIST_DIR}/decode_progressive.c
target_link_libraries(decode_progressive jxl_dec jxl_threads) target_link_libraries(decode_progressive jxl_dec jxl_threads)
add_executable(encode_oneshot ${CMAKE_CURRENT_LIST_DIR}/encode_oneshot.cc) add_executable(encode_oneshot ${CMAKE_CURRENT_LIST_DIR}/encode_oneshot.cc)
target_link_libraries(encode_oneshot jxl jxl_threads) target_link_libraries(encode_oneshot jxl jxl_threads)
add_executable(jxlinfo ${CMAKE_CURRENT_LIST_DIR}/jxlinfo.c)
target_link_libraries(jxlinfo jxl)
if(NOT ${SANITIZER} STREQUAL "none")
# Linking a C test binary with the C++ JPEG XL implementation when using
# address sanitizer is not well supported by clang 9, so force using clang++
# for linking this test if a sanitizer is used.
set_target_properties(jxlinfo PROPERTIES LINKER_LANGUAGE CXX)
endif() # SANITIZER != "none"

View File

@ -1,332 +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.
// This example prints information from the main codestream header.
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "jxl/decode.h"
int PrintBasicInfo(FILE* file) {
uint8_t* data = NULL;
size_t data_size = 0;
// In how large chunks to read from the file and try decoding the basic info.
const size_t chunk_size = 64;
JxlDecoder* dec = JxlDecoderCreate(NULL);
if (!dec) {
fprintf(stderr, "JxlDecoderCreate failed\n");
return 0;
}
JxlDecoderSetKeepOrientation(dec, 1);
if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(
dec, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING |
JXL_DEC_FRAME | JXL_DEC_BOX)) {
fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
JxlDecoderDestroy(dec);
return 0;
}
JxlBasicInfo info;
int seen_basic_info = 0;
JxlFrameHeader frame_header;
for (;;) {
// The first time, this will output JXL_DEC_NEED_MORE_INPUT because no
// input is set yet, this is ok since the input is set when handling this
// event.
JxlDecoderStatus status = JxlDecoderProcessInput(dec);
if (status == JXL_DEC_ERROR) {
fprintf(stderr, "Decoder error\n");
break;
} else if (status == JXL_DEC_NEED_MORE_INPUT) {
// The first time there is nothing to release and it returns 0, but that
// is ok.
size_t remaining = JxlDecoderReleaseInput(dec);
// move any remaining bytes to the front if necessary
if (remaining != 0) {
memmove(data, data + data_size - remaining, remaining);
}
// resize the buffer to append one more chunk of data
// TODO(lode): avoid unnecessary reallocations
data = (uint8_t*)realloc(data, remaining + chunk_size);
// append bytes read from the file behind the remaining bytes
size_t read_size = fread(data + remaining, 1, chunk_size, file);
if (read_size == 0 && feof(file)) {
fprintf(stderr, "Unexpected EOF\n");
break;
}
data_size = remaining + read_size;
JxlDecoderSetInput(dec, data, data_size);
if (feof(file)) JxlDecoderCloseInput(dec);
} else if (status == JXL_DEC_SUCCESS) {
// Finished all processing.
break;
} else if (status == JXL_DEC_BASIC_INFO) {
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec, &info)) {
fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
break;
}
seen_basic_info = 1;
printf("dimensions: %ux%u\n", info.xsize, info.ysize);
printf("have_container: %d\n", info.have_container);
printf("uses_original_profile: %d\n", info.uses_original_profile);
printf("bits_per_sample: %d\n", info.bits_per_sample);
if (info.exponent_bits_per_sample)
printf("float, with exponent_bits_per_sample: %d\n",
info.exponent_bits_per_sample);
if (info.intensity_target != 255.f || info.min_nits != 0.f ||
info.relative_to_max_display != 0 ||
info.relative_to_max_display != 0.f) {
printf("intensity_target: %f\n", info.intensity_target);
printf("min_nits: %f\n", info.min_nits);
printf("relative_to_max_display: %d\n", info.relative_to_max_display);
printf("linear_below: %f\n", info.linear_below);
}
printf("have_preview: %d\n", info.have_preview);
if (info.have_preview) {
printf("preview xsize: %u\n", info.preview.xsize);
printf("preview ysize: %u\n", info.preview.ysize);
}
printf("have_animation: %d\n", info.have_animation);
if (info.have_animation) {
printf("ticks per second (numerator / denominator): %u / %u\n",
info.animation.tps_numerator, info.animation.tps_denominator);
printf("num_loops: %u\n", info.animation.num_loops);
printf("have_timecodes: %d\n", info.animation.have_timecodes);
}
printf("intrinsic xsize: %u\n", info.intrinsic_xsize);
printf("intrinsic ysize: %u\n", info.intrinsic_ysize);
const char* const orientation_string[8] = {
"Normal", "Flipped horizontally",
"Upside down", "Flipped vertically",
"Transposed", "90 degrees clockwise",
"Anti-Transposed", "90 degrees counter-clockwise"};
if (info.orientation > 0 && info.orientation < 9) {
printf("orientation: %d (%s)\n", info.orientation,
orientation_string[info.orientation - 1]);
} else {
fprintf(stderr, "Invalid orientation\n");
}
printf("num_color_channels: %d\n", info.num_color_channels);
printf("num_extra_channels: %d\n", info.num_extra_channels);
const char* const ec_type_names[7] = {"Alpha", "Depth",
"Spot color", "Selection mask",
"K (of CMYK)", "CFA (Bayer data)",
"Thermal"};
for (uint32_t i = 0; i < info.num_extra_channels; i++) {
JxlExtraChannelInfo extra;
if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec, i, &extra)) {
fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n");
break;
}
printf("extra channel %u:\n", i);
printf(" type: %s\n",
(extra.type < 7 ? ec_type_names[extra.type]
: (extra.type == JXL_CHANNEL_OPTIONAL
? "Unknown but can be ignored"
: "Unknown, please update your libjxl")));
printf(" bits_per_sample: %u\n", extra.bits_per_sample);
if (extra.exponent_bits_per_sample > 0) {
printf(" float, with exponent_bits_per_sample: %u\n",
extra.exponent_bits_per_sample);
}
if (extra.dim_shift > 0) {
printf(" dim_shift: %u (upsampled %ux)\n", extra.dim_shift,
1 << extra.dim_shift);
}
if (extra.name_length) {
char* name = malloc(extra.name_length + 1);
if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelName(
dec, i, name, extra.name_length + 1)) {
fprintf(stderr, "JxlDecoderGetExtraChannelName failed\n");
free(name);
break;
}
printf(" name: %s\n", name);
free(name);
}
if (extra.type == JXL_CHANNEL_ALPHA)
printf(" alpha_premultiplied: %d (%s)\n", extra.alpha_premultiplied,
extra.alpha_premultiplied ? "Premultiplied"
: "Non-premultiplied");
if (extra.type == JXL_CHANNEL_SPOT_COLOR) {
printf(" spot_color: (%f, %f, %f) with opacity %f\n",
extra.spot_color[0], extra.spot_color[1], extra.spot_color[2],
extra.spot_color[3]);
}
if (extra.type == JXL_CHANNEL_CFA)
printf(" cfa_channel: %u\n", extra.cfa_channel);
}
} else if (status == JXL_DEC_COLOR_ENCODING) {
JxlPixelFormat format = {4, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0};
printf("color profile:\n");
JxlColorEncoding color_encoding;
if (JXL_DEC_SUCCESS ==
JxlDecoderGetColorAsEncodedProfile(dec, &format,
JXL_COLOR_PROFILE_TARGET_ORIGINAL,
&color_encoding)) {
printf(" format: JPEG XL encoded color profile\n");
const char* const cs_string[4] = {"RGB color", "Grayscale", "XYB",
"Unknown"};
const char* const wp_string[12] = {"", "D65", "Custom", "", "", "",
"", "", "", "", "E", "P3"};
const char* const pr_string[12] = {
"", "sRGB", "Custom", "", "", "", "", "", "", "Rec.2100", "", "P3"};
const char* const tf_string[19] = {
"", "709", "Unknown", "", "", "", "", "", "Linear", "",
"", "", "", "sRGB", "", "", "PQ", "DCI", "HLG"};
const char* const ri_string[4] = {"Perceptual", "Relative",
"Saturation", "Absolute"};
printf(" color_space: %d (%s)\n", color_encoding.color_space,
cs_string[color_encoding.color_space]);
printf(" white_point: %d (%s)\n", color_encoding.white_point,
wp_string[color_encoding.white_point]);
if (color_encoding.white_point == JXL_WHITE_POINT_CUSTOM) {
printf(" white_point XY: %f %f\n", color_encoding.white_point_xy[0],
color_encoding.white_point_xy[1]);
}
if (color_encoding.color_space == JXL_COLOR_SPACE_RGB ||
color_encoding.color_space == JXL_COLOR_SPACE_UNKNOWN) {
printf(" primaries: %d (%s)\n", color_encoding.primaries,
pr_string[color_encoding.primaries]);
if (color_encoding.primaries == JXL_PRIMARIES_CUSTOM) {
printf(" red primaries XY: %f %f\n",
color_encoding.primaries_red_xy[0],
color_encoding.primaries_red_xy[1]);
printf(" green primaries XY: %f %f\n",
color_encoding.primaries_green_xy[0],
color_encoding.primaries_green_xy[1]);
printf(" blue primaries XY: %f %f\n",
color_encoding.primaries_blue_xy[0],
color_encoding.primaries_blue_xy[1]);
}
}
if (color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_GAMMA) {
printf(" transfer_function: gamma: %f\n", color_encoding.gamma);
} else {
printf(" transfer_function: %d (%s)\n",
color_encoding.transfer_function,
tf_string[color_encoding.transfer_function]);
}
printf(" rendering_intent: %d (%s)\n", color_encoding.rendering_intent,
ri_string[color_encoding.rendering_intent]);
} else {
// The profile is not in JPEG XL encoded form, get as ICC profile
// instead.
printf(" format: ICC profile\n");
size_t profile_size;
if (JXL_DEC_SUCCESS !=
JxlDecoderGetICCProfileSize(dec, &format,
JXL_COLOR_PROFILE_TARGET_ORIGINAL,
&profile_size)) {
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
continue;
}
printf(" ICC profile size: %" PRIu64 "\n", (uint64_t)profile_size);
if (profile_size < 132) {
fprintf(stderr, "ICC profile too small\n");
continue;
}
uint8_t* profile = (uint8_t*)malloc(profile_size);
if (JXL_DEC_SUCCESS !=
JxlDecoderGetColorAsICCProfile(dec, &format,
JXL_COLOR_PROFILE_TARGET_ORIGINAL,
profile, profile_size)) {
fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
free(profile);
continue;
}
printf(" CMM type: \"%.4s\"\n", profile + 4);
printf(" color space: \"%.4s\"\n", profile + 16);
printf(" rendering intent: %d\n", (int)profile[67]);
free(profile);
}
} else if (status == JXL_DEC_FRAME) {
if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec, &frame_header)) {
fprintf(stderr, "JxlDecoderGetFrameHeader failed\n");
break;
}
printf("frame:\n");
if (frame_header.name_length) {
char* name = malloc(frame_header.name_length + 1);
if (JXL_DEC_SUCCESS !=
JxlDecoderGetFrameName(dec, name, frame_header.name_length + 1)) {
fprintf(stderr, "JxlDecoderGetFrameName failed\n");
free(name);
break;
}
printf(" name: %s\n", name);
free(name);
}
float ms = frame_header.duration * 1000.f *
info.animation.tps_denominator / info.animation.tps_numerator;
if (info.have_animation) {
printf(" duration: %u ticks (%f ms)\n", frame_header.duration, ms);
if (info.animation.have_timecodes) {
printf(" time code: %X\n", frame_header.timecode);
}
}
if (!frame_header.name_length && !info.have_animation) {
printf(" still frame, unnamed\n");
}
} else if (status == JXL_DEC_BOX) {
JxlBoxType type;
uint64_t size;
JxlDecoderGetBoxType(dec, type, JXL_FALSE);
JxlDecoderGetBoxSizeRaw(dec, &size);
printf("box: type: \"%c%c%c%c\" size: %" PRIu64 "\n", type[0], type[1],
type[2], type[3], (uint64_t)size);
} else {
fprintf(stderr, "Unexpected decoder status\n");
break;
}
}
JxlDecoderDestroy(dec);
free(data);
return seen_basic_info;
}
int main(int argc, char* argv[]) {
if (argc != 2) {
fprintf(stderr,
"Usage: %s <jxl>\n"
"Where:\n"
" jxl = input JPEG XL image filename\n",
argv[0]);
return 1;
}
const char* jxl_filename = argv[1];
FILE* file = fopen(jxl_filename, "rb");
if (!file) {
fprintf(stderr, "Failed to read file %s\n", jxl_filename);
return 1;
}
if (!PrintBasicInfo(file)) {
fclose(file);
fprintf(stderr, "Couldn't print basic info\n");
return 1;
}
fclose(file);
return 0;
}

View File

@ -126,7 +126,7 @@ endif() # WIN32
# Internal flags for coverage builds: # Internal flags for coverage builds:
if(JPEGXL_ENABLE_COVERAGE) if(JPEGXL_ENABLE_COVERAGE)
set(JPEGXL_COVERAGE_FLAGS set(JPEGXL_COVERAGE_FLAGS
-g -O0 -fprofile-arcs -ftest-coverage -DJXL_DISABLE_SLOW_TESTS -g -O0 -fprofile-arcs -ftest-coverage
-DJXL_ENABLE_ASSERT=0 -DJXL_ENABLE_CHECK=0 -DJXL_ENABLE_ASSERT=0 -DJXL_ENABLE_CHECK=0
) )
endif() # JPEGXL_ENABLE_COVERAGE endif() # JPEGXL_ENABLE_COVERAGE

View File

@ -21,7 +21,6 @@
#include "lib/extras/enc/exr.h" #include "lib/extras/enc/exr.h"
#endif #endif
#include "lib/extras/codec_psd.h"
#include "lib/extras/dec/decode.h" #include "lib/extras/dec/decode.h"
#include "lib/extras/enc/pgx.h" #include "lib/extras/enc/pgx.h"
#include "lib/extras/enc/pnm.h" #include "lib/extras/enc/pnm.h"
@ -43,13 +42,9 @@ Status SetFromBytes(const Span<const uint8_t> bytes,
if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes"); if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes");
extras::PackedPixelFile ppf; extras::PackedPixelFile ppf;
if (extras::DecodeBytes(bytes, color_hints, io->constraints, &ppf, pool, if (extras::DecodeBytes(bytes, color_hints, io->constraints, &ppf,
orig_codec)) { orig_codec)) {
return ConvertPackedPixelFileToCodecInOut(ppf, pool, io); return ConvertPackedPixelFileToCodecInOut(ppf, pool, io);
} else if (extras::DecodeImagePSD(bytes, color_hints, pool, io)) {
// TODO(deymo): Migrate PSD codec too.
if (orig_codec) *orig_codec = extras::Codec::kPSD;
return true;
} }
return JXL_FAILURE("Codecs failed to decode"); return JXL_FAILURE("Codecs failed to decode");
} }
@ -124,9 +119,6 @@ Status Encode(const CodecInOut& io, const extras::Codec codec,
bytes); bytes);
case extras::Codec::kGIF: case extras::Codec::kGIF:
return JXL_FAILURE("Encoding to GIF is not implemented"); return JXL_FAILURE("Encoding to GIF is not implemented");
case extras::Codec::kPSD:
return extras::EncodeImagePSD(&io, c_desired, bits_per_sample, pool,
bytes);
case extras::Codec::kEXR: case extras::Codec::kEXR:
#if JPEGXL_ENABLE_EXR #if JPEGXL_ENABLE_EXR
return extras::EncodeImageEXR(&io, c_desired, pool, bytes); return extras::EncodeImageEXR(&io, c_desired, pool, bytes);
@ -147,10 +139,13 @@ Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired,
const extras::Codec codec = const extras::Codec codec =
extras::CodecFromExtension(extension, &bits_per_sample); extras::CodecFromExtension(extension, &bits_per_sample);
// Warn about incorrect usage of PBM/PGM/PGX/PPM - only the latter supports // Warn about incorrect usage of PGM/PGX/PPM - only the latter supports
// color, but CodecFromExtension lumps them all together. // color, but CodecFromExtension lumps them all together.
if (codec == extras::Codec::kPNM && extension != ".pfm") { if (codec == extras::Codec::kPNM && extension != ".pfm") {
if (!io.Main().IsGray() && extension != ".ppm") { if (io.Main().HasAlpha() && extension != ".pam") {
JXL_WARNING(
"For images with alpha, the filename should end with .pam.\n");
} else if (!io.Main().IsGray() && extension == ".pgm") {
JXL_WARNING("For color images, the filename should end with .ppm.\n"); JXL_WARNING("For color images, the filename should end with .ppm.\n");
} else if (io.Main().IsGray() && extension == ".ppm") { } else if (io.Main().IsGray() && extension == ".ppm") {
JXL_WARNING( JXL_WARNING(

View File

@ -1,621 +0,0 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/extras/codec_psd.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <numeric>
#include <string>
#include <utility>
#include <vector>
#include "lib/jxl/base/bits.h"
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/file_io.h"
#include "lib/jxl/base/printf_macros.h"
#include "lib/jxl/color_management.h"
#include "lib/jxl/common.h"
#include "lib/jxl/fields.h" // AllDefault
#include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/image_ops.h"
#include "lib/jxl/luminance.h"
namespace jxl {
namespace extras {
namespace {
uint64_t get_be_int(int bytes, const uint8_t*& pos, const uint8_t* maxpos) {
uint64_t r = 0;
if (pos + bytes <= maxpos) {
if (bytes == 1) {
r = *pos;
} else if (bytes == 2) {
r = LoadBE16(pos);
} else if (bytes == 4) {
r = LoadBE32(pos);
} else if (bytes == 8) {
r = LoadBE64(pos);
}
}
pos += bytes;
return r;
}
// Copies up to n bytes, without reading from maxpos (the STL-style end).
void safe_copy(const uint8_t* JXL_RESTRICT pos,
const uint8_t* JXL_RESTRICT maxpos, char* JXL_RESTRICT out,
size_t n) {
for (size_t i = 0; i < n; ++i) {
if (pos + i >= maxpos) return;
out[i] = pos[i];
}
}
// maxpos is the STL-style end! The valid range is up to [pos, maxpos).
int safe_strncmp(const uint8_t* pos, const uint8_t* maxpos, const char* s2,
size_t n) {
if (pos + n > maxpos) return 1;
return strncmp((const char*)pos, s2, n);
}
constexpr int PSD_VERBOSITY = 1;
Status decode_layer(const uint8_t*& pos, const uint8_t* maxpos,
ImageBundle& layer, std::vector<int> chans,
std::vector<bool> invert, int w, int h, int version,
int colormodel, bool is_layer, int depth) {
int compression_method = 2;
int nb_channels = chans.size();
JXL_DEBUG_V(PSD_VERBOSITY,
"Trying to decode layer with dimensions %ix%i and %i channels", w,
h, nb_channels);
if (w <= 0 || h <= 0) return JXL_FAILURE("PSD: empty layer");
for (int c = 0; c < nb_channels; c++) {
// skip nop byte padding
while (pos < maxpos && *pos == 128) pos++;
JXL_DEBUG_V(PSD_VERBOSITY, "Channel %i (pos %" PRIuS ")", c, (size_t)pos);
// Merged image stores all channels together (same compression method)
// Layers store channel per channel
if (is_layer || c == 0) {
compression_method = get_be_int(2, pos, maxpos);
JXL_DEBUG_V(PSD_VERBOSITY, "compression method: %i", compression_method);
if (compression_method > 1 || compression_method < 0) {
return JXL_FAILURE("PSD: can't handle compression method %i",
compression_method);
}
}
if (!is_layer && c < colormodel) {
// skip to the extra channels
if (compression_method == 0) {
pos += w * h * (depth >> 3) * colormodel;
c = colormodel - 1;
continue;
}
size_t skip_amount = 0;
for (int i = 0; i < nb_channels; i++) {
if (i < colormodel) {
for (int y = 0; y < h; y++) {
skip_amount += get_be_int(2 * version, pos, maxpos);
}
} else {
pos += h * 2 * version;
}
}
pos += skip_amount;
c = colormodel - 1;
continue;
}
if (is_layer || c == 0) {
// skip the line-counts, we don't need them
if (compression_method == 1) {
pos += h * (is_layer ? 1 : nb_channels) * 2 *
version; // PSB uses 4 bytes per rowsize instead of 2
}
}
int c_id = chans[c];
if (c_id < 0) continue; // skip
if (static_cast<unsigned int>(c_id) >= 3 + layer.extra_channels().size())
return JXL_FAILURE("PSD: can't handle channel id %i", c_id);
ImageF& ch = (c_id < 3 ? layer.color()->Plane(c_id)
: layer.extra_channels()[c_id - 3]);
for (int y = 0; y < h; y++) {
if (pos > maxpos) return JXL_FAILURE("PSD: premature end of input");
float* const JXL_RESTRICT row = ch.Row(y);
if (compression_method == 0) {
// uncompressed is easy
if (depth == 8) {
for (int x = 0; x < w; x++) {
row[x] = get_be_int(1, pos, maxpos) * (1.f / 255.f);
}
} else if (depth == 16) {
for (int x = 0; x < w; x++) {
row[x] = get_be_int(2, pos, maxpos) * (1.f / 65535.f);
}
} else if (depth == 32) {
for (int x = 0; x < w; x++) {
uint32_t f = get_be_int(4, pos, maxpos);
memcpy(&row[x], &f, 4);
}
}
} else {
// RLE is not that hard
if (depth != 8)
return JXL_FAILURE("PSD: did not expect RLE with depth>1");
for (int x = 0; x < w;) {
if (pos >= maxpos) return JXL_FAILURE("PSD: out of bounds");
int8_t rle = *pos++;
if (rle <= 0) {
if (rle == -128) continue; // nop
int count = 1 - rle;
float v = get_be_int(1, pos, maxpos) * (1.f / 255.f);
while (count && x < w) {
row[x] = v;
count--;
x++;
}
if (count) return JXL_FAILURE("PSD: row overflow");
} else {
int count = 1 + rle;
while (count && x < w) {
row[x] = get_be_int(1, pos, maxpos) * (1.f / 255.f);
count--;
x++;
}
if (count) return JXL_FAILURE("PSD: row overflow");
}
}
}
if (invert[c]) {
// sometimes 0 means full ink
for (int x = 0; x < w; x++) {
row[x] = 1.f - row[x];
}
}
}
JXL_DEBUG_V(PSD_VERBOSITY, "Channel %i read.", c);
}
return true;
}
} // namespace
Status DecodeImagePSD(const Span<const uint8_t> bytes,
const ColorHints& /*color_hints*/, ThreadPool* pool,
CodecInOut* io) {
const uint8_t* pos = bytes.data();
const uint8_t* maxpos = bytes.data() + bytes.size();
if (safe_strncmp(pos, maxpos, "8BPS", 4)) return false; // not a PSD file
JXL_DEBUG_V(PSD_VERBOSITY, "trying psd decode");
pos += 4;
int version = get_be_int(2, pos, maxpos);
JXL_DEBUG_V(PSD_VERBOSITY, "Version=%i", version);
if (version < 1 || version > 2)
return JXL_FAILURE("PSD: unknown format version");
// PSD = version 1, PSB = version 2
pos += 6;
int nb_channels = get_be_int(2, pos, maxpos);
size_t ysize = get_be_int(4, pos, maxpos);
size_t xsize = get_be_int(4, pos, maxpos);
const SizeConstraints* constraints = &io->constraints;
JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, xsize, ysize));
uint64_t total_pixel_count = static_cast<uint64_t>(xsize) * ysize;
int bitdepth = get_be_int(2, pos, maxpos);
if (bitdepth != 8 && bitdepth != 16 && bitdepth != 32) {
return JXL_FAILURE("PSD: bit depth %i invalid or not supported", bitdepth);
}
if (bitdepth == 32) {
io->metadata.m.SetFloat32Samples();
} else {
io->metadata.m.SetUintSamples(bitdepth);
}
int colormodel = get_be_int(2, pos, maxpos);
// 1 = Grayscale, 3 = RGB, 4 = CMYK
if (colormodel != 1 && colormodel != 3 && colormodel != 4)
return JXL_FAILURE("PSD: unsupported color model");
int real_nb_channels = colormodel;
std::vector<std::vector<float>> spotcolor;
if (get_be_int(4, pos, maxpos))
return JXL_FAILURE("PSD: Unsupported color mode section");
bool hasmergeddata = true;
bool have_alpha = false;
bool merged_has_alpha = false;
bool color_already_set = false;
size_t metalength = get_be_int(4, pos, maxpos);
const uint8_t* metaoffset = pos;
while (pos < metaoffset + metalength) {
char header[5] = "????";
safe_copy(pos, maxpos, header, 4);
if (memcmp(header, "8BIM", 4) != 0) {
return JXL_FAILURE("PSD: Unexpected image resource header: %s", header);
}
pos += 4;
int id = get_be_int(2, pos, maxpos);
int namelength = get_be_int(1, pos, maxpos);
pos += namelength;
if (!(namelength & 1)) pos++; // padding to even length
size_t blocklength = get_be_int(4, pos, maxpos);
// JXL_DEBUG_V(PSD_VERBOSITY, "block id: %i | block length: %" PRIuS,id,
// blocklength);
if (pos > maxpos) return JXL_FAILURE("PSD: Unexpected end of file");
if (id == 1039) { // ICC profile
size_t delta = maxpos - pos;
if (delta < blocklength) {
return JXL_FAILURE("PSD: Invalid block length");
}
PaddedBytes icc;
icc.resize(blocklength);
memcpy(icc.data(), pos, blocklength);
if (!io->metadata.m.color_encoding.SetICC(std::move(icc))) {
return JXL_FAILURE("PSD: Invalid color profile");
}
color_already_set = true;
} else if (id == 1057) { // compatibility mode or not?
if (get_be_int(4, pos, maxpos) != 1) {
return JXL_FAILURE("PSD: expected version=1 in id=1057 resource block");
}
hasmergeddata = get_be_int(1, pos, maxpos);
pos++;
blocklength -= 6; // already skipped these bytes
} else if (id == 1077) { // spot colors
int version = get_be_int(4, pos, maxpos);
if (version != 1) {
return JXL_FAILURE(
"PSD: expected DisplayInfo version 1, got version %i", version);
}
int spotcolorcount = nb_channels - colormodel;
JXL_DEBUG_V(PSD_VERBOSITY, "Reading %i spot colors. %" PRIuS,
spotcolorcount, blocklength);
for (int k = 0; k < spotcolorcount; k++) {
int colorspace = get_be_int(2, pos, maxpos);
if ((colormodel == 3 && colorspace != 0) ||
(colormodel == 4 && colorspace != 2)) {
return JXL_FAILURE(
"PSD: cannot handle spot colors in different color spaces than "
"image itself");
}
if (colorspace == 2) JXL_WARNING("PSD: K ignored in CMYK spot color");
std::vector<float> color;
color.push_back(get_be_int(2, pos, maxpos) / 65535.f); // R or C
color.push_back(get_be_int(2, pos, maxpos) / 65535.f); // G or M
color.push_back(get_be_int(2, pos, maxpos) / 65535.f); // B or Y
color.push_back(get_be_int(2, pos, maxpos) / 65535.f); // ignored or K
color.push_back(get_be_int(2, pos, maxpos) /
100.f); // solidity (alpha, basically)
int kind = get_be_int(1, pos, maxpos);
JXL_DEBUG_V(PSD_VERBOSITY, "Kind=%i", kind);
color.push_back(kind);
spotcolor.push_back(color);
if (kind == 2) {
JXL_DEBUG_V(PSD_VERBOSITY, "Actual spot color");
} else if (kind == 1) {
JXL_DEBUG_V(PSD_VERBOSITY, "Mask (alpha) channel");
} else if (kind == 0) {
JXL_DEBUG_V(PSD_VERBOSITY, "Selection (alpha) channel");
} else {
return JXL_FAILURE("PSD: Unknown extra channel type");
}
}
if (blocklength & 1) pos++;
blocklength = 0;
}
pos += blocklength;
if (blocklength & 1) pos++; // padding again
}
// TODO(deymo): Apply color hints when PSD is converted to PackedPixelFile.
(void)color_already_set;
// JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, color_already_set,
// /*is_gray=*/false, io));
size_t layerlength = get_be_int(4 * version, pos, maxpos);
const uint8_t* after_layers_pos = pos + layerlength;
if (after_layers_pos < pos) return JXL_FAILURE("PSD: invalid layer length");
if (layerlength) {
pos += 4 * version; // don't care about layerinfolength
JXL_DEBUG_V(PSD_VERBOSITY, "Layer section length: %" PRIuS, layerlength);
int layercount = static_cast<int16_t>(get_be_int(2, pos, maxpos));
JXL_DEBUG_V(PSD_VERBOSITY, "Layer count: %i", layercount);
io->frames.clear();
if (layercount == 0) {
if (get_be_int(2, pos, maxpos) != 0) {
return JXL_FAILURE(
"PSD: Expected zero padding before additional layer info");
}
while (pos < after_layers_pos) {
if (safe_strncmp(pos, maxpos, "8BIM", 4) &&
safe_strncmp(pos, maxpos, "8B64", 4))
return JXL_FAILURE("PSD: Unexpected layer info signature");
pos += 4;
const uint8_t* tpos = pos;
pos += 4;
size_t blocklength = get_be_int(4 * version, pos, maxpos);
JXL_DEBUG_V(PSD_VERBOSITY, "Length=%" PRIuS, blocklength);
if (blocklength > 0) {
if (pos >= maxpos) return JXL_FAILURE("PSD: Unexpected end of file");
size_t delta = maxpos - pos;
if (delta < blocklength) {
return JXL_FAILURE("PSD: Invalid block length");
}
}
if (!safe_strncmp(tpos, maxpos, "Layr", 4) ||
!safe_strncmp(tpos, maxpos, "Lr16", 4) ||
!safe_strncmp(tpos, maxpos, "Lr32", 4)) {
layercount = static_cast<int16_t>(get_be_int(2, pos, maxpos));
if (layercount < 0) {
return JXL_FAILURE("PSD: Invalid layer count");
}
JXL_DEBUG_V(PSD_VERBOSITY, "Real layer count: %i", layercount);
if (layercount > 1) have_alpha = true;
break;
}
if (!safe_strncmp(tpos, maxpos, "Mtrn", 4) ||
!safe_strncmp(tpos, maxpos, "Mt16", 4) ||
!safe_strncmp(tpos, maxpos, "Mt32", 4)) {
JXL_DEBUG_V(PSD_VERBOSITY, "Merged layer has transparency channel");
if (nb_channels > real_nb_channels) {
have_alpha = true;
merged_has_alpha = true;
}
}
pos += blocklength;
}
} else if (layercount < 0) {
// negative layer count indicates merged has alpha and it is to be shown
if (nb_channels > real_nb_channels) {
have_alpha = true;
merged_has_alpha = true;
}
layercount = -layercount;
} else {
// multiple layers implies there is alpha
have_alpha = true;
}
ExtraChannelInfo info;
info.bit_depth.bits_per_sample = bitdepth;
info.dim_shift = 0;
if (colormodel == 4) { // cmyk
info.type = ExtraChannel::kBlack;
io->metadata.m.extra_channel_info.push_back(info);
}
if (have_alpha) {
JXL_DEBUG_V(PSD_VERBOSITY, "Have alpha");
real_nb_channels++;
info.type = ExtraChannel::kAlpha;
info.alpha_associated =
false; // true? PSD is not consistent with this, need to check
io->metadata.m.extra_channel_info.push_back(info);
}
if (merged_has_alpha && !spotcolor.empty() && spotcolor[0][5] == 1) {
// first alpha channel
spotcolor.erase(spotcolor.begin());
}
for (size_t i = 0; i < spotcolor.size(); i++) {
real_nb_channels++;
if (spotcolor[i][5] == 2) {
info.type = ExtraChannel::kSpotColor;
info.spot_color[0] = spotcolor[i][0];
info.spot_color[1] = spotcolor[i][1];
info.spot_color[2] = spotcolor[i][2];
info.spot_color[3] = spotcolor[i][4];
} else if (spotcolor[i][5] == 1) {
info.type = ExtraChannel::kAlpha;
} else if (spotcolor[i][5] == 0) {
info.type = ExtraChannel::kSelectionMask;
} else
return JXL_FAILURE("PSD: unhandled extra channel");
io->metadata.m.extra_channel_info.push_back(info);
}
std::vector<std::vector<int>> layer_chan_id;
std::vector<size_t> layer_offsets(layercount + 1, 0);
std::vector<bool> is_real_layer(layercount, false);
for (int l = 0; l < layercount; l++) {
ImageBundle layer(&io->metadata.m);
layer.duration = 0;
layer.blend = (l > 0);
layer.use_for_next_frame = (l + 1 < layercount);
layer.origin.y0 = get_be_int(4, pos, maxpos);
layer.origin.x0 = get_be_int(4, pos, maxpos);
size_t height = get_be_int(4, pos, maxpos) - layer.origin.y0;
size_t width = get_be_int(4, pos, maxpos) - layer.origin.x0;
JXL_DEBUG_V(PSD_VERBOSITY,
"Layer %i: %" PRIuS " x %" PRIuS " at origin (%i, %i)", l,
width, height, layer.origin.x0, layer.origin.y0);
int nb_chs = get_be_int(2, pos, maxpos);
JXL_DEBUG_V(PSD_VERBOSITY, " channels: %i", nb_chs);
std::vector<int> chan_ids;
layer_offsets[l + 1] = layer_offsets[l];
for (int lc = 0; lc < nb_chs; lc++) {
int id = get_be_int(2, pos, maxpos);
JXL_DEBUG_V(PSD_VERBOSITY, " id=%i", id);
if (id == 65535) {
chan_ids.push_back(colormodel); // alpha
} else if (id == 65534) {
chan_ids.push_back(-1); // layer mask, ignored
} else {
chan_ids.push_back(id); // color channel
}
layer_offsets[l + 1] += get_be_int(4 * version, pos, maxpos);
}
layer_chan_id.push_back(chan_ids);
if (safe_strncmp(pos, maxpos, "8BIM", 4))
return JXL_FAILURE("PSD: Layer %i: Unexpected signature (not 8BIM)", l);
pos += 4;
if (safe_strncmp(pos, maxpos, "norm", 4)) {
return JXL_FAILURE(
"PSD: Layer %i: Cannot handle non-default blend mode", l);
}
pos += 4;
int opacity = get_be_int(1, pos, maxpos);
if (opacity < 100) {
JXL_WARNING(
"PSD: ignoring opacity of semi-transparent layer %i (opacity=%i)",
l, opacity);
}
pos++; // clipping
int flags = get_be_int(1, pos, maxpos);
pos++;
bool invisible = (flags & 2);
if (invisible) {
if (l + 1 < layercount) {
layer.blend = false;
layer.use_for_next_frame = false;
} else {
// TODO: instead add dummy last frame?
JXL_WARNING("PSD: invisible top layer was made visible");
}
}
size_t extradata = get_be_int(4, pos, maxpos);
JXL_DEBUG_V(PSD_VERBOSITY, " extradata: %" PRIuS " bytes", extradata);
const uint8_t* after_extra = pos + extradata;
// TODO: deal with non-empty layer masks
pos += get_be_int(4, pos, maxpos); // skip layer mask data
pos += get_be_int(4, pos, maxpos); // skip layer blend range data
size_t namelength = get_be_int(1, pos, maxpos);
size_t delta = maxpos - pos;
if (delta < namelength) return JXL_FAILURE("PSD: Invalid block length");
char lname[256] = {};
memcpy(lname, pos, namelength);
lname[namelength] = 0;
JXL_DEBUG_V(PSD_VERBOSITY, " name: %s", lname);
pos = after_extra;
if (width == 0 || height == 0) {
JXL_DEBUG_V(PSD_VERBOSITY,
" NOT A REAL LAYER"); // probably layer group
continue;
}
is_real_layer[l] = true;
JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, width, height));
uint64_t pixel_count = static_cast<uint64_t>(width) * height;
if (!SafeAdd(total_pixel_count, pixel_count, total_pixel_count)) {
return JXL_FAILURE("Image too big");
}
if (total_pixel_count > constraints->dec_max_pixels) {
return JXL_FAILURE("Image too big");
}
Image3F rgb(width, height);
layer.SetFromImage(std::move(rgb), io->metadata.m.color_encoding);
std::vector<ImageF> ec;
for (const auto& ec_meta : layer.metadata()->extra_channel_info) {
ImageF extra(width, height);
if (ec_meta.type == ExtraChannel::kAlpha) {
FillPlane(1.0f, &extra, Rect(extra)); // opaque
} else {
ZeroFillPlane(&extra, Rect(extra)); // zeroes
}
ec.push_back(std::move(extra));
}
if (!ec.empty()) layer.SetExtraChannels(std::move(ec));
layer.name = lname;
io->dec_pixels += layer.xsize() * layer.ysize();
io->frames.push_back(std::move(layer));
}
std::vector<bool> invert(real_nb_channels, false);
int il = 0;
const uint8_t* bpos = pos;
for (int l = 0; l < layercount; l++) {
if (!is_real_layer[l]) continue;
pos = bpos + layer_offsets[l];
if (pos < bpos) return JXL_FAILURE("PSD: invalid layer offset");
JXL_DEBUG_V(PSD_VERBOSITY, "At position %i (%" PRIuS ")",
(int)(pos - bytes.data()), (size_t)pos);
ImageBundle& layer = io->frames[il++];
std::vector<int>& chan_id = layer_chan_id[l];
if (chan_id.size() > invert.size()) invert.resize(chan_id.size(), false);
JXL_RETURN_IF_ERROR(decode_layer(pos, maxpos, layer, chan_id, invert,
layer.xsize(), layer.ysize(), version,
colormodel, true, bitdepth));
}
} else
return JXL_FAILURE("PSD: no layer data found");
if (!hasmergeddata && !spotcolor.empty()) {
return JXL_FAILURE("PSD: extra channel data declared but not found");
}
if (!spotcolor.empty() || (hasmergeddata && io->frames.empty())) {
// PSD only has spot colors / extra alpha/mask data in the merged image
// We don't redundantly store the merged image, so we put it in the first
// layer (the next layers will kAdd zeroes to it)
pos = after_layers_pos;
bool have_only_merged = false;
if (io->frames.empty()) {
// There is only the merged image, no layers
ImageBundle nlayer(&io->metadata.m);
Image3F rgb(xsize, ysize);
nlayer.SetFromImage(std::move(rgb), io->metadata.m.color_encoding);
std::vector<ImageF> ec;
for (const auto& ec_meta : nlayer.metadata()->extra_channel_info) {
ImageF extra(xsize, ysize);
if (ec_meta.type == ExtraChannel::kAlpha) {
FillPlane(1.0f, &extra, Rect(extra)); // opaque
} else {
ZeroFillPlane(&extra, Rect(extra)); // zeroes
}
ec.push_back(std::move(extra));
}
if (!ec.empty()) nlayer.SetExtraChannels(std::move(ec));
io->dec_pixels += nlayer.xsize() * nlayer.ysize();
io->frames.push_back(std::move(nlayer));
have_only_merged = true;
}
ImageBundle& layer = io->frames[0];
std::vector<int> chan_id(real_nb_channels);
std::iota(chan_id.begin(), chan_id.end(), 0);
std::vector<bool> invert(real_nb_channels, false);
if (static_cast<int>(spotcolor.size()) + colormodel + 1 <
real_nb_channels) {
return JXL_FAILURE("Inconsistent layer configuration");
}
if (!merged_has_alpha) {
if (colormodel >= real_nb_channels) {
return JXL_FAILURE("Inconsistent layer configuration");
}
chan_id.erase(chan_id.begin() + colormodel);
invert.erase(invert.begin() + colormodel);
} else {
colormodel++;
}
for (size_t i = colormodel; i < invert.size(); i++) {
if (spotcolor[i - colormodel][5] == 2) invert[i] = true;
if (spotcolor[i - colormodel][5] == 0) invert[i] = true;
}
JXL_RETURN_IF_ERROR(decode_layer(
pos, maxpos, layer, chan_id, invert, layer.xsize(), layer.ysize(),
version, (have_only_merged ? 0 : colormodel), false, bitdepth));
}
if (io->frames.empty()) return JXL_FAILURE("PSD: no layers");
io->SetSize(xsize, ysize);
SetIntensityTarget(io);
return true;
}
Status EncodeImagePSD(const CodecInOut* io, const ColorEncoding& c_desired,
size_t bits_per_sample, ThreadPool* pool,
PaddedBytes* bytes) {
return JXL_FAILURE("PSD encoding not yet implemented");
}
} // namespace extras
} // namespace jxl

View File

@ -1,37 +0,0 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#ifndef LIB_EXTRAS_CODEC_PSD_H_
#define LIB_EXTRAS_CODEC_PSD_H_
// Decodes Photoshop PSD/PSB, preserving the layers
#include <stddef.h>
#include <stdint.h>
#include "lib/extras/dec/color_hints.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/codec_in_out.h"
#include "lib/jxl/color_encoding_internal.h"
namespace jxl {
namespace extras {
// Decodes `bytes` into `io`.
Status DecodeImagePSD(const Span<const uint8_t> bytes,
const extras::ColorHints& color_hints, ThreadPool* pool,
CodecInOut* io);
// Not implemented yet
Status EncodeImagePSD(const CodecInOut* io, const ColorEncoding& c_desired,
size_t bits_per_sample, ThreadPool* pool,
PaddedBytes* bytes);
} // namespace extras
} // namespace jxl
#endif // LIB_EXTRAS_CODEC_PSD_H_

View File

@ -29,6 +29,31 @@ namespace jxl {
namespace extras { namespace extras {
namespace { namespace {
std::string ExtensionFromCodec(Codec codec, const bool is_gray,
const bool has_alpha,
const size_t bits_per_sample) {
switch (codec) {
case Codec::kJPG:
return ".jpg";
case Codec::kPGX:
return ".pgx";
case Codec::kPNG:
return ".png";
case Codec::kPNM:
if (has_alpha) return ".pam";
if (is_gray) return ".pgm";
return (bits_per_sample == 32) ? ".pfm" : ".ppm";
case Codec::kGIF:
return ".gif";
case Codec::kEXR:
return ".exr";
case Codec::kUnknown:
return std::string();
}
JXL_UNREACHABLE;
return std::string();
}
CodecInOut CreateTestImage(const size_t xsize, const size_t ysize, CodecInOut CreateTestImage(const size_t xsize, const size_t ysize,
const bool is_gray, const bool add_alpha, const bool is_gray, const bool add_alpha,
const size_t bits_per_sample, const size_t bits_per_sample,
@ -76,7 +101,7 @@ void TestRoundTrip(Codec codec, const size_t xsize, const size_t ysize,
// grayscale, and somehow does not have sufficient precision for this test. // grayscale, and somehow does not have sufficient precision for this test.
if (codec == Codec::kEXR) return; if (codec == Codec::kEXR) return;
printf("Codec %s bps:%" PRIuS " gr:%d al:%d\n", printf("Codec %s bps:%" PRIuS " gr:%d al:%d\n",
ExtensionFromCodec(codec, is_gray, bits_per_sample).c_str(), ExtensionFromCodec(codec, is_gray, add_alpha, bits_per_sample).c_str(),
bits_per_sample, is_gray, add_alpha); bits_per_sample, is_gray, add_alpha);
ColorEncoding c_native; ColorEncoding c_native;
@ -231,11 +256,11 @@ CodecInOut DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
TEST(CodecTest, TestMetadataSRGB) { TEST(CodecTest, TestMetadataSRGB) {
ThreadPoolInternal pool(12); ThreadPoolInternal pool(12);
const char* paths[] = {"raw.pixls/DJI-FC6310-16bit_srgb8_v4_krita.png", const char* paths[] = {"third_party/raw.pixls/DJI-FC6310-16bit_srgb8_v4_krita.png",
"raw.pixls/Google-Pixel2XL-16bit_srgb8_v4_krita.png", "third_party/raw.pixls/Google-Pixel2XL-16bit_srgb8_v4_krita.png",
"raw.pixls/HUAWEI-EVA-L09-16bit_srgb8_dt.png", "third_party/raw.pixls/HUAWEI-EVA-L09-16bit_srgb8_dt.png",
"raw.pixls/Nikon-D300-12bit_srgb8_dt.png", "third_party/raw.pixls/Nikon-D300-12bit_srgb8_dt.png",
"raw.pixls/Sony-DSC-RX1RM2-14bit_srgb8_v4_krita.png"}; "third_party/raw.pixls/Sony-DSC-RX1RM2-14bit_srgb8_v4_krita.png"};
for (const char* relative_pathname : paths) { for (const char* relative_pathname : paths) {
const CodecInOut io = const CodecInOut io =
DecodeRoundtrip(relative_pathname, Codec::kPNG, &pool); DecodeRoundtrip(relative_pathname, Codec::kPNG, &pool);
@ -260,9 +285,9 @@ TEST(CodecTest, TestMetadataLinear) {
ThreadPoolInternal pool(12); ThreadPoolInternal pool(12);
const char* paths[3] = { const char* paths[3] = {
"raw.pixls/Google-Pixel2XL-16bit_acescg_g1_v4_krita.png", "third_party/raw.pixls/Google-Pixel2XL-16bit_acescg_g1_v4_krita.png",
"raw.pixls/HUAWEI-EVA-L09-16bit_709_g1_dt.png", "third_party/raw.pixls/HUAWEI-EVA-L09-16bit_709_g1_dt.png",
"raw.pixls/Nikon-D300-12bit_2020_g1_dt.png", "third_party/raw.pixls/Nikon-D300-12bit_2020_g1_dt.png",
}; };
const WhitePoint white_points[3] = {WhitePoint::kCustom, WhitePoint::kD65, const WhitePoint white_points[3] = {WhitePoint::kCustom, WhitePoint::kD65,
WhitePoint::kD65}; WhitePoint::kD65};
@ -292,8 +317,8 @@ TEST(CodecTest, TestMetadataICC) {
ThreadPoolInternal pool(12); ThreadPoolInternal pool(12);
const char* paths[] = { const char* paths[] = {
"raw.pixls/DJI-FC6310-16bit_709_v4_krita.png", "third_party/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png",
"raw.pixls/Sony-DSC-RX1RM2-14bit_709_v4_krita.png", "third_party/raw.pixls/Sony-DSC-RX1RM2-14bit_709_v4_krita.png",
}; };
for (const char* relative_pathname : paths) { for (const char* relative_pathname : paths) {
const CodecInOut io = const CodecInOut io =
@ -315,28 +340,28 @@ TEST(CodecTest, TestMetadataICC) {
} }
} }
TEST(CodecTest, TestPNGSuite) { TEST(CodecTest, Testthird_party/pngsuite) {
ThreadPoolInternal pool(12); ThreadPoolInternal pool(12);
// Ensure we can load PNG with text, japanese UTF-8, compressed text. // Ensure we can load PNG with text, japanese UTF-8, compressed text.
(void)DecodeRoundtrip("pngsuite/ct1n0g04.png", Codec::kPNG, &pool); (void)DecodeRoundtrip("third_party/pngsuite/ct1n0g04.png", Codec::kPNG, &pool);
(void)DecodeRoundtrip("pngsuite/ctjn0g04.png", Codec::kPNG, &pool); (void)DecodeRoundtrip("third_party/pngsuite/ctjn0g04.png", Codec::kPNG, &pool);
(void)DecodeRoundtrip("pngsuite/ctzn0g04.png", Codec::kPNG, &pool); (void)DecodeRoundtrip("third_party/pngsuite/ctzn0g04.png", Codec::kPNG, &pool);
// Extract gAMA // Extract gAMA
const CodecInOut b1 = const CodecInOut b1 =
DecodeRoundtrip("pngsuite/g10n3p04.png", Codec::kPNG, &pool); DecodeRoundtrip("third_party/pngsuite/g10n3p04.png", Codec::kPNG, &pool);
EXPECT_TRUE(b1.metadata.color_encoding.tf.IsLinear()); EXPECT_TRUE(b1.metadata.color_encoding.tf.IsLinear());
// Extract cHRM // Extract cHRM
const CodecInOut b_p = const CodecInOut b_p =
DecodeRoundtrip("pngsuite/ccwn2c08.png", Codec::kPNG, &pool); DecodeRoundtrip("third_party/pngsuite/ccwn2c08.png", Codec::kPNG, &pool);
EXPECT_EQ(Primaries::kSRGB, b_p.metadata.color_encoding.primaries); EXPECT_EQ(Primaries::kSRGB, b_p.metadata.color_encoding.primaries);
EXPECT_EQ(WhitePoint::kD65, b_p.metadata.color_encoding.white_point); EXPECT_EQ(WhitePoint::kD65, b_p.metadata.color_encoding.white_point);
// Extract EXIF from (new-style) dedicated chunk // Extract EXIF from (new-style) dedicated chunk
const CodecInOut b_exif = const CodecInOut b_exif =
DecodeRoundtrip("pngsuite/exif2c08.png", Codec::kPNG, &pool); DecodeRoundtrip("third_party/pngsuite/exif2c08.png", Codec::kPNG, &pool);
EXPECT_EQ(978, b_exif.blobs.exif.size()); EXPECT_EQ(978, b_exif.blobs.exif.size());
} }
#endif #endif
@ -359,13 +384,13 @@ void VerifyWideGamutMetadata(const std::string& relative_pathname,
TEST(CodecTest, TestWideGamut) { TEST(CodecTest, TestWideGamut) {
ThreadPoolInternal pool(12); ThreadPoolInternal pool(12);
// VerifyWideGamutMetadata("wide-gamut-tests/P3-sRGB-color-bars.png", // VerifyWideGamutMetadata("third_party/wide-gamut-tests/P3-sRGB-color-bars.png",
// Primaries::kP3, &pool); // Primaries::kP3, &pool);
VerifyWideGamutMetadata("wide-gamut-tests/P3-sRGB-color-ring.png", VerifyWideGamutMetadata("third_party/wide-gamut-tests/P3-sRGB-color-ring.png",
Primaries::kP3, &pool); Primaries::kP3, &pool);
// VerifyWideGamutMetadata("wide-gamut-tests/R2020-sRGB-color-bars.png", // VerifyWideGamutMetadata("third_party/wide-gamut-tests/R2020-sRGB-color-bars.png",
// Primaries::k2100, &pool); // Primaries::k2100, &pool);
// VerifyWideGamutMetadata("wide-gamut-tests/R2020-sRGB-color-ring.png", // VerifyWideGamutMetadata("third_party/wide-gamut-tests/R2020-sRGB-color-ring.png",
// Primaries::k2100, &pool); // Primaries::k2100, &pool);
} }

View File

@ -43,6 +43,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "jxl/codestream_header.h"
#include "jxl/encode.h" #include "jxl/encode.h"
#include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/printf_macros.h" #include "lib/jxl/base/printf_macros.h"
@ -56,6 +57,12 @@ namespace extras {
namespace { namespace {
/* hIST chunk tail is not proccesed properly; skip this chunk completely;
see https://github.com/glennrp/libpng/pull/413 */
const png_byte kIgnoredPngChunks[] = {
104, 73, 83, 84, '\0' /* hIST */
};
// Returns floating-point value from the PNG encoding (times 10^5). // Returns floating-point value from the PNG encoding (times 10^5).
static double F64FromU32(const uint32_t x) { static double F64FromU32(const uint32_t x) {
return static_cast<int32_t>(x) * 1E-5; return static_cast<int32_t>(x) * 1E-5;
@ -163,6 +170,31 @@ class BlobsReaderPNG {
return true; return true;
} }
// Returns false if invalid.
static JXL_INLINE Status DecodeDecimal(const char** pos, const char* end,
uint32_t* JXL_RESTRICT value) {
size_t len = 0;
*value = 0;
while (*pos < end) {
char next = **pos;
if (next >= '0' && next <= '9') {
*value = (*value * 10) + static_cast<uint32_t>(next - '0');
len++;
if (len > 8) {
break;
}
} else {
// Do not consume terminator (non-decimal digit).
break;
}
(*pos)++;
}
if (len == 0 || len > 8) {
return JXL_FAILURE("Failed to parse decimal");
}
return true;
}
// Parses a PNG text chunk with key of the form "Raw profile type ####", with // Parses a PNG text chunk with key of the form "Raw profile type ####", with
// #### a type. // #### a type.
// Returns whether it could successfully parse the content. // Returns whether it could successfully parse the content.
@ -196,17 +228,15 @@ class BlobsReaderPNG {
// We parsed so far a \n, some number of non \n characters and are now // We parsed so far a \n, some number of non \n characters and are now
// pointing at a \n. // pointing at a \n.
if (*(pos++) != '\n') return false; if (*(pos++) != '\n') return false;
unsigned long bytes_to_decode; uint32_t bytes_to_decode = 0;
const int fields = sscanf(pos, "%8lu", &bytes_to_decode); JXL_RETURN_IF_ERROR(DecodeDecimal(&pos, encoded_end, &bytes_to_decode));
if (fields != 1) return false; // Failed to decode metadata header
JXL_ASSERT(pos + 8 <= encoded_end);
pos += 8; // read %8lu
// We need 2*bytes for the hex values plus 1 byte every 36 values. // We need 2*bytes for the hex values plus 1 byte every 36 values,
// plus terminal \n for length.
const unsigned long needed_bytes = const unsigned long needed_bytes =
bytes_to_decode * 2 + 1 + DivCeil(bytes_to_decode, 36); bytes_to_decode * 2 + 1 + DivCeil(bytes_to_decode, 36);
if (needed_bytes != static_cast<size_t>(encoded_end - pos)) { if (needed_bytes != static_cast<size_t>(encoded_end - pos)) {
return JXL_FAILURE("Not enough bytes to parse %lu bytes in hex", return JXL_FAILURE("Not enough bytes to parse %d bytes in hex",
bytes_to_decode); bytes_to_decode);
} }
JXL_ASSERT(bytes->empty()); JXL_ASSERT(bytes->empty());
@ -251,7 +281,7 @@ constexpr uint32_t kId_cHRM = 0x4D524863;
constexpr uint32_t kId_eXIf = 0x66495865; constexpr uint32_t kId_eXIf = 0x66495865;
struct APNGFrame { struct APNGFrame {
PaddedBytes pixels; std::vector<uint8_t> pixels;
std::vector<uint8_t*> rows; std::vector<uint8_t*> rows;
unsigned int w, h, delay_num, delay_den; unsigned int w, h, delay_num, delay_den;
}; };
@ -270,7 +300,7 @@ struct Reader {
}; };
const unsigned long cMaxPNGSize = 1000000UL; const unsigned long cMaxPNGSize = 1000000UL;
const size_t kMaxPNGChunkSize = 100000000; // 100 MB const size_t kMaxPNGChunkSize = 1lu << 30; // 1 GB
void info_fn(png_structp png_ptr, png_infop info_ptr) { void info_fn(png_structp png_ptr, png_infop info_ptr) {
png_set_expand(png_ptr); png_set_expand(png_ptr);
@ -284,11 +314,12 @@ void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num,
int pass) { int pass) {
APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr); APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr);
JXL_CHECK(frame); JXL_CHECK(frame);
JXL_CHECK(row_num < frame->rows.size());
JXL_CHECK(frame->rows[row_num] < frame->pixels.data() + frame->pixels.size()); JXL_CHECK(frame->rows[row_num] < frame->pixels.data() + frame->pixels.size());
png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row); png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row);
} }
inline unsigned int read_chunk(Reader* r, PaddedBytes* pChunk) { inline unsigned int read_chunk(Reader* r, std::vector<uint8_t>* pChunk) {
unsigned char len[4]; unsigned char len[4];
if (r->Read(&len, 4)) { if (r->Read(&len, 4)) {
const auto size = png_get_uint_32(len); const auto size = png_get_uint_32(len);
@ -307,8 +338,8 @@ inline unsigned int read_chunk(Reader* r, PaddedBytes* pChunk) {
} }
int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr, int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr,
bool hasInfo, PaddedBytes& chunkIHDR, bool hasInfo, std::vector<uint8_t>& chunkIHDR,
std::vector<PaddedBytes>& chunksInfo) { std::vector<std::vector<uint8_t>>& chunksInfo) {
unsigned char header[8] = {137, 80, 78, 71, 13, 10, 26, 10}; unsigned char header[8] = {137, 80, 78, 71, 13, 10, 26, 10};
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
@ -319,6 +350,9 @@ int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr,
return 1; return 1;
} }
png_set_keep_unknown_chunks(png_ptr, 1, kIgnoredPngChunks,
(int)sizeof(kIgnoredPngChunks) / 5);
png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
png_set_progressive_read_fn(png_ptr, frame_ptr, info_fn, row_fn, NULL); png_set_progressive_read_fn(png_ptr, frame_ptr, info_fn, row_fn, NULL);
@ -380,9 +414,9 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
unsigned char sig[8]; unsigned char sig[8];
png_structp png_ptr = nullptr; png_structp png_ptr = nullptr;
png_infop info_ptr = nullptr; png_infop info_ptr = nullptr;
PaddedBytes chunk; std::vector<uint8_t> chunk;
PaddedBytes chunkIHDR; std::vector<uint8_t> chunkIHDR;
std::vector<PaddedBytes> chunksInfo; std::vector<std::vector<uint8_t>> chunksInfo;
bool isAnimated = false; bool isAnimated = false;
bool hasInfo = false; bool hasInfo = false;
APNGFrame frameRaw = {}; APNGFrame frameRaw = {};
@ -594,8 +628,7 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
auto ok = png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, auto ok = png_get_iCCP(png_ptr, info_ptr, &name, &compression_type,
&profile, &proflen); &profile, &proflen);
if (ok && proflen) { if (ok && proflen) {
ppf->icc.resize(proflen); ppf->icc.assign(profile, profile + proflen);
memcpy(ppf->icc.data(), profile, proflen);
have_color = true; have_color = true;
} else { } else {
// TODO(eustas): JXL_WARNING? // TODO(eustas): JXL_WARNING?
@ -670,7 +703,8 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
has_nontrivial_background && frame.dispose_op != DISPOSE_OP_PREVIOUS; has_nontrivial_background && frame.dispose_op != DISPOSE_OP_PREVIOUS;
size_t x0 = frame.x0; size_t x0 = frame.x0;
size_t y0 = frame.y0; size_t y0 = frame.y0;
size_t xsize = frame.data.xsize;
size_t ysize = frame.data.ysize;
if (previous_frame_should_be_cleared) { if (previous_frame_should_be_cleared) {
size_t xs = frame.data.xsize; size_t xs = frame.data.xsize;
size_t ys = frame.data.ysize; size_t ys = frame.data.ysize;
@ -710,6 +744,8 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
x0 = px0; x0 = px0;
y0 = py0; y0 = py0;
xsize = pxs;
ysize = pys;
should_blend = false; should_blend = false;
ppf->frames.emplace_back(std::move(new_data)); ppf->frames.emplace_back(std::move(new_data));
} else { } else {
@ -718,12 +754,15 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
memset(blank.pixels(), 0, blank.pixels_size); memset(blank.pixels(), 0, blank.pixels_size);
ppf->frames.emplace_back(std::move(blank)); ppf->frames.emplace_back(std::move(blank));
auto& pframe = ppf->frames.back(); auto& pframe = ppf->frames.back();
pframe.x0 = px0; pframe.frame_info.layer_info.crop_x0 = px0;
pframe.y0 = py0; pframe.frame_info.layer_info.crop_y0 = py0;
pframe.frame_info.layer_info.xsize = frame.xsize;
pframe.frame_info.layer_info.ysize = frame.ysize;
pframe.frame_info.duration = 0; pframe.frame_info.duration = 0;
pframe.blend = false; pframe.frame_info.layer_info.have_crop = 0;
pframe.use_for_next_frame = true; pframe.frame_info.layer_info.blend_info.blendmode = JXL_BLEND_REPLACE;
pframe.frame_info.layer_info.blend_info.source = 0;
pframe.frame_info.layer_info.save_as_reference = 1;
ppf->frames.emplace_back(std::move(frame.data)); ppf->frames.emplace_back(std::move(frame.data));
} }
} else { } else {
@ -731,18 +770,22 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
} }
auto& pframe = ppf->frames.back(); auto& pframe = ppf->frames.back();
pframe.x0 = x0; pframe.frame_info.layer_info.crop_x0 = x0;
pframe.y0 = y0; pframe.frame_info.layer_info.crop_y0 = y0;
pframe.frame_info.layer_info.xsize = xsize;
pframe.frame_info.layer_info.ysize = ysize;
pframe.frame_info.duration = frame.duration; pframe.frame_info.duration = frame.duration;
pframe.blend = should_blend; pframe.frame_info.layer_info.blend_info.blendmode =
pframe.use_for_next_frame = use_for_next_frame; should_blend ? JXL_BLEND_BLEND : JXL_BLEND_REPLACE;
bool is_full_size = x0 == 0 && y0 == 0 && xsize == ppf->info.xsize &&
ysize == ppf->info.ysize;
pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1;
pframe.frame_info.layer_info.blend_info.source = should_blend ? 1 : 0;
pframe.frame_info.layer_info.blend_info.alpha = 0;
pframe.frame_info.layer_info.save_as_reference = use_for_next_frame ? 1 : 0;
if (has_nontrivial_background && previous_frame_should_be_cleared =
frame.dispose_op == DISPOSE_OP_BACKGROUND) { 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"); if (ppf->frames.empty()) return JXL_FAILURE("No frames decoded");
ppf->frames.back().frame_info.is_last = true; ppf->frames.back().frame_info.is_last = true;
@ -750,60 +793,5 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
return true; return true;
} }
static void PngWrite(png_structp png_ptr, png_bytep data, png_size_t length) {
PaddedBytes* bytes = static_cast<PaddedBytes*>(png_get_io_ptr(png_ptr));
bytes->append(data, data + length);
}
// Stores XMP and EXIF/IPTC into key/value strings for PNG
class BlobsWriterPNG {
public:
static Status Encode(const Blobs& blobs, std::vector<std::string>* strings) {
if (!blobs.exif.empty()) {
JXL_RETURN_IF_ERROR(EncodeBase16("exif", blobs.exif, strings));
}
if (!blobs.iptc.empty()) {
JXL_RETURN_IF_ERROR(EncodeBase16("iptc", blobs.iptc, strings));
}
if (!blobs.xmp.empty()) {
JXL_RETURN_IF_ERROR(EncodeBase16("xmp", blobs.xmp, strings));
}
return true;
}
private:
static JXL_INLINE char EncodeNibble(const uint8_t nibble) {
JXL_ASSERT(nibble < 16);
return (nibble < 10) ? '0' + nibble : 'a' + nibble - 10;
}
static Status EncodeBase16(const std::string& type, const PaddedBytes& bytes,
std::vector<std::string>* strings) {
// Encoding: base16 with newline after 72 chars.
const size_t base16_size =
2 * bytes.size() + DivCeil(bytes.size(), size_t(36)) + 1;
std::string base16;
base16.reserve(base16_size);
for (size_t i = 0; i < bytes.size(); ++i) {
if (i % 36 == 0) base16.push_back('\n');
base16.push_back(EncodeNibble(bytes[i] >> 4));
base16.push_back(EncodeNibble(bytes[i] & 0x0F));
}
base16.push_back('\n');
JXL_ASSERT(base16.length() == base16_size);
char key[30];
snprintf(key, sizeof(key), "Raw profile type %s", type.c_str());
char header[30];
snprintf(header, sizeof(header), "\n%s\n%8" PRIuS, type.c_str(),
bytes.size());
strings->push_back(std::string(key));
strings->push_back(std::string(header) + base16);
return true;
}
};
} // namespace extras } // namespace extras
} // namespace jxl } // namespace jxl

View File

@ -42,9 +42,7 @@ Status ApplyColorHints(const ColorHints& color_hints,
got_color_space = true; got_color_space = true;
} else if (key == "icc_pathname") { } else if (key == "icc_pathname") {
PaddedBytes icc; JXL_RETURN_IF_ERROR(ReadFile(value, &ppf->icc));
JXL_RETURN_IF_ERROR(ReadFile(value, &icc));
ppf->icc = std::vector<uint8_t>{icc.data(), icc.data() + icc.size()};
got_color_space = true; got_color_space = true;
} else { } else {
JXL_WARNING("Ignoring %s hint", key.c_str()); JXL_WARNING("Ignoring %s hint", key.c_str());

View File

@ -31,31 +31,6 @@ constexpr size_t kMinBytes = 9;
} // namespace } // namespace
std::string ExtensionFromCodec(Codec codec, const bool is_gray,
const size_t bits_per_sample) {
switch (codec) {
case Codec::kJPG:
return ".jpg";
case Codec::kPGX:
return ".pgx";
case Codec::kPNG:
return ".png";
case Codec::kPNM:
if (is_gray) return ".pgm";
return (bits_per_sample == 32) ? ".pfm" : ".ppm";
case Codec::kGIF:
return ".gif";
case Codec::kEXR:
return ".exr";
case Codec::kPSD:
return ".psd";
case Codec::kUnknown:
return std::string();
}
JXL_UNREACHABLE;
return std::string();
}
Codec CodecFromExtension(std::string extension, Codec CodecFromExtension(std::string extension,
size_t* JXL_RESTRICT bits_per_sample) { size_t* JXL_RESTRICT bits_per_sample) {
std::transform( std::transform(
@ -68,10 +43,8 @@ Codec CodecFromExtension(std::string extension,
if (extension == ".pgx") return Codec::kPGX; if (extension == ".pgx") return Codec::kPGX;
if (extension == ".pbm") { if (extension == ".pam") return Codec::kPNM;
if (bits_per_sample != nullptr) *bits_per_sample = 1; if (extension == ".pnm") return Codec::kPNM;
return Codec::kPNM;
}
if (extension == ".pgm") return Codec::kPNM; if (extension == ".pgm") return Codec::kPNM;
if (extension == ".ppm") return Codec::kPNM; if (extension == ".ppm") return Codec::kPNM;
if (extension == ".pfm") { if (extension == ".pfm") {
@ -83,16 +56,13 @@ Codec CodecFromExtension(std::string extension,
if (extension == ".exr") return Codec::kEXR; if (extension == ".exr") return Codec::kEXR;
if (extension == ".psd") return Codec::kPSD;
return Codec::kUnknown; return Codec::kUnknown;
} }
Status DecodeBytes(const Span<const uint8_t> bytes, Status DecodeBytes(const Span<const uint8_t> bytes,
const ColorHints& color_hints, const ColorHints& color_hints,
const SizeConstraints& constraints, const SizeConstraints& constraints,
extras::PackedPixelFile* ppf, ThreadPool* pool, extras::PackedPixelFile* ppf, Codec* orig_codec) {
Codec* orig_codec) {
if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes"); if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes");
*ppf = extras::PackedPixelFile(); *ppf = extras::PackedPixelFile();
@ -123,7 +93,7 @@ Status DecodeBytes(const Span<const uint8_t> bytes,
} }
#endif #endif
#if JPEGXL_ENABLE_EXR #if JPEGXL_ENABLE_EXR
else if (DecodeImageEXR(bytes, color_hints, constraints, pool, ppf)) { else if (DecodeImageEXR(bytes, color_hints, constraints, ppf)) {
codec = Codec::kEXR; codec = Codec::kEXR;
} }
#endif #endif

View File

@ -33,8 +33,7 @@ enum class Codec : uint32_t {
kPGX, kPGX,
kJPG, kJPG,
kGIF, kGIF,
kEXR, kEXR
kPSD
}; };
static inline constexpr uint64_t EnumBits(Codec /*unused*/) { static inline constexpr uint64_t EnumBits(Codec /*unused*/) {
@ -49,13 +48,9 @@ static inline constexpr uint64_t EnumBits(Codec /*unused*/) {
#if JPEGXL_ENABLE_EXR #if JPEGXL_ENABLE_EXR
| MakeBit(Codec::kEXR) | MakeBit(Codec::kEXR)
#endif #endif
| MakeBit(Codec::kPSD); ;
} }
// Lower case ASCII including dot, e.g. ".png".
std::string ExtensionFromCodec(Codec codec, bool is_gray,
size_t bits_per_sample);
// If and only if extension is ".pfm", *bits_per_sample is updated to 32 so // If and only if extension is ".pfm", *bits_per_sample is updated to 32 so
// that Encode() would encode to PFM instead of PPM. // that Encode() would encode to PFM instead of PPM.
Codec CodecFromExtension(std::string extension, Codec CodecFromExtension(std::string extension,
@ -65,8 +60,7 @@ Codec CodecFromExtension(std::string extension,
// color_space_hint may specify the color space, otherwise, defaults to sRGB. // color_space_hint may specify the color space, otherwise, defaults to sRGB.
Status DecodeBytes(Span<const uint8_t> bytes, const ColorHints& color_hints, Status DecodeBytes(Span<const uint8_t> bytes, const ColorHints& color_hints,
const SizeConstraints& constraints, const SizeConstraints& constraints,
extras::PackedPixelFile* ppf, ThreadPool* pool = nullptr, extras::PackedPixelFile* ppf, Codec* orig_codec = nullptr);
Codec* orig_codec = nullptr);
} // namespace extras } // namespace extras
} // namespace jxl } // namespace jxl

View File

@ -29,18 +29,6 @@ using ExrInt64 = decltype(std::declval<OpenEXR::IStream>().tellg());
constexpr int kExrBitsPerSample = 16; constexpr int kExrBitsPerSample = 16;
constexpr int kExrAlphaBits = 16; constexpr int kExrAlphaBits = 16;
size_t GetNumThreads(ThreadPool* pool) {
size_t exr_num_threads = 1;
JXL_CHECK(RunOnPool(
pool, 0, 1,
[&](size_t num_threads) {
exr_num_threads = num_threads;
return true;
},
[&](uint32_t /* task */, size_t /*thread*/) {}, "DecodeImageEXRThreads"));
return exr_num_threads;
}
class InMemoryIStream : public OpenEXR::IStream { class InMemoryIStream : public OpenEXR::IStream {
public: public:
// The data pointed to by `bytes` must outlive the InMemoryIStream. // The data pointed to by `bytes` must outlive the InMemoryIStream.
@ -74,15 +62,8 @@ class InMemoryIStream : public OpenEXR::IStream {
} // namespace } // namespace
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints, Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
const SizeConstraints& constraints, ThreadPool* pool, const SizeConstraints& constraints,
PackedPixelFile* ppf) { PackedPixelFile* ppf) {
// Get the number of threads we should be using for OpenEXR.
// OpenEXR creates its own set of threads, independent from ours. `pool` is
// only used for converting from a buffer of OpenEXR::Rgba to Image3F.
// TODO(sboukortt): look into changing that with OpenEXR 2.3 which allows
// custom thread pools according to its changelog.
OpenEXR::setGlobalThreadCount(GetNumThreads(pool));
InMemoryIStream is(bytes); InMemoryIStream is(bytes);
#ifdef __EXCEPTIONS #ifdef __EXCEPTIONS
@ -149,27 +130,24 @@ Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
input_rows.data() - input.dataWindow().min.x - start_y * row_size, input_rows.data() - input.dataWindow().min.x - start_y * row_size,
/*xStride=*/1, /*yStride=*/row_size); /*xStride=*/1, /*yStride=*/row_size);
input.readPixels(start_y, end_y); input.readPixels(start_y, end_y);
JXL_RETURN_IF_ERROR(RunOnPool( for (int exr_y = start_y; exr_y <= end_y; ++exr_y) {
pool, start_y, end_y + 1, ThreadPool::NoInit, const int image_y = exr_y - input.displayWindow().min.y;
[&](const uint32_t exr_y, size_t /* thread */) { const OpenEXR::Rgba* const JXL_RESTRICT input_row =
const int image_y = exr_y - input.displayWindow().min.y; &input_rows[(exr_y - start_y) * row_size];
const OpenEXR::Rgba* const JXL_RESTRICT input_row = uint8_t* row = static_cast<uint8_t*>(frame.color.pixels()) +
&input_rows[(exr_y - start_y) * row_size]; frame.color.stride * image_y;
uint8_t* row = static_cast<uint8_t*>(frame.color.pixels()) + const uint32_t pixel_size =
frame.color.stride * image_y; (3 + (has_alpha ? 1 : 0)) * kExrBitsPerSample / 8;
const uint32_t pixel_size = for (int exr_x =
(3 + (has_alpha ? 1 : 0)) * kExrBitsPerSample / 8; std::max(input.dataWindow().min.x, input.displayWindow().min.x);
for (int exr_x = std::max(input.dataWindow().min.x, exr_x <=
input.displayWindow().min.x); std::min(input.dataWindow().max.x, input.displayWindow().max.x);
exr_x <= ++exr_x) {
std::min(input.dataWindow().max.x, input.displayWindow().max.x); const int image_x = exr_x - input.displayWindow().min.x;
++exr_x) { memcpy(row + image_x * pixel_size,
const int image_x = exr_x - input.displayWindow().min.x; input_row + (exr_x - input.dataWindow().min.x), pixel_size);
memcpy(row + image_x * pixel_size, }
input_row + (exr_x - input.dataWindow().min.x), pixel_size); }
}
},
"DecodeImageEXR"));
} }
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR; ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;

View File

@ -21,8 +21,7 @@ namespace extras {
// Decodes `bytes` into `ppf`. color_hints are ignored. // Decodes `bytes` into `ppf`. color_hints are ignored.
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints, Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
const SizeConstraints& constraints, ThreadPool* pool, const SizeConstraints& constraints, PackedPixelFile* ppf);
PackedPixelFile* ppf);
} // namespace extras } // namespace extras
} // namespace jxl } // namespace jxl

View File

@ -12,6 +12,7 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "jxl/codestream_header.h"
#include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/sanitizers.h" #include "lib/jxl/sanitizers.h"
@ -257,15 +258,26 @@ Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
GraphicsControlBlock gcb; GraphicsControlBlock gcb;
DGifSavedExtensionToGCB(gif.get(), i, &gcb); DGifSavedExtensionToGCB(gif.get(), i, &gcb);
msan::UnpoisonMemory(&gcb, sizeof(gcb)); msan::UnpoisonMemory(&gcb, sizeof(gcb));
bool is_full_size = total_rect.x0() == 0 && total_rect.y0() == 0 &&
total_rect.xsize() == canvas.color.xsize &&
total_rect.ysize() == canvas.color.ysize;
if (ppf->info.have_animation) { if (ppf->info.have_animation) {
frame->frame_info.duration = gcb.DelayTime; frame->frame_info.duration = gcb.DelayTime;
frame->x0 = total_rect.x0(); frame->frame_info.layer_info.have_crop = static_cast<int>(!is_full_size);
frame->y0 = total_rect.y0(); frame->frame_info.layer_info.crop_x0 = total_rect.x0();
frame->frame_info.layer_info.crop_y0 = total_rect.y0();
frame->frame_info.layer_info.xsize = frame->color.xsize;
frame->frame_info.layer_info.ysize = frame->color.ysize;
if (last_base_was_none) { if (last_base_was_none) {
replace = true; replace = true;
} }
frame->blend = !replace; frame->frame_info.layer_info.blend_info.blendmode =
replace ? JXL_BLEND_REPLACE : JXL_BLEND_BLEND;
// We always only reference at most the last frame
frame->frame_info.layer_info.blend_info.source =
last_base_was_none ? 0u : 1u;
frame->frame_info.layer_info.blend_info.clamp = 1;
frame->frame_info.layer_info.blend_info.alpha = 0;
// TODO(veluca): this could in principle be implemented. // TODO(veluca): this could in principle be implemented.
if (last_base_was_none && if (last_base_was_none &&
(total_rect.x0() != 0 || total_rect.y0() != 0 || (total_rect.x0() != 0 || total_rect.y0() != 0 ||
@ -278,14 +290,14 @@ Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
switch (gcb.DisposalMode) { switch (gcb.DisposalMode) {
case DISPOSE_DO_NOT: case DISPOSE_DO_NOT:
case DISPOSE_BACKGROUND: case DISPOSE_BACKGROUND:
frame->use_for_next_frame = true; frame->frame_info.layer_info.save_as_reference = 1u;
last_base_was_none = false; last_base_was_none = false;
break; break;
case DISPOSE_PREVIOUS: case DISPOSE_PREVIOUS:
frame->use_for_next_frame = false; frame->frame_info.layer_info.save_as_reference = 0u;
break; break;
default: default:
frame->use_for_next_frame = false; frame->frame_info.layer_info.save_as_reference = 0u;
last_base_was_none = true; last_base_was_none = true;
} }
} }

View File

@ -117,6 +117,8 @@ class Parser {
// 0xa, or 0xd 0xa. // 0xa, or 0xd 0xa.
JXL_RETURN_IF_ERROR(SkipLineBreak()); JXL_RETURN_IF_ERROR(SkipLineBreak());
// TODO(jon): could do up to 24-bit by converting the values to
// JXL_TYPE_FLOAT.
if (header->bits_per_sample > 16) { if (header->bits_per_sample > 16) {
return JXL_FAILURE("PGX: >16 bits not yet supported"); return JXL_FAILURE("PGX: >16 bits not yet supported");
} }
@ -127,9 +129,7 @@ class Parser {
} }
size_t numpixels = header->xsize * header->ysize; size_t numpixels = header->xsize * header->ysize;
size_t bytes_per_pixel = header->bits_per_sample <= 8 ? 1 size_t bytes_per_pixel = header->bits_per_sample <= 8 ? 1 : 2;
: header->bits_per_sample <= 16 ? 2
: 4;
if (pos_ + numpixels * bytes_per_pixel > end_) { if (pos_ + numpixels * bytes_per_pixel > end_) {
return JXL_FAILURE("PGX: data too small"); return JXL_FAILURE("PGX: data too small");
} }
@ -174,9 +174,7 @@ Status DecodeImagePGX(const Span<const uint8_t> bytes,
ppf->info.orientation = JXL_ORIENT_IDENTITY; ppf->info.orientation = JXL_ORIENT_IDENTITY;
JxlDataType data_type; JxlDataType data_type;
if (header.bits_per_sample > 16) { if (header.bits_per_sample > 8) {
data_type = JXL_TYPE_UINT32;
} else if (header.bits_per_sample > 8) {
data_type = JXL_TYPE_UINT16; data_type = JXL_TYPE_UINT16;
} else { } else {
data_type = JXL_TYPE_UINT8; data_type = JXL_TYPE_UINT8;

View File

@ -19,7 +19,6 @@ namespace {
struct HeaderPNM { struct HeaderPNM {
size_t xsize; size_t xsize;
size_t ysize; size_t ysize;
bool is_bit; // PBM
bool is_gray; // PGM bool is_gray; // PGM
bool has_alpha; // PAM bool has_alpha; // PAM
size_t bits_per_sample; size_t bits_per_sample;
@ -39,14 +38,9 @@ class Parser {
const uint8_t type = pos_[1]; const uint8_t type = pos_[1];
pos_ += 2; pos_ += 2;
header->is_bit = false;
switch (type) { switch (type) {
case '4': case '4':
header->is_bit = true; return JXL_FAILURE("pbm not supported");
header->is_gray = true;
header->bits_per_sample = 1;
return ParseHeaderPNM(header, pos);
case '5': case '5':
header->is_gray = true; header->is_gray = true;
@ -169,7 +163,7 @@ class Parser {
return true; return true;
} }
Status MatchString(const char* keyword) { Status MatchString(const char* keyword, bool skipws = true) {
const uint8_t* ppos = pos_; const uint8_t* ppos = pos_;
while (*keyword) { while (*keyword) {
if (ppos >= end_) return JXL_FAILURE("PAM: unexpected end of input"); if (ppos >= end_) return JXL_FAILURE("PAM: unexpected end of input");
@ -178,14 +172,18 @@ class Parser {
keyword++; keyword++;
} }
pos_ = ppos; pos_ = ppos;
JXL_RETURN_IF_ERROR(SkipWhitespace()); if (skipws) {
JXL_RETURN_IF_ERROR(SkipWhitespace());
} else {
JXL_RETURN_IF_ERROR(SkipSingleWhitespace());
}
return true; return true;
} }
Status ParseHeaderPAM(HeaderPNM* header, const uint8_t** pos) { Status ParseHeaderPAM(HeaderPNM* header, const uint8_t** pos) {
size_t depth = 3; size_t depth = 3;
size_t max_val = 255; size_t max_val = 255;
while (!MatchString("ENDHDR")) { while (!MatchString("ENDHDR", /*skipws=*/false)) {
JXL_RETURN_IF_ERROR(SkipWhitespace()); JXL_RETURN_IF_ERROR(SkipWhitespace());
if (MatchString("WIDTH")) { if (MatchString("WIDTH")) {
JXL_RETURN_IF_ERROR(ParseUnsigned(&header->xsize)); JXL_RETURN_IF_ERROR(ParseUnsigned(&header->xsize));
@ -231,7 +229,11 @@ class Parser {
if (max_val == 0 || max_val >= 65536) { if (max_val == 0 || max_val >= 65536) {
return JXL_FAILURE("PAM: bad MAXVAL"); return JXL_FAILURE("PAM: bad MAXVAL");
} }
header->bits_per_sample = CeilLog2Nonzero(max_val); // e.g When `max_val` is 1 , we want 1 bit:
header->bits_per_sample = FloorLog2Nonzero(max_val) + 1;
if ((1u << header->bits_per_sample) - 1 != max_val)
return JXL_FAILURE("PNM: unsupported MaxVal (expected 2^n - 1)");
// PAM does not pack bits as in PBM.
header->floating_point = false; header->floating_point = false;
header->big_endian = true; header->big_endian = true;
@ -246,15 +248,15 @@ class Parser {
JXL_RETURN_IF_ERROR(SkipWhitespace()); JXL_RETURN_IF_ERROR(SkipWhitespace());
JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize)); JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize));
if (!header->is_bit) { JXL_RETURN_IF_ERROR(SkipWhitespace());
JXL_RETURN_IF_ERROR(SkipWhitespace()); size_t max_val;
size_t max_val; JXL_RETURN_IF_ERROR(ParseUnsigned(&max_val));
JXL_RETURN_IF_ERROR(ParseUnsigned(&max_val)); if (max_val == 0 || max_val >= 65536) {
if (max_val == 0 || max_val >= 65536) { return JXL_FAILURE("PNM: bad MaxVal");
return JXL_FAILURE("PNM: bad MaxVal");
}
header->bits_per_sample = CeilLog2Nonzero(max_val);
} }
header->bits_per_sample = FloorLog2Nonzero(max_val) + 1;
if ((1u << header->bits_per_sample) - 1 != max_val)
return JXL_FAILURE("PNM: unsupported MaxVal (expected 2^n - 1)");
header->floating_point = false; header->floating_point = false;
header->big_endian = true; header->big_endian = true;
@ -276,7 +278,12 @@ class Parser {
// indicate endianness. All software expects nominal range 0..1. // indicate endianness. All software expects nominal range 0..1.
double scale; double scale;
JXL_RETURN_IF_ERROR(ParseSigned(&scale)); JXL_RETURN_IF_ERROR(ParseSigned(&scale));
header->big_endian = scale >= 0.0; if (scale == 0.0) {
return JXL_FAILURE("PFM: bad scale factor value.");
} else if (std::abs(scale) != 1.0) {
JXL_WARNING("PFM: Discarding non-unit scale factor");
}
header->big_endian = scale > 0.0;
header->bits_per_sample = 32; header->bits_per_sample = 32;
header->floating_point = true; header->floating_point = true;
@ -341,12 +348,8 @@ Status DecodeImagePNM(const Span<const uint8_t> bytes,
// There's no float16 pnm version. // There's no float16 pnm version.
data_type = JXL_TYPE_FLOAT; data_type = JXL_TYPE_FLOAT;
} else { } else {
if (header.bits_per_sample > 16) { if (header.bits_per_sample > 8) {
data_type = JXL_TYPE_UINT32;
} else if (header.bits_per_sample > 8) {
data_type = JXL_TYPE_UINT16; data_type = JXL_TYPE_UINT16;
} else if (header.is_bit) {
data_type = JXL_TYPE_BOOLEAN;
} else { } else {
data_type = JXL_TYPE_UINT8; data_type = JXL_TYPE_UINT8;
} }
@ -363,6 +366,7 @@ Status DecodeImagePNM(const Span<const uint8_t> bytes,
ppf->frames.emplace_back(header.xsize, header.ysize, format); ppf->frames.emplace_back(header.xsize, header.ysize, format);
auto* frame = &ppf->frames.back(); auto* frame = &ppf->frames.back();
frame->color.bitdepth_from_format = false;
frame->color.flipped_y = header.bits_per_sample == 32; // PFMs are flipped frame->color.flipped_y = header.bits_per_sample == 32; // PFMs are flipped
size_t pnm_remaining_size = bytes.data() + bytes.size() - pos; size_t pnm_remaining_size = bytes.data() + bytes.size() - pos;
if (pnm_remaining_size < frame->color.pixels_size) { if (pnm_remaining_size < frame->color.pixels_size) {

View File

@ -49,6 +49,7 @@
#include "lib/jxl/dec_external_image.h" #include "lib/jxl/dec_external_image.h"
#include "lib/jxl/enc_color_management.h" #include "lib/jxl/enc_color_management.h"
#include "lib/jxl/enc_image_bundle.h" #include "lib/jxl/enc_image_bundle.h"
#include "lib/jxl/exif.h"
#include "lib/jxl/frame_header.h" #include "lib/jxl/frame_header.h"
#include "lib/jxl/headers.h" #include "lib/jxl/headers.h"
#include "lib/jxl/image.h" #include "lib/jxl/image.h"
@ -70,7 +71,12 @@ class BlobsWriterPNG {
public: public:
static Status Encode(const Blobs& blobs, std::vector<std::string>* strings) { static Status Encode(const Blobs& blobs, std::vector<std::string>* strings) {
if (!blobs.exif.empty()) { if (!blobs.exif.empty()) {
JXL_RETURN_IF_ERROR(EncodeBase16("exif", blobs.exif, strings)); // PNG viewers typically ignore Exif orientation but not all of them do
// (and e.g. cjxl doesn't), so we overwrite the Exif orientation to the
// identity to avoid repeated orientation.
std::vector<uint8_t> exif = blobs.exif;
ResetExifOrientation(exif);
JXL_RETURN_IF_ERROR(EncodeBase16("exif", exif, strings));
} }
if (!blobs.iptc.empty()) { if (!blobs.iptc.empty()) {
JXL_RETURN_IF_ERROR(EncodeBase16("iptc", blobs.iptc, strings)); JXL_RETURN_IF_ERROR(EncodeBase16("iptc", blobs.iptc, strings));
@ -87,7 +93,8 @@ class BlobsWriterPNG {
return (nibble < 10) ? '0' + nibble : 'a' + nibble - 10; return (nibble < 10) ? '0' + nibble : 'a' + nibble - 10;
} }
static Status EncodeBase16(const std::string& type, const PaddedBytes& bytes, static Status EncodeBase16(const std::string& type,
const std::vector<uint8_t>& bytes,
std::vector<std::string>* strings) { std::vector<std::string>* strings) {
// Encoding: base16 with newline after 72 chars. // Encoding: base16 with newline after 72 chars.
const size_t base16_size = const size_t base16_size =
@ -158,12 +165,12 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
size_t stride = ib.oriented_xsize() * size_t stride = ib.oriented_xsize() *
DivCeil(c_desired.Channels() * bits_per_sample + alpha_bits, DivCeil(c_desired.Channels() * bits_per_sample + alpha_bits,
kBitsPerByte); kBitsPerByte);
PaddedBytes raw_bytes(stride * ib.oriented_ysize()); std::vector<uint8_t> raw_bytes(stride * ib.oriented_ysize());
JXL_RETURN_IF_ERROR(ConvertToExternal( JXL_RETURN_IF_ERROR(ConvertToExternal(
*transformed, bits_per_sample, /*float_out=*/false, *transformed, bits_per_sample, /*float_out=*/false,
c_desired.Channels() + (ib.HasAlpha() ? 1 : 0), JXL_BIG_ENDIAN, stride, c_desired.Channels() + (ib.HasAlpha() ? 1 : 0), JXL_BIG_ENDIAN, stride,
pool, raw_bytes.data(), raw_bytes.size(), /*out_callback=*/nullptr, pool, raw_bytes.data(), raw_bytes.size(),
/*out_opaque=*/nullptr, metadata.GetOrientation())); /*out_callback=*/{}, metadata.GetOrientation()));
int width = ib.oriented_xsize(); int width = ib.oriented_xsize();
int height = ib.oriented_ysize(); int height = ib.oriented_ysize();
@ -188,10 +195,10 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
std::vector<std::string> textstrings; std::vector<std::string> textstrings;
JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(io->blobs, &textstrings)); JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(io->blobs, &textstrings));
for (size_t i = 0; i + 1 < textstrings.size(); i += 2) { for (size_t kk = 0; kk + 1 < textstrings.size(); kk += 2) {
png_text text; png_text text;
text.key = const_cast<png_charp>(textstrings[i].c_str()); text.key = const_cast<png_charp>(textstrings[kk].c_str());
text.text = const_cast<png_charp>(textstrings[i + 1].c_str()); text.text = const_cast<png_charp>(textstrings[kk + 1].c_str());
text.compression = PNG_TEXT_COMPRESSION_zTXt; text.compression = PNG_TEXT_COMPRESSION_zTXt;
png_set_text(png_ptr, info_ptr, &text, 1); png_set_text(png_ptr, info_ptr, &text, 1);
} }
@ -241,7 +248,7 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
png_write_image(png_ptr, &rows[0]); png_write_image(png_ptr, &rows[0]);
png_write_flush(png_ptr); png_write_flush(png_ptr);
if (count > 0) { if (count > 0) {
PaddedBytes fdata(4); std::vector<uint8_t> fdata(4);
png_save_uint_32(fdata.data(), anim_chunks++); png_save_uint_32(fdata.data(), anim_chunks++);
size_t p = pos; size_t p = pos;
while (p + 8 < bytes->size()) { while (p + 8 < bytes->size()) {
@ -250,7 +257,8 @@ Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
JXL_ASSERT(bytes->operator[](p + 5) == 'D'); JXL_ASSERT(bytes->operator[](p + 5) == 'D');
JXL_ASSERT(bytes->operator[](p + 6) == 'A'); JXL_ASSERT(bytes->operator[](p + 6) == 'A');
JXL_ASSERT(bytes->operator[](p + 7) == 'T'); JXL_ASSERT(bytes->operator[](p + 7) == 'T');
fdata.append(bytes->data() + p + 8, bytes->data() + p + 8 + len); fdata.insert(fdata.end(), bytes->data() + p + 8,
bytes->data() + p + 8 + len);
p += len + 12; p += len + 12;
} }
bytes->resize(pos); bytes->resize(pos);

View File

@ -19,8 +19,10 @@
#include "lib/jxl/base/status.h" #include "lib/jxl/base/status.h"
#include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/common.h" #include "lib/jxl/common.h"
#include "lib/jxl/dec_external_image.h"
#include "lib/jxl/enc_color_management.h" #include "lib/jxl/enc_color_management.h"
#include "lib/jxl/enc_image_bundle.h" #include "lib/jxl/enc_image_bundle.h"
#include "lib/jxl/exif.h"
#include "lib/jxl/image.h" #include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h" #include "lib/jxl/image_bundle.h"
#include "lib/jxl/sanitizers.h" #include "lib/jxl/sanitizers.h"
@ -33,7 +35,6 @@ namespace extras {
namespace { namespace {
constexpr float kJPEGSampleMultiplier = MAXJSAMPLE;
constexpr unsigned char kICCSignature[12] = { constexpr unsigned char kICCSignature[12] = {
0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00}; 0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00};
constexpr int kICCMarker = JPEG_APP0 + 2; constexpr int kICCMarker = JPEG_APP0 + 2;
@ -43,13 +44,6 @@ constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
0x66, 0x00, 0x00}; 0x66, 0x00, 0x00};
constexpr int kExifMarker = JPEG_APP0 + 1; constexpr int kExifMarker = JPEG_APP0 + 1;
constexpr float kJPEGSampleMin = 0;
constexpr float kJPEGSampleMax = MAXJSAMPLE;
// TODO (jon): take orientation into account when writing jpeg output
// TODO (jon): write Exif blob also in sjpeg encoding
// TODO (jon): overwrite orientation in Exif blob to avoid double orientation
void WriteICCProfile(jpeg_compress_struct* const cinfo, void WriteICCProfile(jpeg_compress_struct* const cinfo,
const PaddedBytes& icc) { const PaddedBytes& icc) {
constexpr size_t kMaxIccBytesInMarker = constexpr size_t kMaxIccBytesInMarker =
@ -73,15 +67,15 @@ void WriteICCProfile(jpeg_compress_struct* const cinfo,
} }
} }
} }
void WriteExif(jpeg_compress_struct* const cinfo, const PaddedBytes& exif) { void WriteExif(jpeg_compress_struct* const cinfo,
if (exif.size() < 4) return; const std::vector<uint8_t>& exif) {
jpeg_write_m_header( jpeg_write_m_header(
cinfo, kExifMarker, cinfo, kExifMarker,
static_cast<unsigned int>(exif.size() - 4 + sizeof kExifSignature)); static_cast<unsigned int>(exif.size() + sizeof kExifSignature));
for (const unsigned char c : kExifSignature) { for (const unsigned char c : kExifSignature) {
jpeg_write_m_byte(cinfo, c); jpeg_write_m_byte(cinfo, c);
} }
for (size_t i = 4; i < exif.size(); ++i) { for (size_t i = 0; i < exif.size(); ++i) {
jpeg_write_m_byte(cinfo, exif[i]); jpeg_write_m_byte(cinfo, exif[i]);
} }
} }
@ -115,8 +109,8 @@ Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
unsigned char* buffer = nullptr; unsigned char* buffer = nullptr;
unsigned long size = 0; unsigned long size = 0;
jpeg_mem_dest(&cinfo, &buffer, &size); jpeg_mem_dest(&cinfo, &buffer, &size);
cinfo.image_width = ib->xsize(); cinfo.image_width = ib->oriented_xsize();
cinfo.image_height = ib->ysize(); cinfo.image_height = ib->oriented_ysize();
if (ib->IsGray()) { if (ib->IsGray()) {
cinfo.input_components = 1; cinfo.input_components = 1;
cinfo.in_color_space = JCS_GRAYSCALE; cinfo.in_color_space = JCS_GRAYSCALE;
@ -134,27 +128,26 @@ Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
if (!ib->IsSRGB()) { if (!ib->IsSRGB()) {
WriteICCProfile(&cinfo, ib->c_current().ICC()); WriteICCProfile(&cinfo, ib->c_current().ICC());
} }
WriteExif(&cinfo, io->blobs.exif); if (!io->blobs.exif.empty()) {
std::vector<uint8_t> exif = io->blobs.exif;
ResetExifOrientation(exif);
WriteExif(&cinfo, exif);
}
if (cinfo.input_components > 3 || cinfo.input_components < 0) if (cinfo.input_components > 3 || cinfo.input_components < 0)
return JXL_FAILURE("invalid numbers of components"); return JXL_FAILURE("invalid numbers of components");
std::unique_ptr<JSAMPLE[]> row( size_t stride =
new JSAMPLE[cinfo.input_components * cinfo.image_width]); ib->oriented_xsize() * cinfo.input_components * sizeof(JSAMPLE);
for (size_t y = 0; y < ib->ysize(); ++y) { PaddedBytes raw_bytes(stride * ib->oriented_ysize());
const float* const JXL_RESTRICT input_row[3] = { JXL_RETURN_IF_ERROR(ConvertToExternal(
ib->color().ConstPlaneRow(0, y), ib->color().ConstPlaneRow(1, y), *ib, BITS_IN_JSAMPLE, /*float_out=*/false, cinfo.input_components,
ib->color().ConstPlaneRow(2, y)}; JXL_BIG_ENDIAN, stride, nullptr, raw_bytes.data(), raw_bytes.size(),
for (size_t x = 0; x < ib->xsize(); ++x) { /*out_callback=*/{}, ib->metadata()->GetOrientation()));
for (size_t c = 0; c < static_cast<size_t>(cinfo.input_components); ++c) {
JXL_RETURN_IF_ERROR(c < 3); for (size_t y = 0; y < ib->oriented_ysize(); ++y) {
row[cinfo.input_components * x + c] = static_cast<JSAMPLE>( JSAMPROW row[] = {raw_bytes.data() + y * stride};
std::max(std::min(kJPEGSampleMultiplier * input_row[c][x] + .5f,
kJPEGSampleMax), jpeg_write_scanlines(&cinfo, row, 1);
kJPEGSampleMin));
}
}
JSAMPROW rows[] = {row.get()};
jpeg_write_scanlines(&cinfo, rows, 1);
} }
jpeg_finish_compress(&cinfo); jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo); jpeg_destroy_compress(&cinfo);
@ -167,7 +160,8 @@ Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
return true; return true;
} }
Status EncodeWithSJpeg(const ImageBundle* ib, size_t quality, Status EncodeWithSJpeg(const ImageBundle* ib, const CodecInOut* io,
size_t quality,
const YCbCrChromaSubsampling& chroma_subsampling, const YCbCrChromaSubsampling& chroma_subsampling,
PaddedBytes* bytes) { PaddedBytes* bytes) {
#if !JPEGXL_ENABLE_SJPEG #if !JPEGXL_ENABLE_SJPEG
@ -178,6 +172,11 @@ Status EncodeWithSJpeg(const ImageBundle* ib, size_t quality,
param.iccp.assign(ib->metadata()->color_encoding.ICC().begin(), param.iccp.assign(ib->metadata()->color_encoding.ICC().begin(),
ib->metadata()->color_encoding.ICC().end()); ib->metadata()->color_encoding.ICC().end());
} }
std::vector<uint8_t> exif = io->blobs.exif;
if (!exif.empty()) {
ResetExifOrientation(exif);
param.exif.assign(exif.begin(), exif.end());
}
if (chroma_subsampling.Is444()) { if (chroma_subsampling.Is444()) {
param.yuv_mode = SJPEG_YUV_444; param.yuv_mode = SJPEG_YUV_444;
} else if (chroma_subsampling.Is420()) { } else if (chroma_subsampling.Is420()) {
@ -185,24 +184,17 @@ Status EncodeWithSJpeg(const ImageBundle* ib, size_t quality,
} else { } else {
return JXL_FAILURE("sjpeg does not support this chroma subsampling mode"); return JXL_FAILURE("sjpeg does not support this chroma subsampling mode");
} }
std::vector<uint8_t> rgb; size_t stride = ib->oriented_xsize() * 3;
rgb.reserve(ib->xsize() * ib->ysize() * 3); PaddedBytes rgb(ib->xsize() * ib->ysize() * 3);
for (size_t y = 0; y < ib->ysize(); ++y) { JXL_RETURN_IF_ERROR(
const float* const rows[] = { ConvertToExternal(*ib, 8, /*float_out=*/false, 3, JXL_BIG_ENDIAN, stride,
ib->color().ConstPlaneRow(0, y), nullptr, rgb.data(), rgb.size(),
ib->color().ConstPlaneRow(1, y), /*out_callback=*/{}, ib->metadata()->GetOrientation()));
ib->color().ConstPlaneRow(2, y),
};
for (size_t x = 0; x < ib->xsize(); ++x) {
for (const float* const row : rows) {
rgb.push_back(static_cast<uint8_t>(
std::max(0.f, std::min(255.f, roundf(255.f * row[x])))));
}
}
}
std::string output; std::string output;
JXL_RETURN_IF_ERROR(sjpeg::Encode(rgb.data(), ib->xsize(), ib->ysize(), JXL_RETURN_IF_ERROR(sjpeg::Encode(rgb.data(), ib->oriented_xsize(),
ib->xsize() * 3, param, &output)); ib->oriented_ysize(), stride, param,
&output));
bytes->assign( bytes->assign(
reinterpret_cast<const uint8_t*>(output.data()), reinterpret_cast<const uint8_t*>(output.data()),
reinterpret_cast<const uint8_t*>(output.data() + output.size())); reinterpret_cast<const uint8_t*>(output.data() + output.size()));
@ -234,7 +226,7 @@ Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
break; break;
case JpegEncoder::kSJpeg: case JpegEncoder::kSJpeg:
JXL_RETURN_IF_ERROR( JXL_RETURN_IF_ERROR(
EncodeWithSJpeg(ib, quality, chroma_subsampling, bytes)); EncodeWithSJpeg(ib, io, quality, chroma_subsampling, bytes));
break; break;
default: default:
return JXL_FAILURE("tried to use an unknown JPEG encoder"); return JXL_FAILURE("tried to use an unknown JPEG encoder");

View File

@ -75,8 +75,8 @@ Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
ConvertToExternal(*transformed, bits_per_sample, ConvertToExternal(*transformed, bits_per_sample,
/*float_out=*/false, /*float_out=*/false,
/*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool, /*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool,
pixels.data(), pixels.size(), /*out_callback=*/nullptr, pixels.data(), pixels.size(),
/*out_opaque=*/nullptr, metadata.GetOrientation())); /*out_callback=*/{}, metadata.GetOrientation()));
char header[kMaxHeaderSize]; char header[kMaxHeaderSize];
int header_size = 0; int header_size = 0;

View File

@ -35,14 +35,23 @@ constexpr size_t kMaxHeaderSize = 200;
Status EncodeHeader(const PackedPixelFile& ppf, const size_t bits_per_sample, Status EncodeHeader(const PackedPixelFile& ppf, const size_t bits_per_sample,
const bool little_endian, char* header, const bool little_endian, char* header,
int* JXL_RESTRICT chars_written) { int* JXL_RESTRICT chars_written) {
if (ppf.info.alpha_bits > 0) return JXL_FAILURE("PNM: can't store alpha");
bool is_gray = ppf.info.num_color_channels <= 2; bool is_gray = ppf.info.num_color_channels <= 2;
size_t oriented_xsize = size_t oriented_xsize =
ppf.info.orientation <= 4 ? ppf.info.xsize : ppf.info.ysize; ppf.info.orientation <= 4 ? ppf.info.xsize : ppf.info.ysize;
size_t oriented_ysize = size_t oriented_ysize =
ppf.info.orientation <= 4 ? ppf.info.ysize : ppf.info.xsize; ppf.info.orientation <= 4 ? ppf.info.ysize : ppf.info.xsize;
if (ppf.info.alpha_bits > 0) { // PAM
if (bits_per_sample == 32) { // PFM if (bits_per_sample > 16) return JXL_FAILURE("PNM cannot have > 16 bits");
const uint32_t max_val = (1U << bits_per_sample) - 1;
*chars_written =
snprintf(header, kMaxHeaderSize,
"P7\nWIDTH %" PRIuS "\nHEIGHT %" PRIuS
"\nDEPTH %u\nMAXVAL %u\nTUPLTYPE %s\nENDHDR\n",
oriented_xsize, oriented_ysize, is_gray ? 2 : 4, max_val,
is_gray ? "GRAYSCALE_ALPHA" : "RGB_ALPHA");
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
kMaxHeaderSize);
} else if (bits_per_sample == 32) { // PFM
const char type = is_gray ? 'f' : 'F'; const char type = is_gray ? 'f' : 'F';
const double scale = little_endian ? -1.0 : 1.0; const double scale = little_endian ? -1.0 : 1.0;
*chars_written = *chars_written =
@ -50,15 +59,6 @@ Status EncodeHeader(const PackedPixelFile& ppf, const size_t bits_per_sample,
type, oriented_xsize, oriented_ysize, scale); type, oriented_xsize, oriented_ysize, scale);
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) < JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
kMaxHeaderSize); kMaxHeaderSize);
} else if (bits_per_sample == 1) { // PBM
if (is_gray) {
return JXL_FAILURE("Cannot encode color as PBM");
}
*chars_written =
snprintf(header, kMaxHeaderSize, "P4\n%" PRIuS " %" PRIuS "\n",
oriented_xsize, oriented_ysize);
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
kMaxHeaderSize);
} else { // PGM/PPM } else { // PGM/PPM
const uint32_t max_val = (1U << bits_per_sample) - 1; const uint32_t max_val = (1U << bits_per_sample) - 1;
if (max_val >= 65536) return JXL_FAILURE("PNM cannot have > 16 bits"); if (max_val >= 65536) return JXL_FAILURE("PNM cannot have > 16 bits");
@ -100,7 +100,7 @@ Status EncodeImagePNM(const PackedPixelFile& ppf, size_t bits_per_sample,
ThreadPool* pool, size_t frame_index, ThreadPool* pool, size_t frame_index,
std::vector<uint8_t>* bytes) { std::vector<uint8_t>* bytes) {
const bool floating_point = bits_per_sample > 16; const bool floating_point = bits_per_sample > 16;
// Choose native for PFM; PGM/PPM require big-endian (N/A for PBM) // Choose native for PFM; PGM/PPM require big-endian
const JxlEndianness endianness = const JxlEndianness endianness =
floating_point ? JXL_NATIVE_ENDIAN : JXL_BIG_ENDIAN; floating_point ? JXL_NATIVE_ENDIAN : JXL_BIG_ENDIAN;
if (!ppf.metadata.exif.empty() || !ppf.metadata.iptc.empty() || if (!ppf.metadata.exif.empty() || !ppf.metadata.iptc.empty() ||

View File

@ -64,6 +64,12 @@ class PackedImage {
// Whether the y coordinate is flipped (y=0 is the last row). // Whether the y coordinate is flipped (y=0 is the last row).
bool flipped_y = false; bool flipped_y = false;
// Whether the range is determined by format or by JxlBasicInfo
// e.g. if format is UINT16 and JxlBasicInfo bits_per_sample is 10,
// then if bitdepth_from_format == true, the range is 0..65535
// while if bitdepth_from_format == false, the range is 0..1023.
bool bitdepth_from_format = true;
// The number of bytes per row. // The number of bytes per row.
size_t stride; size_t stride;
@ -73,21 +79,17 @@ class PackedImage {
static size_t BitsPerChannel(JxlDataType data_type) { static size_t BitsPerChannel(JxlDataType data_type) {
switch (data_type) { switch (data_type) {
case JXL_TYPE_BOOLEAN:
return 1;
case JXL_TYPE_UINT8: case JXL_TYPE_UINT8:
return 8; return 8;
case JXL_TYPE_UINT16: case JXL_TYPE_UINT16:
return 16; return 16;
case JXL_TYPE_UINT32:
return 32;
case JXL_TYPE_FLOAT: case JXL_TYPE_FLOAT:
return 32; return 32;
case JXL_TYPE_FLOAT16: case JXL_TYPE_FLOAT16:
return 16; return 16;
// No default, give compiler error if new type not handled. default:
JXL_ABORT("Unhandled JxlDataType");
} }
return 0; // Indicate invalid data type.
} }
private: private:
@ -116,16 +118,6 @@ class PackedFrame {
JxlFrameHeader frame_info = {}; JxlFrameHeader frame_info = {};
std::string name; std::string name;
// Offset of the frame in the image.
// TODO(deymo): Add support in the API for this.
size_t x0 = 0;
size_t y0 = 0;
// Whether this frame should be blended with the previous one.
// TODO(deymo): Maybe add support for this in the API.
bool blend = false;
bool use_for_next_frame = false;
// The pixel data for the color (or grayscale) channels. // The pixel data for the color (or grayscale) channels.
PackedImage color; PackedImage color;
// Extra channel image data. // Extra channel image data.

View File

@ -65,21 +65,25 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
if (!io->metadata.m.color_encoding.SetICC(std::move(icc))) { if (!io->metadata.m.color_encoding.SetICC(std::move(icc))) {
fprintf(stderr, "Warning: error setting ICC profile, assuming SRGB"); fprintf(stderr, "Warning: error setting ICC profile, assuming SRGB");
io->metadata.m.color_encoding = ColorEncoding::SRGB(is_gray); io->metadata.m.color_encoding = ColorEncoding::SRGB(is_gray);
} else {
if (io->metadata.m.color_encoding.IsGray() != is_gray) {
// E.g. JPG image has 3 channels, but gray ICC.
return JXL_FAILURE("Embedded ICC does not match image color type");
}
} }
} else { } else {
JXL_RETURN_IF_ERROR(ConvertExternalToInternalColorEncoding( JXL_RETURN_IF_ERROR(ConvertExternalToInternalColorEncoding(
ppf.color_encoding, &io->metadata.m.color_encoding)); ppf.color_encoding, &io->metadata.m.color_encoding));
if (io->metadata.m.color_encoding.ICC().empty()) {
return JXL_FAILURE("Failed to serialize ICC");
}
} }
// Convert the extra blobs // Convert the extra blobs
io->blobs.exif.clear(); io->blobs.exif = ppf.metadata.exif;
io->blobs.exif.append(ppf.metadata.exif); io->blobs.iptc = ppf.metadata.iptc;
io->blobs.iptc.clear(); io->blobs.jumbf = ppf.metadata.jumbf;
io->blobs.iptc.append(ppf.metadata.iptc); io->blobs.xmp = ppf.metadata.xmp;
io->blobs.jumbf.clear();
io->blobs.jumbf.append(ppf.metadata.jumbf);
io->blobs.xmp.clear();
io->blobs.xmp.append(ppf.metadata.xmp);
// Append all other extra channels. // Append all other extra channels.
for (const PackedPixelFile::PackedExtraChannel& info : for (const PackedPixelFile::PackedExtraChannel& info :
@ -107,7 +111,9 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
for (const auto& frame : ppf.frames) { for (const auto& frame : ppf.frames) {
JXL_ASSERT(frame.color.pixels() != nullptr); JXL_ASSERT(frame.color.pixels() != nullptr);
size_t frame_bits_per_sample = size_t frame_bits_per_sample =
frame.color.BitsPerChannel(frame.color.format.data_type); (frame.color.bitdepth_from_format
? frame.color.BitsPerChannel(frame.color.format.data_type)
: ppf.info.bits_per_sample);
JXL_ASSERT(frame_bits_per_sample != 0); JXL_ASSERT(frame_bits_per_sample != 0);
// It is ok for the frame.color.format.num_channels to not match the // It is ok for the frame.color.format.num_channels to not match the
// number of channels on the image. // number of channels on the image.
@ -117,20 +123,21 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
const Span<const uint8_t> span( const Span<const uint8_t> span(
static_cast<const uint8_t*>(frame.color.pixels()), static_cast<const uint8_t*>(frame.color.pixels()),
frame.color.pixels_size); frame.color.pixels_size);
Rect frame_rect = Rect frame_rect = Rect(frame.frame_info.layer_info.crop_x0,
Rect(frame.x0, frame.y0, frame.color.xsize, frame.color.ysize); frame.frame_info.layer_info.crop_y0,
frame.frame_info.layer_info.xsize,
frame.frame_info.layer_info.ysize);
JXL_ASSERT(frame_rect.IsInside(Rect(0, 0, ppf.info.xsize, ppf.info.ysize))); JXL_ASSERT(frame_rect.IsInside(Rect(0, 0, ppf.info.xsize, ppf.info.ysize)));
ImageBundle bundle(&io->metadata.m); ImageBundle bundle(&io->metadata.m);
if (ppf.info.have_animation) { if (ppf.info.have_animation) {
bundle.duration = frame.frame_info.duration; bundle.duration = frame.frame_info.duration;
bundle.blend = frame.blend; bundle.blend = frame.frame_info.layer_info.blend_info.blendmode > 0;
bundle.use_for_next_frame = frame.use_for_next_frame; bundle.use_for_next_frame =
frame.frame_info.layer_info.save_as_reference > 0;
bundle.origin.x0 = frame.frame_info.layer_info.crop_x0;
bundle.origin.y0 = frame.frame_info.layer_info.crop_y0;
} }
bundle.name = frame.name; // frame.frame_info.name_length is ignored here. bundle.name = frame.name; // frame.frame_info.name_length is ignored here.
bundle.origin.x0 = frame.x0;
bundle.origin.y0 = frame.y0;
JXL_ASSERT(io->metadata.m.color_encoding.IsGray() == JXL_ASSERT(io->metadata.m.color_encoding.IsGray() ==
(frame.color.format.num_channels <= 2)); (frame.color.format.num_channels <= 2));
@ -144,11 +151,13 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
/*flipped_y=*/frame.color.flipped_y, pool, &bundle, /*flipped_y=*/frame.color.flipped_y, pool, &bundle,
/*float_in=*/float_in, /*align=*/0)); /*float_in=*/float_in, /*align=*/0));
for (const auto& ppf_ec : frame.extra_channels) { bundle.extra_channels().resize(io->metadata.m.extra_channel_info.size());
bundle.extra_channels().emplace_back(ppf_ec.xsize, ppf_ec.ysize); for (size_t i = 0; i < frame.extra_channels.size(); i++) {
const auto& ppf_ec = frame.extra_channels[i];
bundle.extra_channels()[i] = ImageF(ppf_ec.xsize, ppf_ec.ysize);
JXL_CHECK(BufferToImageF(ppf_ec.format, ppf_ec.xsize, ppf_ec.ysize, JXL_CHECK(BufferToImageF(ppf_ec.format, ppf_ec.xsize, ppf_ec.ysize,
ppf_ec.pixels(), ppf_ec.pixels_size, pool, ppf_ec.pixels(), ppf_ec.pixels_size, pool,
&bundle.extra_channels().back())); &bundle.extra_channels()[i]));
} }
io->frames.push_back(std::move(bundle)); io->frames.push_back(std::move(bundle));
@ -218,10 +227,10 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
} }
// Convert the extra blobs // Convert the extra blobs
ppf->metadata.exif.assign(io.blobs.exif.begin(), io.blobs.exif.end()); ppf->metadata.exif = io.blobs.exif;
ppf->metadata.iptc.assign(io.blobs.iptc.begin(), io.blobs.iptc.end()); ppf->metadata.iptc = io.blobs.iptc;
ppf->metadata.jumbf.assign(io.blobs.jumbf.begin(), io.blobs.jumbf.end()); ppf->metadata.jumbf = io.blobs.jumbf;
ppf->metadata.xmp.assign(io.blobs.xmp.begin(), io.blobs.xmp.end()); ppf->metadata.xmp = io.blobs.xmp;
const bool float_out = pixel_format.data_type == JXL_TYPE_FLOAT || const bool float_out = pixel_format.data_type == JXL_TYPE_FLOAT ||
pixel_format.data_type == JXL_TYPE_FLOAT16; pixel_format.data_type == JXL_TYPE_FLOAT16;
// Convert the pixels // Convert the pixels
@ -240,8 +249,11 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
PackedFrame packed_frame(frame.oriented_xsize(), frame.oriented_ysize(), PackedFrame packed_frame(frame.oriented_xsize(), frame.oriented_ysize(),
format); format);
packed_frame.color.bitdepth_from_format = float_out;
const size_t bits_per_sample = const size_t bits_per_sample =
packed_frame.color.BitsPerChannel(pixel_format.data_type); packed_frame.color.bitdepth_from_format
? packed_frame.color.BitsPerChannel(pixel_format.data_type)
: ppf->info.bits_per_sample;
packed_frame.name = frame.name; packed_frame.name = frame.name;
packed_frame.frame_info.name_length = frame.name.size(); packed_frame.frame_info.name_length = frame.name.size();
// Color transform // Color transform
@ -264,8 +276,7 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
format.endianness, format.endianness,
/* stride_out=*/packed_frame.color.stride, pool, /* stride_out=*/packed_frame.color.stride, pool,
packed_frame.color.pixels(), packed_frame.color.pixels_size, packed_frame.color.pixels(), packed_frame.color.pixels_size,
/*out_callback=*/nullptr, /*out_opaque=*/nullptr, /*out_callback=*/{}, frame.metadata()->GetOrientation()));
frame.metadata()->GetOrientation()));
// TODO(firsching): Convert the extra channels, beside one potential alpha // TODO(firsching): Convert the extra channels, beside one potential alpha
// channel. FIXME! // channel. FIXME!

View File

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

View File

@ -917,6 +917,56 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetImageOutBuffer(
typedef void (*JxlImageOutCallback)(void* opaque, size_t x, size_t y, typedef void (*JxlImageOutCallback)(void* opaque, size_t x, size_t y,
size_t num_pixels, const void* pixels); size_t num_pixels, const void* pixels);
/**
* Initialization callback for @c JxlDecoderSetMultithreadedImageOutCallback.
*
* @see JxlDecoderSetMultithreadedImageOutCallback
*
* @param init_opaque optional user data, as given to
* @c JxlDecoderSetMultithreadedImageOutCallback.
* @param num_threads maximum number of threads that will call the @c run
* callback concurrently.
* @param num_pixels_per_thread maximum number of pixels that will be passed in
* one call to @c run.
* @return a pointer to data that will be passed to the @c run callback, or
* @c NULL if initialization failed.
*/
typedef void* (*JxlImageOutInitCallback)(void* init_opaque, size_t num_threads,
size_t num_pixels_per_thread);
/**
* Worker callback for @c JxlDecoderSetMultithreadedImageOutCallback.
*
* @see JxlDecoderSetMultithreadedImageOutCallback
*
* @param run_opaque user data returned by the @c init callback.
* @param thread_id number in `[0, num_threads)` identifying the thread of the
* current invocation of the callback.
* @param x horizontal position of the first (leftmost) pixel of the pixel data.
* @param y vertical position of the pixel data.
* @param num_pixels number of pixels in the pixel data. May be less than the
* full @c xsize of the image, and will be at most equal to the @c
* num_pixels_per_thread that was passed to @c init.
* @param pixels pixel data as a horizontal stripe, in the format passed to @c
* JxlDecoderSetMultithreadedImageOutCallback. The data pointed to remains owned
* by the caller and is only guaranteed to outlive the current callback
* invocation.
*/
typedef void (*JxlImageOutRunCallback)(void* run_opaque, size_t thread_id,
size_t x, size_t y, size_t num_pixels,
const void* pixels);
/**
* Destruction callback for @c JxlDecoderSetMultithreadedImageOutCallback,
* called after all invocations of the @c run callback to perform any
* appropriate clean-up of the @c run_opaque data returned by @c init.
*
* @see JxlDecoderSetMultithreadedImageOutCallback
*
* @param run_opaque user data returned by the @c init callback.
*/
typedef void (*JxlImageOutDestroyCallback)(void* run_opaque);
/** /**
* Sets pixel output callback. This is an alternative to * Sets pixel output callback. This is an alternative to
* JxlDecoderSetImageOutBuffer. This can be set when the JXL_DEC_FRAME event * JxlDecoderSetImageOutBuffer. This can be set when the JXL_DEC_FRAME event
@ -950,8 +1000,8 @@ typedef void (*JxlImageOutCallback)(void* opaque, size_t x, size_t y,
* guaranteed). * guaranteed).
* *
* @param dec decoder object * @param dec decoder object
* @param format format of the pixels. Object owned by user and its contents * @param format format of the pixels. Object owned by user; its contents are
* are copied internally. * copied internally.
* @param callback the callback function receiving partial scanlines of pixel * @param callback the callback function receiving partial scanlines of pixel
* data. * data.
* @param opaque optional user data, which will be passed on to the callback, * @param opaque optional user data, which will be passed on to the callback,
@ -963,6 +1013,30 @@ JXL_EXPORT JxlDecoderStatus
JxlDecoderSetImageOutCallback(JxlDecoder* dec, const JxlPixelFormat* format, JxlDecoderSetImageOutCallback(JxlDecoder* dec, const JxlPixelFormat* format,
JxlImageOutCallback callback, void* opaque); JxlImageOutCallback callback, void* opaque);
/** Similar to @c JxlDecoderSetImageOutCallback except that the callback is
* allowed an initialization phase during which it is informed of how many
* threads will call it concurrently, and those calls are further informed of
* which thread they are occurring in.
*
* @param dec decoder object
* @param format format of the pixels. Object owned by user; its contents are
* copied internally.
* @param init_callback initialization callback.
* @param run_callback the callback function receiving partial scanlines of
* pixel data.
* @param destroy_callback clean-up callback invoked after all calls to @c
* run_callback. May be NULL if no clean-up is necessary.
* @param init_opaque optional user data passed to @c init_callback, may be NULL
* (unlike the return value from @c init_callback which may only be NULL if
* initialization failed).
* @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as @c
* JxlDecoderSetImageOutBuffer having already been called.
*/
JXL_EXPORT JxlDecoderStatus JxlDecoderSetMultithreadedImageOutCallback(
JxlDecoder* dec, const JxlPixelFormat* format,
JxlImageOutInitCallback init_callback, JxlImageOutRunCallback run_callback,
JxlImageOutDestroyCallback destroy_callback, void* init_opaque);
/** /**
* Returns the minimum size in bytes of an extra channel pixel buffer for the * Returns the minimum size in bytes of an extra channel pixel buffer for the
* given format. This is the buffer for JxlDecoderSetExtraChannelBuffer. * given format. This is the buffer for JxlDecoderSetExtraChannelBuffer.
@ -1123,10 +1197,60 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetDecompressBoxes(JxlDecoder* dec,
* box, this will return "brob" if the decompressed argument is JXL_FALSE, or * box, this will return "brob" if the decompressed argument is JXL_FALSE, or
* the underlying box type if the decompressed argument is JXL_TRUE. * the underlying box type if the decompressed argument is JXL_TRUE.
* *
* The following box types are currently described in ISO/IEC 18181-2:
* - "Exif": a box with EXIF metadata. Starts with a 4-byte tiff header
* offset (big-endian uint32) that indicates the start of the actual EXIF data
* (which starts with a tiff header). Usually the offset will be zero and the
* EXIF data starts immediately after the offset field. The Exif orientation
* should be ignored by applications; the JPEG XL codestream orientation takes
* precedence and libjxl will by default apply the correct orientation
* automatically (see JxlDecoderSetKeepOrientation).
* - "xml ": a box with XML data, in particular XMP metadata.
* - "jumb": a JUMBF superbox (JPEG Universal Metadata Box Format, ISO/IEC
* 19566-5).
* - "JXL ": mandatory signature box, must come first, 12 bytes long including
* the box header
* - "ftyp": a second mandatory signature box, must come second, 20 bytes long
* including the box header
* - "jxll": a JXL level box. This indicates if the codestream is level 5 or
* level 10 compatible. If not present, it is level 5. Level 10 allows more
* features such as very high image resolution and bit-depths above 16 bits
* per channel. Added automatically by the encoder when
* JxlEncoderSetCodestreamLevel is used
* - "jxlc": a box with the image codestream, in case the codestream is not
* split across multiple boxes. The codestream contains the JPEG XL image
* itself, including the basic info such as image dimensions, ICC color
* profile, and all the pixel data of all the image frames.
* - "jxlp": a codestream box in case it is split across multiple boxes.
* The contents are the same as in case of a jxlc box, when concatenated.
* - "brob": a Brotli-compressed box, which otherwise represents an existing
* type of box such as Exif or "xml ". When JxlDecoderSetDecompressBoxes is
* set to JXL_TRUE, these boxes will be transparently decompressed by the
* decoder.
* - "jxli": frame index box, can list the keyframes in case of a JXL animation,
* allowing the decoder to jump to individual frames more efficiently.
* - "jbrd": JPEG reconstruction box, contains the information required to
* byte-for-byte losslessly recontruct a JPEG-1 image. The JPEG DCT
* coefficients (pixel content) themselves as well as the ICC profile are
* encoded in the JXL codestream (jxlc or jxlp) itself. EXIF, XMP and JUMBF
* metadata is encoded in the corresponding boxes. The jbrd box itself
* contains information such as the remaining app markers of the JPEG-1 file
* and everything else required to fit the information together into the
* exact original JPEG file.
*
* Other application-specific boxes can exist. Their typename should not begin
* with "jxl" or "JXL" or conflict with other existing typenames.
*
* The signature, jxl* and jbrd boxes are processed by the decoder and would
* typically be ignored by applications. The typical way to use this function is
* to check if an encountered box contains metadata that the application is
* interested in (e.g. EXIF or XMP metadata), in order to conditionally set a
* box buffer.
*
* @param dec decoder object * @param dec decoder object
* @param type buffer to copy the type into * @param type buffer to copy the type into
* @param decompressed which box type to get: JXL_TRUE to get the raw box type, * @param decompressed which box type to get: JXL_FALSE to get the raw box type,
* which can be "brob", JXL_FALSE, get the underlying box type. * which can be "brob", JXL_TRUE, get the underlying box type.
* @return JXL_DEC_SUCCESS if the value is available, JXL_DEC_ERROR if not, for * @return JXL_DEC_SUCCESS if the value is available, JXL_DEC_ERROR if not, for
* example the JXL file does not use the container format. * example the JXL file does not use the container format.
*/ */

View File

@ -283,6 +283,13 @@ typedef enum {
*/ */
JXL_ENC_FRAME_INDEX_BOX = 31, JXL_ENC_FRAME_INDEX_BOX = 31,
/** Sets brotli encode effort for use in JPEG recompression and compressed
* metadata boxes (brob). Can be -1 (default) or 0 (fastest) to 11 (slowest).
* Default is based on the general encode effort in case of JPEG
* recompression, and 4 for brob boxes.
*/
JXL_ENC_FRAME_SETTING_BROTLI_EFFORT = 32,
/** Enum value not to be used as an option. This value is added to force the /** 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. * C compiler to have the enum to take a known size.
*/ */
@ -509,6 +516,8 @@ JxlEncoderAddJPEGFrame(const JxlEncoderFrameSettings* frame_settings,
* *
* Extra channels not handled here need to be set by @ref * Extra channels not handled here need to be set by @ref
* JxlEncoderSetExtraChannelBuffer. * JxlEncoderSetExtraChannelBuffer.
* If the image has alpha, and alpha is not passed here, it will implicitly be
* set to all-opaque (an alpha value of 1.0 everywhere).
* *
* The color profile of the pixels depends on the value of uses_original_profile * The color profile of the pixels depends on the value of uses_original_profile
* in the JxlBasicInfo. If true, the pixels are assumed to be encoded in the * in the JxlBasicInfo. If true, the pixels are assumed to be encoded in the
@ -571,10 +580,8 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer(
* to effectively write the box to the output. @ref JxlEncoderUseBoxes must * to effectively write the box to the output. @ref JxlEncoderUseBoxes must
* be enabled before using this function. * be enabled before using this function.
* *
* Background information about the container format and boxes follows here: * Boxes allow inserting application-specific data and metadata (Exif, XML/XMP,
* * JUMBF and user defined boxes).
* For users of libjxl, boxes allow inserting application-specific data and
* metadata (Exif, XML, JUMBF and user defined boxes).
* *
* The box format follows ISO BMFF and shares features and box types with other * The box format follows ISO BMFF and shares features and box types with other
* image and video formats, including the Exif, XML and JUMBF boxes. The box * image and video formats, including the Exif, XML and JUMBF boxes. The box
@ -582,7 +589,7 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer(
* *
* Boxes in general don't contain other boxes inside, except a JUMBF superbox. * Boxes in general don't contain other boxes inside, except a JUMBF superbox.
* Boxes follow each other sequentially and are byte-aligned. If the container * Boxes follow each other sequentially and are byte-aligned. If the container
* format is used, the JXL stream exists out of 3 or more concatenated boxes. * format is used, the JXL stream consists of concatenated boxes.
* It is also possible to use a direct codestream without boxes, but in that * It is also possible to use a direct codestream without boxes, but in that
* case metadata cannot be added. * case metadata cannot be added.
* *
@ -594,80 +601,42 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer(
* - N bytes: box contents. * - N bytes: box contents.
* *
* Only the box contents are provided to the contents argument of this function, * Only the box contents are provided to the contents argument of this function,
* the encoder encodes the size header itself. * the encoder encodes the size header itself. Most boxes are written
* automatically by the encoder as needed ("JXL ", "ftyp", "jxll", "jxlc",
* "jxlp", "jxli", "jbrd"), and this function only needs to be called to add
* optional metadata when encoding from pixels (using JxlEncoderAddImageFrame).
* When recompressing JPEG files (using JxlEncoderAddJPEGFrame), if the input
* JPEG contains EXIF, XMP or JUMBF metadata, the corresponding boxes are
* already added automatically.
* *
* Box types are given by 4 characters. A list of known types follows: * Box types are given by 4 characters. The following boxes can be added with
* - "JXL ": mandatory signature box, must come first, 12 bytes long including * this function:
* the box header
* - "ftyp": a second mandatory signature box, must come second, 20 bytes long
* including the box header
* - "jxll": A JXL level box. This indicates if the codestream is level 5 or
* level 10 compatible. If not present, it is level 5. Level 10 allows more
* features such as very high image resolution and bit-depths above 16 bits
* per channel. Added automatically by the encoder when
* JxlEncoderSetCodestreamLevel is used
* - "jxlc": a box with the image codestream, in case the codestream is not
* split across multiple boxes. The codestream contains the JPEG XL image
* itself, including the basic info such as image dimensions, ICC color
* profile, and all the pixel data of all the image frames.
* - "jxlp": a codestream box in case it is split across multiple boxes. The
* encoder will automatically do this if necessary. The contents are the same
* as in case of a jxlc box, when concatenated.
* - "Exif": a box with EXIF metadata, can be added by libjxl users, or is * - "Exif": a box with EXIF metadata, can be added by libjxl users, or is
* automatically added when needed for JPEG reconstruction. The contents of * automatically added when needed for JPEG reconstruction. The contents of
* this box must be prepended by a 4-byte tiff header offset, which may * this box must be prepended by a 4-byte tiff header offset, which may
* be 4 zero bytes. * be 4 zero bytes in case the tiff header follows immediately.
* - "XML ": a box with XMP or IPTC metadata, can be added by libjxl users, or * The EXIF metadata must be in sync with what is encoded in the JPEG XL
* is automatically added when needed for JPEG reconstruction * codestream, specifically the image orientation. While this is not
* recommended in practice, in case of conflicting metadata, the JPEG XL
* codestream takes precedence.
* - "xml ": a box with XML data, in particular XMP metadata, can be added by
* libjxl users, or is automatically added when needed for JPEG reconstruction
* - "jumb": a JUMBF superbox, which can contain boxes with different types of * - "jumb": a JUMBF superbox, which can contain boxes with different types of
* metadata inside. This box type can be added by the encoder transparently, * metadata inside. This box type can be added by the encoder transparently,
* and other libraries to create and handle JUMBF content exist. * and other libraries to create and handle JUMBF content exist.
* - "brob": a Brotli-compressed box, which otherwise represents an existing * - Application-specific boxes. Their typename should not begin with "jxl" or
* type of box such as Exif or XML. The encoder creates these when enabled and * "JXL" or conflict with other existing typenames, and they should be
* users of libjxl don't need to create them directly. Some box types are not * registered with MP4RA (mp4ra.org).
* allowed to be compressed: any of the signature, jxl* and jbrd boxes.
* - "jxli": frame index box, can list the keyframes in case of a JXL animation,
* allowing the decoder to jump to individual frames more efficiently. This
* box type is specified, but not currently supported by the encoder or
* decoder.
* - "jbrd": JPEG reconstruction box, contains the information required to
* byte-for-byte losslessly recontruct a JPEG-1 image. The JPEG coefficients
* (pixel content) themselves are encoded in the JXL codestream (jxlc or jxlp)
* itself. Exif and XMP metadata will be encoded in Exif and XMP boxes. The
* jbrd box itself contains information such as the app markers of the JPEG-1
* file and everything else required to fit the information together into the
* exact original JPEG file. This box is added automatically by the encoder
* when needed, and only when JPEG reconstruction is used.
* - other: other application-specific boxes can be added. Their typename should
* not begin with "jxl" or "JXL" or conflict with other existing typenames.
* *
* Most boxes are automatically added by the encoder and should not be added * These boxes can be stored uncompressed or Brotli-compressed (using a "brob"
* with JxlEncoderAddBox. Boxes that one may wish to add with JxlEncoderAddBox * box), depending on the compress_box parameter.
* are: Exif and XML (but not when using JPEG reconstruction since if the
* JPEG has those, these boxes are already added automatically), jumb, and
* application-specific boxes.
*
* Adding metadata boxes increases the filesize. When adding Exif metadata, the
* data must be in sync with what is encoded in the JPEG XL codestream,
* specifically the image orientation. While this is not recommended in
* practice, in case of conflicting metadata, the JPEG XL codestream takes
* precedence.
*
* It is possible to create a codestream without boxes, then what would be in
* the jxlc box is written directly to the output
*
* It is possible to split the codestream across multiple boxes, in that case
* multiple boxes of type jxlp are used. This is handled by the encoder when
* needed.
* *
* @param enc encoder object. * @param enc encoder object.
* @param type the box type, e.g. "Exif" for EXIF metadata, "XML " for XMP or * @param type the box type, e.g. "Exif" for EXIF metadata, "xml " for XMP or
* IPTC metadata, "jumb" for JUMBF metadata. * IPTC metadata, "jumb" for JUMBF metadata.
* @param contents the full contents of the box, for example EXIF * @param contents the full contents of the box, for example EXIF
* data. For an "Exif" box, the EXIF data must be prepended by a 4-byte tiff * data. ISO BMFF box header must not be included, only the contents. Owned by
* header offset, which may be 4 zero-bytes. The ISO BMFF box header must not * the caller and its contents are copied internally.
* be included, only the contents. Owned by the caller and its contents are
* copied internally.
* @param size size of the box contents. * @param size size of the box contents.
* @param compress_box Whether to compress this box as a "brob" box. Requires * @param compress_box Whether to compress this box as a "brob" box. Requires
* Brotli support. * Brotli support.
@ -1049,7 +1018,7 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameDistance(
/** DEPRECATED: use JxlEncoderSetFrameDistance instead. /** DEPRECATED: use JxlEncoderSetFrameDistance instead.
*/ */
JXL_EXPORT JxlEncoderStatus JXL_EXPORT JXL_DEPRECATED JxlEncoderStatus
JxlEncoderOptionsSetDistance(JxlEncoderFrameSettings*, float); JxlEncoderOptionsSetDistance(JxlEncoderFrameSettings*, float);
/** /**
@ -1072,7 +1041,7 @@ JXL_EXPORT JxlEncoderFrameSettings* JxlEncoderFrameSettingsCreate(
/** DEPRECATED: use JxlEncoderFrameSettingsCreate instead. /** DEPRECATED: use JxlEncoderFrameSettingsCreate instead.
*/ */
JXL_EXPORT JxlEncoderFrameSettings* JxlEncoderOptionsCreate( JXL_EXPORT JXL_DEPRECATED JxlEncoderFrameSettings* JxlEncoderOptionsCreate(
JxlEncoder*, const JxlEncoderFrameSettings*); JxlEncoder*, const JxlEncoderFrameSettings*);
/** /**

View File

@ -16,6 +16,8 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include "jxl/jxl_export.h"
#if defined(__cplusplus) || defined(c_plusplus) #if defined(__cplusplus) || defined(c_plusplus)
extern "C" { extern "C" {
#endif #endif
@ -41,28 +43,26 @@ typedef enum {
* for HDR and wide gamut images when color profile conversion is required. */ * for HDR and wide gamut images when color profile conversion is required. */
JXL_TYPE_FLOAT = 0, JXL_TYPE_FLOAT = 0,
/** Use 1-bit packed in uint8_t, first pixel in LSB, padded to uint8_t per
* row.
* TODO(lode): support first in MSB, other padding.
*/
JXL_TYPE_BOOLEAN,
/** Use type uint8_t. May clip wide color gamut data. /** Use type uint8_t. May clip wide color gamut data.
*/ */
JXL_TYPE_UINT8, JXL_TYPE_UINT8 = 2,
/** Use type uint16_t. May clip wide color gamut data. /** Use type uint16_t. May clip wide color gamut data.
*/ */
JXL_TYPE_UINT16, JXL_TYPE_UINT16 = 3,
/** Use type uint32_t. May clip wide color gamut data.
*/
JXL_TYPE_UINT32,
/** Use 16-bit IEEE 754 half-precision floating point values */ /** Use 16-bit IEEE 754 half-precision floating point values */
JXL_TYPE_FLOAT16, JXL_TYPE_FLOAT16 = 5,
} JxlDataType; } JxlDataType;
/* DEPRECATED: bit-packed 1-bit data type. Use JXL_TYPE_UINT8 instead.
*/
static const int JXL_DEPRECATED JXL_TYPE_BOOLEAN = 1;
/* DEPRECATED: uint32_t data type. Use JXL_TYPE_FLOAT instead.
*/
static const int JXL_DEPRECATED JXL_TYPE_UINT32 = 4;
/** Ordering of multi-byte data. /** Ordering of multi-byte data.
*/ */
typedef enum { typedef enum {

View File

@ -39,9 +39,9 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/base/profiler.h jxl/base/profiler.h
jxl/base/random.cc jxl/base/random.cc
jxl/base/random.h jxl/base/random.h
jxl/base/sanitizer_definitions.h
jxl/base/scope_guard.h jxl/base/scope_guard.h
jxl/base/span.h jxl/base/span.h
jxl/base/status.cc
jxl/base/status.h jxl/base/status.h
jxl/base/thread_pool_internal.h jxl/base/thread_pool_internal.h
jxl/blending.cc jxl/blending.cc
@ -106,6 +106,8 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/entropy_coder.h jxl/entropy_coder.h
jxl/epf.cc jxl/epf.cc
jxl/epf.h jxl/epf.h
jxl/exif.cc
jxl/exif.h
jxl/fast_dct-inl.h jxl/fast_dct-inl.h
jxl/fast_dct.cc jxl/fast_dct.cc
jxl/fast_dct.h jxl/fast_dct.h
@ -344,6 +346,8 @@ set(JPEGXL_DEC_INTERNAL_LIBS
brotlidec-static brotlidec-static
brotlicommon-static brotlicommon-static
hwy hwy
Threads::Threads
${ATOMICS_LIBRARIES}
) )
if(JPEGXL_ENABLE_PROFILER) if(JPEGXL_ENABLE_PROFILER)
@ -353,7 +357,6 @@ endif()
set(JPEGXL_INTERNAL_LIBS set(JPEGXL_INTERNAL_LIBS
${JPEGXL_DEC_INTERNAL_LIBS} ${JPEGXL_DEC_INTERNAL_LIBS}
brotlienc-static brotlienc-static
Threads::Threads
) )
# strips the -static suffix from all the elements in LIST # strips the -static suffix from all the elements in LIST
@ -465,7 +468,7 @@ add_library(jxl_dec-static STATIC
$<TARGET_OBJECTS:jxl_dec-obj> $<TARGET_OBJECTS:jxl_dec-obj>
) )
target_link_libraries(jxl_dec-static target_link_libraries(jxl_dec-static
PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_DEC_INTERNAL_LIBS} hwy) PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_DEC_INTERNAL_LIBS})
target_include_directories(jxl_dec-static PUBLIC target_include_directories(jxl_dec-static PUBLIC
"${PROJECT_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/include"
@ -486,7 +489,7 @@ endif()
# to do, remove $<TARGET_OBJECTS:jxl_dec-obj> here and depend on jxl_dec-static # to do, remove $<TARGET_OBJECTS:jxl_dec-obj> here and depend on jxl_dec-static
add_library(jxl-static STATIC ${JPEGXL_INTERNAL_OBJECTS}) add_library(jxl-static STATIC ${JPEGXL_INTERNAL_OBJECTS})
target_link_libraries(jxl-static target_link_libraries(jxl-static
PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_INTERNAL_LIBS} hwy) PUBLIC ${JPEGXL_COVERAGE_FLAGS} ${JPEGXL_INTERNAL_LIBS})
target_include_directories(jxl-static PUBLIC target_include_directories(jxl-static PUBLIC
"${PROJECT_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}"
"${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/include"

View File

@ -220,7 +220,7 @@ struct AuxOut {
Image3MinMax(image, &min, &max); Image3MinMax(image, &min, &max);
Image3B normalized(image.xsize(), image.ysize()); Image3B normalized(image.xsize(), image.ysize());
for (size_t c = 0; c < 3; ++c) { for (size_t c = 0; c < 3; ++c) {
float mul = min[c] == max[c] ? 0 : (1.0f / (max[c] - min[c])); float mul = min[c] == max[c] ? 0 : (255.0f / (max[c] - min[c]));
for (size_t y = 0; y < image.ysize(); ++y) { for (size_t y = 0; y < image.ysize(); ++y) {
const T* JXL_RESTRICT row_in = image.ConstPlaneRow(c, y); const T* JXL_RESTRICT row_in = image.ConstPlaneRow(c, y);
uint8_t* JXL_RESTRICT row_out = normalized.PlaneRow(c, y); uint8_t* JXL_RESTRICT row_out = normalized.PlaneRow(c, y);

View File

@ -0,0 +1,44 @@
// Copyright (c) the JPEG XL Project 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_SANITIZER_DEFINITIONS_H_
#define LIB_JXL_BASE_SANITIZER_DEFINITIONS_H_
#ifdef MEMORY_SANITIZER
#define JXL_MEMORY_SANITIZER 1
#elif defined(__has_feature)
#if __has_feature(memory_sanitizer)
#define JXL_MEMORY_SANITIZER 1
#else
#define JXL_MEMORY_SANITIZER 0
#endif
#else
#define JXL_MEMORY_SANITIZER 0
#endif
#ifdef ADDRESS_SANITIZER
#define JXL_ADDRESS_SANITIZER 1
#elif defined(__has_feature)
#if __has_feature(address_sanitizer)
#define JXL_ADDRESS_SANITIZER 1
#else
#define JXL_ADDRESS_SANITIZER 0
#endif
#else
#define JXL_ADDRESS_SANITIZER 0
#endif
#ifdef THREAD_SANITIZER
#define JXL_THREAD_SANITIZER 1
#elif defined(__has_feature)
#if __has_feature(thread_sanitizer)
#define JXL_THREAD_SANITIZER 1
#else
#define JXL_THREAD_SANITIZER 0
#endif
#else
#define JXL_THREAD_SANITIZER 0
#endif
#endif // LIB_JXL_BASE_SANITIZER_DEFINITIONS_H

View File

@ -1,46 +0,0 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/jxl/base/status.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include "lib/jxl/sanitizers.h"
#if JXL_ADDRESS_SANITIZER || JXL_MEMORY_SANITIZER || JXL_THREAD_SANITIZER
#include "sanitizer/common_interface_defs.h" // __sanitizer_print_stack_trace
#endif // defined(*_SANITIZER)
namespace jxl {
bool Debug(const char* format, ...) {
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
return false;
}
bool Abort() {
#if JXL_ADDRESS_SANITIZER || JXL_MEMORY_SANITIZER || JXL_THREAD_SANITIZER
// If compiled with any sanitizer print a stack trace. This call doesn't crash
// the program, instead the trap below will crash it also allowing gdb to
// break there.
__sanitizer_print_stack_trace();
#endif // *_SANITIZER)
#if JXL_COMPILER_MSVC
__debugbreak();
abort();
#else
__builtin_trap();
#endif
}
} // namespace jxl

View File

@ -14,6 +14,11 @@
#include <stdlib.h> #include <stdlib.h>
#include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/sanitizer_definitions.h"
#if JXL_ADDRESS_SANITIZER || JXL_MEMORY_SANITIZER || JXL_THREAD_SANITIZER
#include "sanitizer/common_interface_defs.h" // __sanitizer_print_stack_trace
#endif // defined(*_SANITIZER)
namespace jxl { namespace jxl {
@ -70,7 +75,13 @@ namespace jxl {
// instead of calling Debug directly. This function returns false, so it can be // instead of calling Debug directly. This function returns false, so it can be
// used as a return value in JXL_FAILURE. // used as a return value in JXL_FAILURE.
JXL_FORMAT(1, 2) JXL_FORMAT(1, 2)
bool Debug(const char* format, ...); inline JXL_NOINLINE bool Debug(const char* format, ...) {
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
return false;
}
// Print a debug message on standard error if "enabled" is true. "enabled" is // Print a debug message on standard error if "enabled" is true. "enabled" is
// normally a macro that evaluates to 0 or 1 at compile time, so the Debug // normally a macro that evaluates to 0 or 1 at compile time, so the Debug
@ -113,7 +124,21 @@ bool Debug(const char* format, ...);
JXL_DEBUG(JXL_DEBUG_WARNING, format, ##__VA_ARGS__) JXL_DEBUG(JXL_DEBUG_WARNING, format, ##__VA_ARGS__)
// Exits the program after printing a stack trace when possible. // Exits the program after printing a stack trace when possible.
JXL_NORETURN bool Abort(); JXL_NORETURN inline JXL_NOINLINE bool Abort() {
#if JXL_ADDRESS_SANITIZER || JXL_MEMORY_SANITIZER || JXL_THREAD_SANITIZER
// If compiled with any sanitizer print a stack trace. This call doesn't crash
// the program, instead the trap below will crash it also allowing gdb to
// break there.
__sanitizer_print_stack_trace();
#endif // *_SANITIZER)
#if JXL_COMPILER_MSVC
__debugbreak();
abort();
#else
__builtin_trap();
#endif
}
// Exits the program after printing file/line plus a formatted string. // Exits the program after printing file/line plus a formatted string.
#define JXL_ABORT(format, ...) \ #define JXL_ABORT(format, ...) \

View File

@ -41,6 +41,10 @@ TEST(BitsTest, TestFloorLog2) {
EXPECT_EQ(expected[i - 1], FloorLog2Nonzero(uint64_t(i))) << " " << i; EXPECT_EQ(expected[i - 1], FloorLog2Nonzero(uint64_t(i))) << " " << i;
} }
EXPECT_EQ(11u, FloorLog2Nonzero(0x00000fffu)); // 4095
EXPECT_EQ(12u, FloorLog2Nonzero(0x00001000u)); // 4096
EXPECT_EQ(12u, FloorLog2Nonzero(0x00001001u)); // 4097
EXPECT_EQ(31u, FloorLog2Nonzero(0x80000000u)); EXPECT_EQ(31u, FloorLog2Nonzero(0x80000000u));
EXPECT_EQ(31u, FloorLog2Nonzero(0x80000001u)); EXPECT_EQ(31u, FloorLog2Nonzero(0x80000001u));
EXPECT_EQ(31u, FloorLog2Nonzero(0xFFFFFFFFu)); EXPECT_EQ(31u, FloorLog2Nonzero(0xFFFFFFFFu));
@ -62,6 +66,10 @@ TEST(BitsTest, TestCeilLog2) {
EXPECT_EQ(expected[i - 1], CeilLog2Nonzero(uint64_t(i))) << " " << i; EXPECT_EQ(expected[i - 1], CeilLog2Nonzero(uint64_t(i))) << " " << i;
} }
EXPECT_EQ(12u, CeilLog2Nonzero(0x00000fffu)); // 4095
EXPECT_EQ(12u, CeilLog2Nonzero(0x00001000u)); // 4096
EXPECT_EQ(13u, CeilLog2Nonzero(0x00001001u)); // 4097
EXPECT_EQ(31u, CeilLog2Nonzero(0x80000000u)); EXPECT_EQ(31u, CeilLog2Nonzero(0x80000000u));
EXPECT_EQ(32u, CeilLog2Nonzero(0x80000001u)); EXPECT_EQ(32u, CeilLog2Nonzero(0x80000001u));
EXPECT_EQ(32u, CeilLog2Nonzero(0xFFFFFFFFu)); EXPECT_EQ(32u, CeilLog2Nonzero(0xFFFFFFFFu));

View File

@ -78,7 +78,7 @@ void PerformBlending(const float* const* bg, const float* const* fg,
} else if (ec_blending[i].mode == PatchBlendMode::kReplace) { } else if (ec_blending[i].mode == PatchBlendMode::kReplace) {
memcpy(tmp.Row(3 + i), fg[3 + i] + x0, xsize * sizeof(**fg)); memcpy(tmp.Row(3 + i), fg[3 + i] + x0, xsize * sizeof(**fg));
} else if (ec_blending[i].mode == PatchBlendMode::kNone) { } else if (ec_blending[i].mode == PatchBlendMode::kNone) {
memcpy(tmp.Row(3 + i), bg[3 + i] + x0, xsize * sizeof(**fg)); if (xsize) memcpy(tmp.Row(3 + i), bg[3 + i] + x0, xsize * sizeof(**fg));
} else { } else {
JXL_ABORT("Unreachable"); JXL_ABORT("Unreachable");
} }

View File

@ -36,10 +36,6 @@ void SetMetadataFromPixelFormat(const JxlPixelFormat* pixel_format,
metadata->SetFloat16Samples(); metadata->SetFloat16Samples();
potential_alpha_bits = 16; potential_alpha_bits = 16;
break; break;
case JXL_TYPE_UINT32:
metadata->SetUintSamples(32);
potential_alpha_bits = 16;
break;
case JXL_TYPE_UINT16: case JXL_TYPE_UINT16:
metadata->SetUintSamples(16); metadata->SetUintSamples(16);
potential_alpha_bits = 16; potential_alpha_bits = 16;
@ -48,10 +44,8 @@ void SetMetadataFromPixelFormat(const JxlPixelFormat* pixel_format,
metadata->SetUintSamples(8); metadata->SetUintSamples(8);
potential_alpha_bits = 8; potential_alpha_bits = 8;
break; break;
case JXL_TYPE_BOOLEAN: default:
metadata->SetUintSamples(2); JXL_ABORT("Unhandled JxlDataType");
potential_alpha_bits = 2;
break;
} }
if (pixel_format->num_channels == 2 || pixel_format->num_channels == 4) { if (pixel_format->num_channels == 2 || pixel_format->num_channels == 4) {
metadata->SetAlphaBits(potential_alpha_bits); metadata->SetAlphaBits(potential_alpha_bits);
@ -116,9 +110,10 @@ void JxlButteraugliApiSetIntensityTarget(JxlButteraugliApi* api, float v) {
void JxlButteraugliApiDestroy(JxlButteraugliApi* api) { void JxlButteraugliApiDestroy(JxlButteraugliApi* api) {
if (api) { if (api) {
JxlMemoryManager local_memory_manager = api->memory_manager;
// Call destructor directly since custom free function is used. // Call destructor directly since custom free function is used.
api->~JxlButteraugliApi(); api->~JxlButteraugliApi();
jxl::MemoryManagerFree(&api->memory_manager, api); jxl::MemoryManagerFree(&local_memory_manager, api);
} }
} }
@ -200,8 +195,9 @@ float JxlButteraugliResultGetMaxDistance(const JxlButteraugliResult* result) {
void JxlButteraugliResultDestroy(JxlButteraugliResult* result) { void JxlButteraugliResultDestroy(JxlButteraugliResult* result) {
if (result) { if (result) {
JxlMemoryManager local_memory_manager = result->memory_manager;
// Call destructor directly since custom free function is used. // Call destructor directly since custom free function is used.
result->~JxlButteraugliResult(); result->~JxlButteraugliResult();
jxl::MemoryManagerFree(&result->memory_manager, result); jxl::MemoryManagerFree(&local_memory_manager, result);
} }
} }

View File

@ -56,10 +56,10 @@ using CodecIntervals = std::array<CodecInterval, 4>; // RGB[A] or Y[A]
// Optional text/EXIF metadata. // Optional text/EXIF metadata.
struct Blobs { struct Blobs {
PaddedBytes exif; std::vector<uint8_t> exif;
PaddedBytes iptc; std::vector<uint8_t> iptc;
PaddedBytes jumbf; std::vector<uint8_t> jumbf;
PaddedBytes xmp; std::vector<uint8_t> xmp;
}; };
// Holds a preview, a main image or one or more frames, plus the inputs/outputs // Holds a preview, a main image or one or more frames, plus the inputs/outputs

View File

@ -17,6 +17,7 @@
#include <string> #include <string>
#include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/padded_bytes.h"
#ifndef JXL_HIGH_PRECISION #ifndef JXL_HIGH_PRECISION
#define JXL_HIGH_PRECISION 1 #define JXL_HIGH_PRECISION 1
@ -192,6 +193,46 @@ std::string ToString(T n) {
} }
return data; return data;
} }
namespace {
static inline uint64_t DecodeVarInt(const uint8_t* input, size_t inputSize,
size_t* pos) {
size_t i;
uint64_t ret = 0;
for (i = 0; *pos + i < inputSize && i < 10; ++i) {
ret |= uint64_t(input[*pos + i] & 127) << uint64_t(7 * i);
// If the next-byte flag is not set, stop
if ((input[*pos + i] & 128) == 0) break;
}
// TODO: Return a decoding error if i == 10.
*pos += i + 1;
return ret;
}
static inline bool EncodeVarInt(uint64_t value, size_t output_size,
size_t* output_pos, uint8_t* output) {
// While more than 7 bits of data are left,
// store 7 bits and set the next byte flag
while (value > 127) {
if (*output_pos > output_size) return false;
// |128: Set the next byte flag
output[(*output_pos)++] = ((uint8_t)(value & 127)) | 128;
// Remove the seven bits we just wrote
value >>= 7;
}
if (*output_pos > output_size) return false;
output[(*output_pos)++] = ((uint8_t)value) & 127;
return true;
}
static inline void EncodeVarInt(uint64_t value, PaddedBytes* data) {
size_t pos = data->size();
data->resize(data->size() + 9);
JXL_CHECK(EncodeVarInt(value, data->size(), &pos, data->data()));
data->resize(pos);
}
} // namespace
} // namespace jxl } // namespace jxl
#endif // LIB_JXL_COMMON_H_ #endif // LIB_JXL_COMMON_H_

View File

@ -44,7 +44,7 @@ class Neighbors {
return c; // Same (the first mirrored value is the last valid one) return c; // Same (the first mirrored value is the last valid one)
#else // 128 bit #else // 128 bit
// c = LKJI // c = LKJI
#if HWY_ARCH_X86 #if HWY_TARGET <= (1 << HWY_HIGHEST_TARGET_BIT_X86)
return V{_mm_shuffle_ps(c.raw, c.raw, _MM_SHUFFLE(2, 1, 0, 0))}; // KJII return V{_mm_shuffle_ps(c.raw, c.raw, _MM_SHUFFLE(2, 1, 0, 0))}; // KJII
#else #else
const D d; const D d;
@ -72,7 +72,7 @@ class Neighbors {
return Zero(d); return Zero(d);
#else // 128 bit #else // 128 bit
// c = LKJI // c = LKJI
#if HWY_ARCH_X86 #if HWY_TARGET <= (1 << HWY_HIGHEST_TARGET_BIT_X86)
return V{_mm_shuffle_ps(c.raw, c.raw, _MM_SHUFFLE(1, 0, 0, 1))}; // JIIJ return V{_mm_shuffle_ps(c.raw, c.raw, _MM_SHUFFLE(1, 0, 0, 1))}; // JIIJ
#else #else
const D d; const D d;
@ -98,7 +98,7 @@ class Neighbors {
return Zero(d); return Zero(d);
#else // 128 bit #else // 128 bit
// c = LKJI // c = LKJI
#if HWY_ARCH_X86 #if HWY_TARGET <= (1 << HWY_HIGHEST_TARGET_BIT_X86)
return V{_mm_shuffle_ps(c.raw, c.raw, _MM_SHUFFLE(0, 0, 1, 2))}; // IIJK return V{_mm_shuffle_ps(c.raw, c.raw, _MM_SHUFFLE(0, 0, 1, 2))}; // IIJK
#else #else
const D d; const D d;

View File

@ -74,9 +74,6 @@ Status ReadHistogram(int precision_bits, std::vector<int>* counts,
int is_flat = input->ReadBits(1); int is_flat = input->ReadBits(1);
if (is_flat == 1) { if (is_flat == 1) {
int alphabet_size = DecodeVarLenUint8(input) + 1; int alphabet_size = DecodeVarLenUint8(input) + 1;
if (alphabet_size == 0) {
return JXL_FAILURE("Invalid alphabet size for flat histogram.");
}
*counts = CreateFlatHistogram(alphabet_size, 1 << precision_bits); *counts = CreateFlatHistogram(alphabet_size, 1 << precision_bits);
return true; return true;
} }

View File

@ -180,12 +180,12 @@ Status PassesDecoderState::PreparePipeline(ImageBundle* decoded,
} }
} }
if (pixel_callback) { if (pixel_callback.IsPresent()) {
builder.AddStage(GetWriteToPixelCallbackStage(pixel_callback, width, builder.AddStage(GetWriteToPixelCallbackStage(pixel_callback, width,
height, rgb_output_is_rgba, height, rgb_output_is_rgba,
has_alpha, alpha_c)); has_alpha, alpha_c));
} else if (rgb_output) { } else if (rgb_output) {
builder.AddStage(GetWriteToU8Stage(rgb_output, rgb_stride, width, height, builder.AddStage(GetWriteToU8Stage(rgb_output, rgb_stride, height,
rgb_output_is_rgba, has_alpha, rgb_output_is_rgba, has_alpha,
alpha_c)); alpha_c));
} else { } else {

View File

@ -10,6 +10,7 @@
#include <hwy/base.h> // HWY_ALIGN_MAX #include <hwy/base.h> // HWY_ALIGN_MAX
#include "jxl/decode.h"
#include "lib/jxl/ac_strategy.h" #include "lib/jxl/ac_strategy.h"
#include "lib/jxl/base/profiler.h" #include "lib/jxl/base/profiler.h"
#include "lib/jxl/coeff_order.h" #include "lib/jxl/coeff_order.h"
@ -29,6 +30,31 @@ namespace jxl {
constexpr size_t kSigmaBorder = 1; constexpr size_t kSigmaBorder = 1;
constexpr size_t kSigmaPadding = 2; constexpr size_t kSigmaPadding = 2;
struct PixelCallback {
PixelCallback() = default;
PixelCallback(JxlImageOutInitCallback init, JxlImageOutRunCallback run,
JxlImageOutDestroyCallback destroy, void* init_opaque)
: init(init), run(run), destroy(destroy), init_opaque(init_opaque) {
#if JXL_ENABLE_ASSERT
const bool has_init = init != nullptr;
const bool has_run = run != nullptr;
const bool has_destroy = destroy != nullptr;
JXL_ASSERT(has_init == has_run && has_run == has_destroy);
#endif
}
bool IsPresent() const { return run != nullptr; }
void* Init(size_t num_threads, size_t num_pixels) const {
return init(init_opaque, num_threads, num_pixels);
}
JxlImageOutInitCallback init = nullptr;
JxlImageOutRunCallback run = nullptr;
JxlImageOutDestroyCallback destroy = nullptr;
void* init_opaque = nullptr;
};
// Per-frame decoder state. All the images here should be accessed through a // Per-frame decoder state. All the images here should be accessed through a
// group rect (either with block units or pixel units). // group rect (either with block units or pixel units).
struct PassesDecoderState { struct PassesDecoderState {
@ -65,7 +91,8 @@ struct PassesDecoderState {
bool rgb_output_is_rgba; bool rgb_output_is_rgba;
// Callback for line-by-line output. // Callback for line-by-line output.
std::function<void(const float*, size_t, size_t, size_t)> pixel_callback; PixelCallback pixel_callback;
// Buffer of upsampling * kApplyImageFeaturesTileDim ones. // Buffer of upsampling * kApplyImageFeaturesTileDim ones.
std::vector<float> opaque_alpha; std::vector<float> opaque_alpha;
// One row per thread // One row per thread
@ -106,7 +133,6 @@ struct PassesDecoderState {
std::pow(1 / (1.25f), shared->frame_header.b_qm_scale - 2.0f); std::pow(1 / (1.25f), shared->frame_header.b_qm_scale - 2.0f);
rgb_output = nullptr; rgb_output = nullptr;
pixel_callback = nullptr;
rgb_output_is_rgba = false; rgb_output_is_rgba = false;
fast_xyb_srgb8_conversion = false; fast_xyb_srgb8_conversion = false;
used_acs = 0; used_acs = 0;

View File

@ -32,28 +32,9 @@ HWY_BEFORE_NAMESPACE();
namespace jxl { namespace jxl {
namespace HWY_NAMESPACE { namespace HWY_NAMESPACE {
// TODO(jon): check if this can be replaced by a FloatToU16 function
void FloatToU32(const float* in, uint32_t* out, size_t num, float mul, void FloatToU32(const float* in, uint32_t* out, size_t num, float mul,
size_t bits_per_sample) { size_t bits_per_sample) {
// TODO(eustas): investigate 24..31 bpp cases.
if (bits_per_sample == 32) {
// Conversion to real 32-bit *unsigned* integers requires more intermediate
// precision that what is given by the usual f32 -> i32 conversion
// instructions, so we run the non-SIMD path for those.
const uint32_t cap = (1ull << bits_per_sample) - 1;
for (size_t x = 0; x < num; x++) {
float v = in[x];
if (v >= 1.0f) {
out[x] = cap;
} else if (v >= 0.0f) { // Inverted condition => NaN -> 0.
out[x] = static_cast<uint32_t>(v * mul + 0.5f);
} else {
out[x] = 0;
}
}
return;
}
// General SIMD case for less than 32 bits output.
const HWY_FULL(float) d; const HWY_FULL(float) d;
const hwy::HWY_NAMESPACE::Rebind<uint32_t, decltype(d)> du; const hwy::HWY_NAMESPACE::Rebind<uint32_t, decltype(d)> du;
@ -267,39 +248,36 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
JxlEndianness endianness, size_t stride, JxlEndianness endianness, size_t stride,
jxl::ThreadPool* pool, void* out_image, jxl::ThreadPool* pool, void* out_image,
size_t out_size, size_t out_size,
JxlImageOutCallback out_callback, const PixelCallback& out_callback,
void* out_opaque,
jxl::Orientation undo_orientation) { jxl::Orientation undo_orientation) {
JXL_DASSERT(num_channels != 0 && num_channels <= kConvertMaxChannels); JXL_DASSERT(num_channels != 0 && num_channels <= kConvertMaxChannels);
JXL_DASSERT(channels[0] != nullptr); JXL_DASSERT(channels[0] != nullptr);
JXL_CHECK(float_out ? bits_per_sample == 16 || bits_per_sample == 32
if (bits_per_sample < 1 || bits_per_sample > 32) { : bits_per_sample > 0 && bits_per_sample <= 16);
return JXL_FAILURE("Invalid bits_per_sample value."); if (!!out_image == out_callback.IsPresent()) {
}
if (!!out_image == !!out_callback) {
return JXL_FAILURE( return JXL_FAILURE(
"Must provide either an out_image or an out_callback, but not both."); "Must provide either an out_image or an out_callback, but not both.");
} }
// TODO(deymo): Implement 1-bit per pixel packed in 8 samples per byte.
if (bits_per_sample == 1) {
return JXL_FAILURE("packed 1-bit per sample is not yet supported");
}
if (bits_per_sample > 16 && bits_per_sample < 32) {
return JXL_FAILURE("not supported, try bits_per_sample=32");
}
// bytes_per_channel and is only valid for bits_per_sample > 1.
const size_t bytes_per_channel = DivCeil(bits_per_sample, jxl::kBitsPerByte); const size_t bytes_per_channel = DivCeil(bits_per_sample, jxl::kBitsPerByte);
const size_t bytes_per_pixel = num_channels * bytes_per_channel; const size_t bytes_per_pixel = num_channels * bytes_per_channel;
std::vector<std::vector<uint8_t>> row_out_callback; std::vector<std::vector<uint8_t>> row_out_callback;
auto InitOutCallback = [&](size_t num_threads) { const auto FreeCallbackOpaque = [&out_callback](void* p) {
if (out_callback) { out_callback.destroy(p);
};
std::unique_ptr<void, decltype(FreeCallbackOpaque)> out_run_opaque(
nullptr, FreeCallbackOpaque);
auto InitOutCallback = [&](size_t num_threads) -> Status {
if (out_callback.IsPresent()) {
out_run_opaque.reset(out_callback.Init(num_threads, stride));
JXL_RETURN_IF_ERROR(out_run_opaque != nullptr);
row_out_callback.resize(num_threads); row_out_callback.resize(num_threads);
for (size_t i = 0; i < num_threads; ++i) { for (size_t i = 0; i < num_threads; ++i) {
row_out_callback[i].resize(stride); row_out_callback[i].resize(stride);
} }
} }
return true;
}; };
// Channels used to store the transformed original channels if needed. // Channels used to store the transformed original channels if needed.
@ -322,7 +300,7 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
" vs %" PRIuS, " vs %" PRIuS,
stride, bytes_per_pixel * xsize); stride, bytes_per_pixel * xsize);
} }
if (!out_callback && if (!out_callback.IsPresent() &&
out_size < (ysize - 1) * stride + bytes_per_pixel * xsize) { out_size < (ysize - 1) * stride + bytes_per_pixel * xsize) {
return JXL_FAILURE("out_size is too small to store image"); return JXL_FAILURE("out_size is too small to store image");
} }
@ -351,8 +329,7 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
[&](size_t num_threads) { [&](size_t num_threads) {
f16_cache = f16_cache =
Plane<hwy::float16_t>(xsize, num_channels * num_threads); Plane<hwy::float16_t>(xsize, num_channels * num_threads);
InitOutCallback(num_threads); return InitOutCallback(num_threads);
return true;
}, },
[&](const uint32_t task, const size_t thread) { [&](const uint32_t task, const size_t thread) {
const int64_t y = task; const int64_t y = task;
@ -367,7 +344,7 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
(row_in[c], row_f16[c], xsize); (row_in[c], row_f16[c], xsize);
} }
uint8_t* row_out = uint8_t* row_out =
out_callback out_callback.IsPresent()
? row_out_callback[thread].data() ? row_out_callback[thread].data()
: &(reinterpret_cast<uint8_t*>(out_image))[stride * y]; : &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
// interleave the one scanline // interleave the one scanline
@ -384,22 +361,20 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
std::swap(row_out[i + 0], row_out[i + 1]); std::swap(row_out[i + 0], row_out[i + 1]);
} }
} }
if (out_callback) { if (out_callback.IsPresent()) {
(*out_callback)(out_opaque, 0, y, xsize, row_out); out_callback.run(out_run_opaque.get(), thread, 0, y, xsize,
row_out);
} }
}, },
"ConvertF16")); "ConvertF16"));
} else if (bits_per_sample == 32) { } else if (bits_per_sample == 32) {
JXL_RETURN_IF_ERROR(RunOnPool( JXL_RETURN_IF_ERROR(RunOnPool(
pool, 0, static_cast<uint32_t>(ysize), pool, 0, static_cast<uint32_t>(ysize),
[&](size_t num_threads) { [&](size_t num_threads) { return InitOutCallback(num_threads); },
InitOutCallback(num_threads);
return true;
},
[&](const uint32_t task, const size_t thread) { [&](const uint32_t task, const size_t thread) {
const int64_t y = task; const int64_t y = task;
uint8_t* row_out = uint8_t* row_out =
out_callback out_callback.IsPresent()
? row_out_callback[thread].data() ? row_out_callback[thread].data()
: &(reinterpret_cast<uint8_t*>(out_image))[stride * y]; : &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
const float* JXL_RESTRICT row_in[kConvertMaxChannels]; const float* JXL_RESTRICT row_in[kConvertMaxChannels];
@ -411,8 +386,9 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
} else { } else {
StoreFloatRow<StoreBEFloat>(row_in, num_channels, xsize, row_out); StoreFloatRow<StoreBEFloat>(row_in, num_channels, xsize, row_out);
} }
if (out_callback) { if (out_callback.IsPresent()) {
(*out_callback)(out_opaque, 0, y, xsize, row_out); out_callback.run(out_run_opaque.get(), thread, 0, y, xsize,
row_out);
} }
}, },
"ConvertFloat")); "ConvertFloat"));
@ -428,13 +404,12 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
pool, 0, static_cast<uint32_t>(ysize), pool, 0, static_cast<uint32_t>(ysize),
[&](size_t num_threads) { [&](size_t num_threads) {
u32_cache = Plane<uint32_t>(xsize, num_channels * num_threads); u32_cache = Plane<uint32_t>(xsize, num_channels * num_threads);
InitOutCallback(num_threads); return InitOutCallback(num_threads);
return true;
}, },
[&](const uint32_t task, const size_t thread) { [&](const uint32_t task, const size_t thread) {
const int64_t y = task; const int64_t y = task;
uint8_t* row_out = uint8_t* row_out =
out_callback out_callback.IsPresent()
? row_out_callback[thread].data() ? row_out_callback[thread].data()
: &(reinterpret_cast<uint8_t*>(out_image))[stride * y]; : &(reinterpret_cast<uint8_t*>(out_image))[stride * y];
const float* JXL_RESTRICT row_in[kConvertMaxChannels]; const float* JXL_RESTRICT row_in[kConvertMaxChannels];
@ -450,24 +425,18 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
HWY_DYNAMIC_DISPATCH(FloatToU32) HWY_DYNAMIC_DISPATCH(FloatToU32)
(row_in[c], row_u32[c], xsize, mul, bits_per_sample); (row_in[c], row_u32[c], xsize, mul, bits_per_sample);
} }
// TODO(deymo): add bits_per_sample == 1 case here.
if (bits_per_sample <= 8) { if (bits_per_sample <= 8) {
StoreUintRow<Store8>(row_u32, num_channels, xsize, 1, row_out); StoreUintRow<Store8>(row_u32, num_channels, xsize, 1, row_out);
} else if (bits_per_sample <= 16) { } else {
if (little_endian) { if (little_endian) {
StoreUintRow<StoreLE16>(row_u32, num_channels, xsize, 2, row_out); StoreUintRow<StoreLE16>(row_u32, num_channels, xsize, 2, row_out);
} else { } else {
StoreUintRow<StoreBE16>(row_u32, num_channels, xsize, 2, row_out); StoreUintRow<StoreBE16>(row_u32, num_channels, xsize, 2, row_out);
} }
} else {
if (little_endian) {
StoreUintRow<StoreLE32>(row_u32, num_channels, xsize, 4, row_out);
} else {
StoreUintRow<StoreBE32>(row_u32, num_channels, xsize, 4, row_out);
}
} }
if (out_callback) { if (out_callback.IsPresent()) {
(*out_callback)(out_opaque, 0, y, xsize, row_out); out_callback.run(out_run_opaque.get(), thread, 0, y, xsize,
row_out);
} }
}, },
"ConvertUint")); "ConvertUint"));
@ -481,8 +450,8 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
bool float_out, size_t num_channels, bool float_out, size_t num_channels,
JxlEndianness endianness, size_t stride, JxlEndianness endianness, size_t stride,
jxl::ThreadPool* pool, void* out_image, jxl::ThreadPool* pool, void* out_image,
size_t out_size, JxlImageOutCallback out_callback, size_t out_size, const PixelCallback& out_callback,
void* out_opaque, jxl::Orientation undo_orientation) { jxl::Orientation undo_orientation) {
bool want_alpha = num_channels == 2 || num_channels == 4; bool want_alpha = num_channels == 2 || num_channels == 4;
size_t color_channels = num_channels <= 2 ? 1 : 3; size_t color_channels = num_channels <= 2 ? 1 : 3;
@ -512,19 +481,19 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
return ConvertChannelsToExternal( return ConvertChannelsToExternal(
channels, num_channels, bits_per_sample, float_out, endianness, stride, channels, num_channels, bits_per_sample, float_out, endianness, stride,
pool, out_image, out_size, out_callback, out_opaque, undo_orientation); pool, out_image, out_size, out_callback, undo_orientation);
} }
Status ConvertToExternal(const jxl::ImageF& channel, size_t bits_per_sample, Status ConvertToExternal(const jxl::ImageF& channel, size_t bits_per_sample,
bool float_out, JxlEndianness endianness, bool float_out, JxlEndianness endianness,
size_t stride, jxl::ThreadPool* pool, void* out_image, size_t stride, jxl::ThreadPool* pool, void* out_image,
size_t out_size, JxlImageOutCallback out_callback, size_t out_size, const PixelCallback& out_callback,
void* out_opaque, jxl::Orientation undo_orientation) { jxl::Orientation undo_orientation) {
const ImageF* channels[1]; const ImageF* channels[1];
channels[0] = &channel; channels[0] = &channel;
return ConvertChannelsToExternal( return ConvertChannelsToExternal(channels, 1, bits_per_sample, float_out,
channels, 1, bits_per_sample, float_out, endianness, stride, pool, endianness, stride, pool, out_image,
out_image, out_size, out_callback, out_opaque, undo_orientation); out_size, out_callback, undo_orientation);
} }
} // namespace jxl } // namespace jxl

View File

@ -15,18 +15,18 @@
#include "jxl/types.h" #include "jxl/types.h"
#include "lib/jxl/base/status.h" #include "lib/jxl/base/status.h"
#include "lib/jxl/color_encoding_internal.h" #include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/dec_cache.h"
#include "lib/jxl/image.h" #include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h" #include "lib/jxl/image_bundle.h"
namespace jxl { namespace jxl {
// Converts ib to interleaved void* pixel buffer with the given format. // Converts ib to interleaved void* pixel buffer with the given format.
// bits_per_sample: must be 8, 16 or 32, and must be 32 if float_out // bits_per_sample: must be 16 or 32 if float_out is true, and at most 16
// is true. 1 and 32 int are not yet implemented. // if it is false. No bit packing is done.
// num_channels: must be 1, 2, 3 or 4 for gray, gray+alpha, RGB, RGB+alpha. // num_channels: must be 1, 2, 3 or 4 for gray, gray+alpha, RGB, RGB+alpha.
// This supports the features needed for the C API and does not perform // This supports the features needed for the C API and does not perform
// color space conversion. // color space conversion.
// TODO(lode): support 1-bit output (bits_per_sample == 1)
// TODO(lode): support rectangle crop. // TODO(lode): support rectangle crop.
// stride_out is output scanline size in bytes, must be >= // stride_out is output scanline size in bytes, must be >=
// output_xsize * output_bytes_per_pixel. // output_xsize * output_bytes_per_pixel.
@ -37,24 +37,18 @@ Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
bool float_out, size_t num_channels, bool float_out, size_t num_channels,
JxlEndianness endianness, size_t stride_out, JxlEndianness endianness, size_t stride_out,
jxl::ThreadPool* thread_pool, void* out_image, jxl::ThreadPool* thread_pool, void* out_image,
size_t out_size, JxlImageOutCallback out_callback, size_t out_size, const PixelCallback& out_callback,
void* out_opaque, jxl::Orientation undo_orientation); jxl::Orientation undo_orientation);
// Converts single-channel image to interleaved void* pixel buffer with the // Converts single-channel image to interleaved void* pixel buffer with the
// given format, with a single channel. // given format, with a single channel.
// bits_per_sample: must be 8, 16 or 32, and must be 32 if float_out
// is true. 1 and 32 int are not yet implemented.
// This supports the features needed for the C API to get extra channels. // This supports the features needed for the C API to get extra channels.
// stride_out is output scanline size in bytes, must be >= // Arguments are similar to the multi-channel function above.
// output_xsize * output_bytes_per_pixel.
// undo_orientation is an EXIF orientation to undo. Depending on the
// orientation, the output xsize and ysize are swapped compared to input
// xsize and ysize.
Status ConvertToExternal(const jxl::ImageF& channel, size_t bits_per_sample, Status ConvertToExternal(const jxl::ImageF& channel, size_t bits_per_sample,
bool float_out, JxlEndianness endianness, bool float_out, JxlEndianness endianness,
size_t stride_out, jxl::ThreadPool* thread_pool, size_t stride_out, jxl::ThreadPool* thread_pool,
void* out_image, size_t out_size, void* out_image, size_t out_size,
JxlImageOutCallback out_callback, void* out_opaque, const PixelCallback& out_callback,
jxl::Orientation undo_orientation); jxl::Orientation undo_orientation);
} // namespace jxl } // namespace jxl

View File

@ -38,7 +38,7 @@ void BM_DecExternalImage_ConvertImageRGBA(benchmark::State& state) {
/*float_out=*/false, num_channels, JXL_NATIVE_ENDIAN, /*float_out=*/false, num_channels, JXL_NATIVE_ENDIAN,
/*stride*/ bytes_per_row, /*stride*/ bytes_per_row,
/*thread_pool=*/nullptr, interleaved.data(), interleaved.size(), /*thread_pool=*/nullptr, interleaved.data(), interleaved.size(),
/*out_callback=*/nullptr, /*out_opaque=*/nullptr, /*out_callback=*/{},
/*undo_orientation=*/jxl::Orientation::kIdentity)); /*undo_orientation=*/jxl::Orientation::kIdentity));
} }
} }

View File

@ -66,7 +66,23 @@ Status DecodeFile(const DecompressParams& dparams,
{ {
BitReader reader(file); BitReader reader(file);
BitReaderScopedCloser reader_closer(&reader, &ret); BitReaderScopedCloser reader_closer(&reader, &ret);
(void)reader.ReadFixedBits<16>(); // skip marker if (reader.ReadFixedBits<16>() != 0x0AFF) {
// We don't have a naked codestream. Make a quick & dirty attempt to find
// the codestream.
// TODO(jon): get rid of this whole function
const unsigned char* begin = file.data();
const unsigned char* end = file.data() + file.size() - 4;
while (begin < end) {
if (!memcmp(begin, "jxlc", 4)) break;
begin++;
}
if (begin >= end) return JXL_FAILURE("Couldn't find jxl codestream");
reader.SkipBits(8 * (begin - file.data() + 2));
unsigned int firstbytes = reader.ReadFixedBits<16>();
if (firstbytes != 0x0AFF)
return JXL_FAILURE("Codestream didn't start with FF0A but with %X",
firstbytes);
}
{ {
JXL_RETURN_IF_ERROR(DecodeHeaders(&reader, io)); JXL_RETURN_IF_ERROR(DecodeHeaders(&reader, io));

View File

@ -265,6 +265,7 @@ Status FrameDecoder::InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded,
decoded->duration = frame_header_.animation_frame.duration; decoded->duration = frame_header_.animation_frame.duration;
if (!frame_header_.nonserialized_is_preview && if (!frame_header_.nonserialized_is_preview &&
(frame_header_.is_last || frame_header_.animation_frame.duration > 0) &&
(frame_header_.frame_type == kRegularFrame || (frame_header_.frame_type == kRegularFrame ||
frame_header_.frame_type == kSkipProgressive)) { frame_header_.frame_type == kSkipProgressive)) {
++dec_state_->visible_frame_index; ++dec_state_->visible_frame_index;
@ -556,10 +557,11 @@ Status FrameDecoder::ProcessACGroup(size_t ac_group_id,
size_t num_passes, size_t thread, size_t num_passes, size_t thread,
bool force_draw, bool dc_only) { bool force_draw, bool dc_only) {
PROFILER_ZONE("process_group"); PROFILER_ZONE("process_group");
size_t group_dim = frame_dim_.group_dim;
const size_t gx = ac_group_id % frame_dim_.xsize_groups; const size_t gx = ac_group_id % frame_dim_.xsize_groups;
const size_t gy = ac_group_id / frame_dim_.xsize_groups; const size_t gy = ac_group_id / frame_dim_.xsize_groups;
const size_t x = gx * frame_dim_.group_dim; const size_t x = gx * group_dim;
const size_t y = gy * frame_dim_.group_dim; const size_t y = gy * group_dim;
RenderPipelineInput render_pipeline_input = RenderPipelineInput render_pipeline_input =
dec_state_->render_pipeline->GetInputBuffers(ac_group_id, thread); dec_state_->render_pipeline->GetInputBuffers(ac_group_id, thread);
@ -577,7 +579,7 @@ Status FrameDecoder::ProcessACGroup(size_t ac_group_id,
} }
// don't limit to image dimensions here (is done in DecodeGroup) // don't limit to image dimensions here (is done in DecodeGroup)
const Rect mrect(x, y, frame_dim_.group_dim, frame_dim_.group_dim); const Rect mrect(x, y, group_dim, group_dim);
for (size_t i = 0; i < frame_header_.passes.num_passes; i++) { for (size_t i = 0; i < frame_header_.passes.num_passes; i++) {
int minShift, maxShift; int minShift, maxShift;
frame_header_.passes.GetDownsamplingBracket(i, minShift, maxShift); frame_header_.passes.GetDownsamplingBracket(i, minShift, maxShift);
@ -612,14 +614,14 @@ Status FrameDecoder::ProcessACGroup(size_t ac_group_id,
rects[c].first = r.first; rects[c].first = r.first;
size_t x1 = r.second.x0() + r.second.xsize(); size_t x1 = r.second.x0() + r.second.xsize();
size_t y1 = r.second.y0() + r.second.ysize(); size_t y1 = r.second.y0() + r.second.ysize();
rects[c].second = Rect(r.second.x0() + ix * kGroupDim, rects[c].second = Rect(r.second.x0() + ix * group_dim,
r.second.y0() + iy * kGroupDim, kGroupDim, r.second.y0() + iy * group_dim, group_dim,
kGroupDim, x1, y1); group_dim, x1, y1);
} }
Random3Planes(dec_state_->visible_frame_index, Random3Planes(dec_state_->visible_frame_index,
dec_state_->nonvisible_frame_index, dec_state_->nonvisible_frame_index,
(gx * frame_header_.upsampling + ix) * kGroupDim, (gx * frame_header_.upsampling + ix) * group_dim,
(gy * frame_header_.upsampling + iy) * kGroupDim, (gy * frame_header_.upsampling + iy) * group_dim,
rects[0], rects[1], rects[2]); rects[0], rects[1], rects[2]);
} }
} }
@ -792,8 +794,8 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
JXL_RETURN_IF_ERROR(RunOnPool( JXL_RETURN_IF_ERROR(RunOnPool(
pool_, 0, ac_group_sec.size(), pool_, 0, ac_group_sec.size(),
[this](size_t num_threads) { [this](size_t num_threads) {
PrepareStorage(num_threads, decoded_passes_per_ac_group_.size()); return PrepareStorage(num_threads,
return true; decoded_passes_per_ac_group_.size());
}, },
[this, &ac_group_sec, &num_ac_passes, &num, &sections, &section_status, [this, &ac_group_sec, &num_ac_passes, &num, &sections, &section_status,
&has_error](size_t g, size_t thread) { &has_error](size_t g, size_t thread) {
@ -863,8 +865,8 @@ Status FrameDecoder::Flush() {
JXL_RETURN_IF_ERROR(RunOnPool( JXL_RETURN_IF_ERROR(RunOnPool(
pool_, 0, decoded_passes_per_ac_group_.size(), pool_, 0, decoded_passes_per_ac_group_.size(),
[this](const size_t num_threads) { [this](const size_t num_threads) {
PrepareStorage(num_threads, decoded_passes_per_ac_group_.size()); return PrepareStorage(num_threads,
return true; decoded_passes_per_ac_group_.size());
}, },
[this, &has_error](const uint32_t g, size_t thread) { [this, &has_error](const uint32_t g, size_t thread) {
if (decoded_passes_per_ac_group_[g] == if (decoded_passes_per_ac_group_[g] ==

View File

@ -8,6 +8,7 @@
#include <stdint.h> #include <stdint.h>
#include "jxl/decode.h"
#include "lib/jxl/base/compiler_specific.h" #include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/data_parallel.h" #include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/span.h" #include "lib/jxl/base/span.h"
@ -159,7 +160,7 @@ class FrameDecoder {
dec_state_->rgb_output = rgb_output; dec_state_->rgb_output = rgb_output;
dec_state_->rgb_output_is_rgba = is_rgba; dec_state_->rgb_output_is_rgba = is_rgba;
dec_state_->rgb_stride = stride; dec_state_->rgb_stride = stride;
JXL_ASSERT(dec_state_->pixel_callback == nullptr); JXL_ASSERT(!dec_state_->pixel_callback.IsPresent());
#if !JXL_HIGH_PRECISION #if !JXL_HIGH_PRECISION
if (decoded_->metadata()->xyb_encoded && if (decoded_->metadata()->xyb_encoded &&
dec_state_->output_encoding_info.color_encoding.IsSRGB() && dec_state_->output_encoding_info.color_encoding.IsSRGB() &&
@ -181,12 +182,10 @@ class FrameDecoder {
// orientation. Performing this operation is not yet supported, so this // orientation. Performing this operation is not yet supported, so this
// results in not setting the buffer if the image has a non-identity EXIF // results in not setting the buffer if the image has a non-identity EXIF
// orientation. When outputting to the ImageBundle, no orientation is undone. // orientation. When outputting to the ImageBundle, no orientation is undone.
void MaybeSetFloatCallback( void MaybeSetFloatCallback(const PixelCallback& pixel_callback, bool is_rgba,
const std::function<void(const float* pixels, size_t x, size_t y, bool undo_orientation) const {
size_t num_pixels)>& cb,
bool is_rgba, bool undo_orientation) const {
if (!CanDoLowMemoryPath(undo_orientation)) return; if (!CanDoLowMemoryPath(undo_orientation)) return;
dec_state_->pixel_callback = cb; dec_state_->pixel_callback = pixel_callback;
dec_state_->rgb_output_is_rgba = is_rgba; dec_state_->rgb_output_is_rgba = is_rgba;
JXL_ASSERT(dec_state_->rgb_output == nullptr); JXL_ASSERT(dec_state_->rgb_output == nullptr);
} }
@ -196,7 +195,7 @@ class FrameDecoder {
// callback has been used. // callback has been used.
bool HasRGBBuffer() const { bool HasRGBBuffer() const {
return dec_state_->rgb_output != nullptr || return dec_state_->rgb_output != nullptr ||
dec_state_->pixel_callback != nullptr; dec_state_->pixel_callback.IsPresent();
} }
private: private:
@ -216,18 +215,19 @@ class FrameDecoder {
// `GetStorageLocation` must be smaller than the `num_threads` value passed // `GetStorageLocation` must be smaller than the `num_threads` value passed
// here. The value of `task` passed to `GetStorageLocation` must be smaller // here. The value of `task` passed to `GetStorageLocation` must be smaller
// than the value of `num_tasks` passed here. // than the value of `num_tasks` passed here.
void PrepareStorage(size_t num_threads, size_t num_tasks) { Status PrepareStorage(size_t num_threads, size_t num_tasks) {
size_t storage_size = std::min(num_threads, num_tasks); size_t storage_size = std::min(num_threads, num_tasks);
if (storage_size > group_dec_caches_.size()) { if (storage_size > group_dec_caches_.size()) {
group_dec_caches_.resize(storage_size); group_dec_caches_.resize(storage_size);
} }
use_task_id_ = num_threads > num_tasks; use_task_id_ = num_threads > num_tasks;
if (dec_state_->render_pipeline) { if (dec_state_->render_pipeline) {
dec_state_->render_pipeline->PrepareForThreads( JXL_RETURN_IF_ERROR(dec_state_->render_pipeline->PrepareForThreads(
storage_size, storage_size,
/*use_group_ids=*/modular_frame_decoder_.UsesFullImage() && /*use_group_ids=*/modular_frame_decoder_.UsesFullImage() &&
frame_header_.encoding == FrameEncoding::kVarDCT); frame_header_.encoding == FrameEncoding::kVarDCT));
} }
return true;
} }
size_t GetStorageLocation(size_t thread, size_t task) { size_t GetStorageLocation(size_t thread, size_t task) {
@ -242,18 +242,8 @@ class FrameDecoder {
// (uint8 output buffer or float pixel callback). // (uint8 output buffer or float pixel callback).
// TODO(veluca): reduce this set of restrictions. // TODO(veluca): reduce this set of restrictions.
bool CanDoLowMemoryPath(bool undo_orientation) const { bool CanDoLowMemoryPath(bool undo_orientation) const {
if (undo_orientation && return !(undo_orientation &&
decoded_->metadata()->GetOrientation() != Orientation::kIdentity) { decoded_->metadata()->GetOrientation() != Orientation::kIdentity);
return false;
}
if (NeedsBlending(dec_state_)) return false;
if (frame_header_.CanBeReferenced()) return false;
if (render_spotcolors_ &&
decoded_->metadata()->Find(ExtraChannel::kSpotColor)) {
return false;
}
if (decoded_->AlphaIsPremultiplied()) return false;
return true;
} }
PassesDecoderState* dec_state_; PassesDecoderState* dec_state_;

View File

@ -740,7 +740,7 @@ Status DecodeGroup(BitReader* JXL_RESTRICT* JXL_RESTRICT readers,
// Arguments set to 0/nullptr are not used. // Arguments set to 0/nullptr are not used.
dec_state->upsampler8x->ProcessRow(input_rows, output_rows, dec_state->upsampler8x->ProcessRow(input_rows, output_rows,
/*xextra=*/0, src_rect.xsize(), 0, 0, /*xextra=*/0, src_rect.xsize(), 0, 0,
nullptr); thread);
} }
} }
return true; return true;

View File

@ -89,6 +89,16 @@ HWY_EXPORT(MultiplySum); // Local function
HWY_EXPORT(RgbFromSingle); // Local function HWY_EXPORT(RgbFromSingle); // Local function
HWY_EXPORT(SingleFromSingle); // Local function HWY_EXPORT(SingleFromSingle); // Local function
// Slow conversion using double precision multiplication, only
// needed when the bit depth is too high for single precision
void SingleFromSingleAccurate(const size_t xsize,
const pixel_type* const JXL_RESTRICT row_in,
const double factor, float* row_out) {
for (size_t x = 0; x < xsize; x++) {
row_out[x] = row_in[x] * factor;
}
}
// convert custom [bits]-bit float (with [exp_bits] exponent bits) stored as int // convert custom [bits]-bit float (with [exp_bits] exponent bits) stored as int
// back to binary32 float // back to binary32 float
void int_to_float(const pixel_type* const JXL_RESTRICT row_in, void int_to_float(const pixel_type* const JXL_RESTRICT row_in,
@ -457,9 +467,9 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
const auto* metadata = frame_header.nonserialized_metadata; const auto* metadata = frame_header.nonserialized_metadata;
JXL_CHECK(gi.transform.empty()); JXL_CHECK(gi.transform.empty());
auto get_row = [&](Rect r, size_t c, size_t y) { auto get_row = [&](size_t c, size_t y) {
return render_pipeline_input.GetBuffer(c).second.Row( const auto& buffer = render_pipeline_input.GetBuffer(c);
render_pipeline_input.GetBuffer(c).first, y); return buffer.second.Row(buffer.first, y);
}; };
size_t c = 0; size_t c = 0;
@ -470,9 +480,9 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
const bool fp = metadata->m.bit_depth.floating_point_sample && const bool fp = metadata->m.bit_depth.floating_point_sample &&
frame_header.color_transform != ColorTransform::kXYB; frame_header.color_transform != ColorTransform::kXYB;
for (; c < 3; c++) { for (; c < 3; c++) {
float factor = full_image.bitdepth < 32 double factor = full_image.bitdepth < 32
? 1.f / ((1u << full_image.bitdepth) - 1) ? 1.0 / ((1u << full_image.bitdepth) - 1)
: 0; : 0;
size_t c_in = c; size_t c_in = c;
if (frame_header.color_transform == ColorTransform::kXYB) { if (frame_header.color_transform == ColorTransform::kXYB) {
factor = dec_state->shared->matrices.DCQuants()[c]; factor = dec_state->shared->matrices.DCQuants()[c];
@ -513,7 +523,7 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
mr.Row(&ch_in.plane, y); mr.Row(&ch_in.plane, y);
const pixel_type* const JXL_RESTRICT row_in_Y = const pixel_type* const JXL_RESTRICT row_in_Y =
mr.Row(&gi.channel[0].plane, y); mr.Row(&gi.channel[0].plane, y);
float* const JXL_RESTRICT row_out = get_row(r, c, y); float* const JXL_RESTRICT row_out = get_row(c, y);
HWY_DYNAMIC_DISPATCH(MultiplySum) HWY_DYNAMIC_DISPATCH(MultiplySum)
(xsize_shifted, row_in, row_in_Y, factor, row_out); (xsize_shifted, row_in, row_in_Y, factor, row_out);
}, },
@ -529,11 +539,11 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
mr.Row(&ch_in.plane, y); mr.Row(&ch_in.plane, y);
if (rgb_from_gray) { if (rgb_from_gray) {
for (size_t cc = 0; cc < 3; cc++) { for (size_t cc = 0; cc < 3; cc++) {
float* const JXL_RESTRICT row_out = get_row(r, cc, y); float* const JXL_RESTRICT row_out = get_row(cc, y);
int_to_float(row_in, row_out, xsize_shifted, bits, exp_bits); int_to_float(row_in, row_out, xsize_shifted, bits, exp_bits);
} }
} else { } else {
float* const JXL_RESTRICT row_out = get_row(r, c, y); float* const JXL_RESTRICT row_out = get_row(c, y);
int_to_float(row_in, row_out, xsize_shifted, bits, exp_bits); int_to_float(row_in, row_out, xsize_shifted, bits, exp_bits);
} }
}, },
@ -546,13 +556,27 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
const pixel_type* const JXL_RESTRICT row_in = const pixel_type* const JXL_RESTRICT row_in =
mr.Row(&ch_in.plane, y); mr.Row(&ch_in.plane, y);
if (rgb_from_gray) { if (rgb_from_gray) {
HWY_DYNAMIC_DISPATCH(RgbFromSingle) if (full_image.bitdepth < 23) {
(xsize_shifted, row_in, factor, get_row(r, 0, y), HWY_DYNAMIC_DISPATCH(RgbFromSingle)
get_row(r, 1, y), get_row(r, 2, y)); (xsize_shifted, row_in, factor, get_row(0, y), get_row(1, y),
get_row(2, y));
} else {
SingleFromSingleAccurate(xsize_shifted, row_in, factor,
get_row(0, y));
SingleFromSingleAccurate(xsize_shifted, row_in, factor,
get_row(1, y));
SingleFromSingleAccurate(xsize_shifted, row_in, factor,
get_row(2, y));
}
} else { } else {
float* const JXL_RESTRICT row_out = get_row(r, c, y); float* const JXL_RESTRICT row_out = get_row(c, y);
HWY_DYNAMIC_DISPATCH(SingleFromSingle) if (full_image.bitdepth < 23) {
(xsize_shifted, row_in, factor, row_out); HWY_DYNAMIC_DISPATCH(SingleFromSingle)
(xsize_shifted, row_in, factor, row_out);
} else {
SingleFromSingleAccurate(xsize_shifted, row_in, factor,
row_out);
}
} }
}, },
"ModularIntToFloat")); "ModularIntToFloat"));
@ -572,7 +596,7 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
int exp_bits = eci.bit_depth.exponent_bits_per_sample; int exp_bits = eci.bit_depth.exponent_bits_per_sample;
bool fp = eci.bit_depth.floating_point_sample; bool fp = eci.bit_depth.floating_point_sample;
JXL_ASSERT(fp || bits < 32); JXL_ASSERT(fp || bits < 32);
const float mul = fp ? 0 : (1.0f / ((1u << bits) - 1)); const double factor = fp ? 0 : (1.0 / ((1u << bits) - 1));
JXL_ASSERT(c < gi.channel.size()); JXL_ASSERT(c < gi.channel.size());
Channel& ch_in = gi.channel[c]; Channel& ch_in = gi.channel[c];
Rect r = render_pipeline_input.GetBuffer(3 + ec).second; Rect r = render_pipeline_input.GetBuffer(3 + ec).second;
@ -589,8 +613,11 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
if (fp) { if (fp) {
int_to_float(row_in, row_out, r.xsize(), bits, exp_bits); int_to_float(row_in, row_out, r.xsize(), bits, exp_bits);
} else { } else {
for (size_t x = 0; x < r.xsize(); ++x) { if (full_image.bitdepth < 23) {
row_out[x] = row_in[x] * mul; HWY_DYNAMIC_DISPATCH(SingleFromSingle)
(r.xsize(), row_in, factor, row_out);
} else {
SingleFromSingleAccurate(r.xsize(), row_in, factor, row_out);
} }
} }
} }
@ -621,11 +648,10 @@ Status ModularFrameDecoder::FinalizeDecoding(PassesDecoderState* dec_state,
JXL_RETURN_IF_ERROR(RunOnPool( JXL_RETURN_IF_ERROR(RunOnPool(
pool, 0, dec_state->shared->frame_dim.num_groups, pool, 0, dec_state->shared->frame_dim.num_groups,
[&](size_t num_threads) { [&](size_t num_threads) {
dec_state->render_pipeline->PrepareForThreads( return dec_state->render_pipeline->PrepareForThreads(
num_threads, num_threads,
/*use_group_ids=*/dec_state->shared->frame_header.encoding == /*use_group_ids=*/dec_state->shared->frame_header.encoding ==
FrameEncoding::kVarDCT); FrameEncoding::kVarDCT);
return true;
}, },
[&](const uint32_t group, size_t thread_id) { [&](const uint32_t group, size_t thread_id) {
RenderPipelineInput input = RenderPipelineInput input =

View File

@ -85,182 +85,6 @@ void RandomImage(Xorshift128Plus* rng, const Rect& rect,
} }
} }
} }
// [0, max_value]
template <class D, class V>
static HWY_INLINE V Clamp0ToMax(D d, const V x, const V max_value) {
const auto clamped = Min(x, max_value);
return ZeroIfNegative(clamped);
}
// x is in [0+delta, 1+delta], delta ~= 0.06
template <class StrengthEval>
typename StrengthEval::V NoiseStrength(const StrengthEval& eval,
const typename StrengthEval::V x) {
return Clamp0ToMax(D(), eval(x), Set(D(), 1.0f));
}
// TODO(veluca): SIMD-fy.
class StrengthEvalLut {
public:
using V = Vec<D>;
explicit StrengthEvalLut(const NoiseParams& noise_params)
#if HWY_TARGET == HWY_SCALAR
: noise_params_(noise_params)
#endif
{
#if HWY_TARGET != HWY_SCALAR
uint32_t lut[8];
memcpy(lut, noise_params.lut, sizeof(lut));
for (size_t i = 0; i < 8; i++) {
low16_lut[2 * i] = (lut[i] >> 0) & 0xFF;
low16_lut[2 * i + 1] = (lut[i] >> 8) & 0xFF;
high16_lut[2 * i] = (lut[i] >> 16) & 0xFF;
high16_lut[2 * i + 1] = (lut[i] >> 24) & 0xFF;
}
#endif
}
V operator()(const V vx) const {
constexpr size_t kScale = NoiseParams::kNumNoisePoints - 2;
auto scaled_vx = Max(Zero(D()), vx * Set(D(), kScale));
auto floor_x = Floor(scaled_vx);
auto frac_x = scaled_vx - floor_x;
floor_x = IfThenElse(scaled_vx >= Set(D(), kScale + 1), Set(D(), kScale),
floor_x);
frac_x = IfThenElse(scaled_vx >= Set(D(), kScale + 1), Set(D(), 1), frac_x);
auto floor_x_int = ConvertTo(DI(), floor_x);
#if HWY_TARGET == HWY_SCALAR
auto low = Set(D(), noise_params_.lut[floor_x_int.raw]);
auto hi = Set(D(), noise_params_.lut[floor_x_int.raw + 1]);
#else
// Set each lane's bytes to {0, 0, 2x+1, 2x}.
auto floorx_indices_low =
floor_x_int * Set(DI(), 0x0202) + Set(DI(), 0x0100);
// Set each lane's bytes to {2x+1, 2x, 0, 0}.
auto floorx_indices_hi =
floor_x_int * Set(DI(), 0x02020000) + Set(DI(), 0x01000000);
// load LUT
auto low16 = BitCast(DI(), LoadDup128(DI8(), low16_lut));
auto lowm = Set(DI(), 0xFFFF);
auto hi16 = BitCast(DI(), LoadDup128(DI8(), high16_lut));
auto him = Set(DI(), 0xFFFF0000);
// low = noise_params.lut[floor_x]
auto low =
BitCast(D(), (TableLookupBytes(low16, floorx_indices_low) & lowm) |
(TableLookupBytes(hi16, floorx_indices_hi) & him));
// hi = noise_params.lut[floor_x+1]
floorx_indices_low += Set(DI(), 0x0202);
floorx_indices_hi += Set(DI(), 0x02020000);
auto hi =
BitCast(D(), (TableLookupBytes(low16, floorx_indices_low) & lowm) |
(TableLookupBytes(hi16, floorx_indices_hi) & him));
#endif
return MulAdd(hi - low, frac_x, low);
}
private:
#if HWY_TARGET != HWY_SCALAR
// noise_params.lut transformed into two 16-bit lookup tables.
HWY_ALIGN uint8_t high16_lut[16];
HWY_ALIGN uint8_t low16_lut[16];
#else
const NoiseParams& noise_params_;
#endif
};
template <class D>
void AddNoiseToRGB(const D d, const Vec<D> rnd_noise_r,
const Vec<D> rnd_noise_g, const Vec<D> rnd_noise_cor,
const Vec<D> noise_strength_g, const Vec<D> noise_strength_r,
float ytox, float ytob, float* JXL_RESTRICT out_x,
float* JXL_RESTRICT out_y, float* JXL_RESTRICT out_b) {
const auto kRGCorr = Set(d, 0.9921875f); // 127/128
const auto kRGNCorr = Set(d, 0.0078125f); // 1/128
const auto red_noise = kRGNCorr * rnd_noise_r * noise_strength_r +
kRGCorr * rnd_noise_cor * noise_strength_r;
const auto green_noise = kRGNCorr * rnd_noise_g * noise_strength_g +
kRGCorr * rnd_noise_cor * noise_strength_g;
auto vx = Load(d, out_x);
auto vy = Load(d, out_y);
auto vb = Load(d, out_b);
vx += red_noise - green_noise + Set(d, ytox) * (red_noise + green_noise);
vy += red_noise + green_noise;
vb += Set(d, ytob) * (red_noise + green_noise);
Store(vx, d, out_x);
Store(vy, d, out_y);
Store(vb, d, out_b);
}
void AddNoise(const NoiseParams& noise_params, const Rect& noise_rect,
const Image3F& noise, const Rect& opsin_rect,
const ColorCorrelationMap& cmap, Image3F* opsin) {
if (!noise_params.HasAny()) return;
const StrengthEvalLut noise_model(noise_params);
D d;
const auto half = Set(d, 0.5f);
const size_t xsize = opsin_rect.xsize();
const size_t ysize = opsin_rect.ysize();
// With the prior subtract-random Laplacian approximation, rnd_* ranges were
// about [-1.5, 1.6]; Laplacian3 about doubles this to [-3.6, 3.6], so the
// normalizer is half of what it was before (0.5).
const auto norm_const = Set(d, 0.22f);
float ytox = cmap.YtoXRatio(0);
float ytob = cmap.YtoBRatio(0);
const size_t xsize_v = RoundUpTo(xsize, Lanes(d));
for (size_t y = 0; y < ysize; ++y) {
float* JXL_RESTRICT row_x = opsin_rect.PlaneRow(opsin, 0, y);
float* JXL_RESTRICT row_y = opsin_rect.PlaneRow(opsin, 1, y);
float* JXL_RESTRICT row_b = opsin_rect.PlaneRow(opsin, 2, y);
const float* JXL_RESTRICT row_rnd_r = noise_rect.ConstPlaneRow(noise, 0, y);
const float* JXL_RESTRICT row_rnd_g = noise_rect.ConstPlaneRow(noise, 1, y);
const float* JXL_RESTRICT row_rnd_c = noise_rect.ConstPlaneRow(noise, 2, y);
// Needed by the calls to Floor() in StrengthEvalLut. Only arithmetic and
// shuffles are otherwise done on the data, so this is safe.
msan::UnpoisonMemory(row_x + xsize, (xsize_v - xsize) * sizeof(float));
msan::UnpoisonMemory(row_y + xsize, (xsize_v - xsize) * sizeof(float));
for (size_t x = 0; x < xsize; x += Lanes(d)) {
const auto vx = Load(d, row_x + x);
const auto vy = Load(d, row_y + x);
const auto in_g = vy - vx;
const auto in_r = vy + vx;
const auto noise_strength_g = NoiseStrength(noise_model, in_g * half);
const auto noise_strength_r = NoiseStrength(noise_model, in_r * half);
const auto addit_rnd_noise_red = Load(d, row_rnd_r + x) * norm_const;
const auto addit_rnd_noise_green = Load(d, row_rnd_g + x) * norm_const;
const auto addit_rnd_noise_correlated =
Load(d, row_rnd_c + x) * norm_const;
AddNoiseToRGB(D(), addit_rnd_noise_red, addit_rnd_noise_green,
addit_rnd_noise_correlated, noise_strength_g,
noise_strength_r, ytox, ytob, row_x + x, row_y + x,
row_b + x);
}
msan::PoisonMemory(row_x + xsize, (xsize_v - xsize) * sizeof(float));
msan::PoisonMemory(row_y + xsize, (xsize_v - xsize) * sizeof(float));
msan::PoisonMemory(row_b + xsize, (xsize_v - xsize) * sizeof(float));
}
}
void RandomImage3(size_t visible_frame_index, size_t nonvisible_frame_index,
size_t x0, size_t y0, const Rect& rect,
Image3F* JXL_RESTRICT noise) {
HWY_ALIGN Xorshift128Plus rng(visible_frame_index, nonvisible_frame_index, x0,
y0);
RandomImage(&rng, rect, &noise->Plane(0));
RandomImage(&rng, rect, &noise->Plane(1));
RandomImage(&rng, rect, &noise->Plane(2));
}
void Random3Planes(size_t visible_frame_index, size_t nonvisible_frame_index, void Random3Planes(size_t visible_frame_index, size_t nonvisible_frame_index,
size_t x0, size_t y0, const std::pair<ImageF*, Rect>& plane0, size_t x0, size_t y0, const std::pair<ImageF*, Rect>& plane0,
const std::pair<ImageF*, Rect>& plane1, const std::pair<ImageF*, Rect>& plane1,
@ -280,22 +104,6 @@ HWY_AFTER_NAMESPACE();
#if HWY_ONCE #if HWY_ONCE
namespace jxl { namespace jxl {
HWY_EXPORT(AddNoise);
void AddNoise(const NoiseParams& noise_params, const Rect& noise_rect,
const Image3F& noise, const Rect& opsin_rect,
const ColorCorrelationMap& cmap, Image3F* opsin) {
return HWY_DYNAMIC_DISPATCH(AddNoise)(noise_params, noise_rect, noise,
opsin_rect, cmap, opsin);
}
HWY_EXPORT(RandomImage3);
void RandomImage3(size_t visible_frame_index, size_t nonvisible_frame_index,
size_t x0, size_t y0, const Rect& rect,
Image3F* JXL_RESTRICT noise) {
return HWY_DYNAMIC_DISPATCH(RandomImage3)(
visible_frame_index, nonvisible_frame_index, x0, y0, rect, noise);
}
HWY_EXPORT(Random3Planes); HWY_EXPORT(Random3Planes);
void Random3Planes(size_t visible_frame_index, size_t nonvisible_frame_index, void Random3Planes(size_t visible_frame_index, size_t nonvisible_frame_index,
size_t x0, size_t y0, const std::pair<ImageF*, Rect>& plane0, size_t x0, size_t y0, const std::pair<ImageF*, Rect>& plane0,

View File

@ -20,16 +20,6 @@
namespace jxl { namespace jxl {
// Add a noise to Opsin image, loading generated random noise from `noise_rect`
// in `noise`.
void AddNoise(const NoiseParams& noise_params, const Rect& noise_rect,
const Image3F& noise, const Rect& opsin_rect,
const ColorCorrelationMap& cmap, Image3F* opsin);
void RandomImage3(size_t visible_frame_index, size_t nonvisible_frame_index,
size_t x0, size_t y0, const Rect& rect,
Image3F* JXL_RESTRICT noise);
void Random3Planes(size_t visible_frame_index, size_t nonvisible_frame_index, void Random3Planes(size_t visible_frame_index, size_t nonvisible_frame_index,
size_t x0, size_t y0, const std::pair<ImageF*, Rect>& plane0, size_t x0, size_t y0, const std::pair<ImageF*, Rect>& plane0,
const std::pair<ImageF*, Rect>& plane1, const std::pair<ImageF*, Rect>& plane1,

View File

@ -156,9 +156,6 @@ Status PatchDictionary::Decode(BitReader* br, size_t xsize, size_t ysize,
if (!decoder.CheckANSFinalState()) { if (!decoder.CheckANSFinalState()) {
return JXL_FAILURE("ANS checksum failure."); return JXL_FAILURE("ANS checksum failure.");
} }
if (!HasAny()) {
return JXL_FAILURE("Decoded patch dictionary but got none");
}
ComputePatchCache(); ComputePatchCache();
return true; return true;
@ -237,51 +234,4 @@ void PatchDictionary::AddOneRow(float* const* inout, size_t y, size_t x0,
shared_->metadata->m.extra_channel_info); shared_->metadata->m.extra_channel_info);
} }
} }
void PatchDictionary::AddTo(Image3F* opsin, const Rect& opsin_rect,
float* const* extra_channels,
const Rect& image_rect) const {
JXL_CHECK(SameSize(opsin_rect, image_rect));
if (patch_starts_.empty()) return;
size_t num_ec = shared_->metadata->m.num_extra_channels;
std::vector<const float*> fg_ptrs(3 + num_ec);
std::vector<float*> bg_ptrs(3 + num_ec);
for (size_t y = image_rect.y0(); y < image_rect.y0() + image_rect.ysize();
y++) {
if (y + 1 >= patch_starts_.size()) continue;
for (size_t id = patch_starts_[y]; id < patch_starts_[y + 1]; id++) {
const PatchPosition& pos = positions_[sorted_patches_[id]];
size_t by = pos.y;
size_t bx = pos.x;
size_t xsize = pos.ref_pos.xsize;
JXL_DASSERT(y >= by);
JXL_DASSERT(y < by + pos.ref_pos.ysize);
size_t iy = y - by;
size_t ref = pos.ref_pos.ref;
if (bx >= image_rect.x0() + image_rect.xsize()) continue;
if (bx + xsize < image_rect.x0()) continue;
size_t x0 = std::max(bx, image_rect.x0());
size_t x1 = std::min(bx + xsize, image_rect.x0() + image_rect.xsize());
for (size_t c = 0; c < 3; c++) {
fg_ptrs[c] =
shared_->reference_frames[ref].frame->color()->ConstPlaneRow(
c, pos.ref_pos.y0 + iy) +
pos.ref_pos.x0 + x0 - bx;
bg_ptrs[c] = opsin_rect.PlaneRow(opsin, c, y - image_rect.y0()) + x0 -
image_rect.x0();
}
for (size_t i = 0; i < num_ec; i++) {
fg_ptrs[3 + i] =
shared_->reference_frames[ref].frame->extra_channels()[i].ConstRow(
pos.ref_pos.y0 + iy) +
pos.ref_pos.x0 + x0 - bx;
bg_ptrs[3 + i] = extra_channels[i] + x0 - image_rect.x0();
}
PerformBlending(bg_ptrs.data(), fg_ptrs.data(), bg_ptrs.data(), 0,
x1 - x0, pos.blending[0], pos.blending.data() + 1,
shared_->metadata->m.extra_channel_info);
}
}
}
} // namespace jxl } // namespace jxl

View File

@ -126,11 +126,6 @@ class PatchDictionary {
// to be located at position (x0, y) in the frame. // to be located at position (x0, y) in the frame.
void AddOneRow(float* const* inout, size_t y, size_t x0, size_t xsize) const; void AddOneRow(float* const* inout, size_t y, size_t x0, size_t xsize) const;
// Only adds patches that belong to the `image_rect` area of the decoded
// image, writing them to the `opsin_rect` area of `opsin`.
void AddTo(Image3F* opsin, const Rect& opsin_rect,
float* const* extra_channels, const Rect& image_rect) const;
// Returns dependencies of this patch dictionary on reference frame ids as a // Returns dependencies of this patch dictionary on reference frame ids as a
// bit mask: bits 0-3 indicate reference frame 0-3. // bit mask: bits 0-3 indicate reference frame 0-3.
int GetReferences() const; int GetReferences() const;

View File

@ -139,21 +139,17 @@ namespace {
size_t BitsPerChannel(JxlDataType data_type) { size_t BitsPerChannel(JxlDataType data_type) {
switch (data_type) { switch (data_type) {
case JXL_TYPE_BOOLEAN:
return 1;
case JXL_TYPE_UINT8: case JXL_TYPE_UINT8:
return 8; return 8;
case JXL_TYPE_UINT16: case JXL_TYPE_UINT16:
return 16; return 16;
case JXL_TYPE_UINT32:
return 32;
case JXL_TYPE_FLOAT: case JXL_TYPE_FLOAT:
return 32; return 32;
case JXL_TYPE_FLOAT16: case JXL_TYPE_FLOAT16:
return 16; return 16;
// No default, give compiler error if new type not handled. default:
return 0; // signals unhandled JxlDataType
} }
return 0; // Indicate invalid data type.
} }
enum class DecoderStage : uint32_t { enum class DecoderStage : uint32_t {
@ -465,8 +461,15 @@ struct JxlDecoderStruct {
// Owned by the caller, buffers for DC image and full resolution images // Owned by the caller, buffers for DC image and full resolution images
void* preview_out_buffer; void* preview_out_buffer;
void* image_out_buffer; void* image_out_buffer;
JxlImageOutCallback image_out_callback; JxlImageOutInitCallback image_out_init_callback;
void* image_out_opaque; JxlImageOutRunCallback image_out_run_callback;
JxlImageOutDestroyCallback image_out_destroy_callback;
void* image_out_init_opaque;
struct SimpleImageOutCallback {
JxlImageOutCallback callback;
void* opaque;
};
SimpleImageOutCallback simple_image_out_callback;
size_t preview_out_size; size_t preview_out_size;
size_t image_out_size; size_t image_out_size;
@ -609,6 +612,11 @@ namespace {
bool CheckSizeLimit(JxlDecoder* dec, size_t xsize, size_t ysize) { bool CheckSizeLimit(JxlDecoder* dec, size_t xsize, size_t ysize) {
if (!dec->memory_limit_base) return true; if (!dec->memory_limit_base) return true;
if (xsize == 0 || ysize == 0) return true; if (xsize == 0 || ysize == 0) return true;
if (xsize >= dec->memory_limit_base || ysize >= dec->memory_limit_base) {
return false;
}
// Rough estimate of real row length.
xsize = jxl::DivCeil(xsize, 32) * 32;
size_t num_pixels = xsize * ysize; size_t num_pixels = xsize * ysize;
if (num_pixels / xsize != ysize) return false; // overflow if (num_pixels / xsize != ysize) return false; // overflow
if (num_pixels > dec->memory_limit_base) return false; if (num_pixels > dec->memory_limit_base) return false;
@ -670,8 +678,10 @@ void JxlDecoderRewindDecodingState(JxlDecoder* dec) {
dec->image_out_buffer_set = false; dec->image_out_buffer_set = false;
dec->preview_out_buffer = nullptr; dec->preview_out_buffer = nullptr;
dec->image_out_buffer = nullptr; dec->image_out_buffer = nullptr;
dec->image_out_callback = nullptr; dec->image_out_init_callback = nullptr;
dec->image_out_opaque = nullptr; dec->image_out_run_callback = nullptr;
dec->image_out_destroy_callback = nullptr;
dec->image_out_init_opaque = nullptr;
dec->preview_out_size = 0; dec->preview_out_size = 0;
dec->image_out_size = 0; dec->image_out_size = 0;
dec->extra_channel_output.clear(); dec->extra_channel_output.clear();
@ -731,7 +741,7 @@ JxlDecoder* JxlDecoderCreate(const JxlMemoryManager* memory_manager) {
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (!memory_manager) { if (!memory_manager) {
dec->memory_limit_base = 1 << 21; dec->memory_limit_base = 53 << 16;
// Allow 5 x max_image_size processing units; every frame is accounted // Allow 5 x max_image_size processing units; every frame is accounted
// as W x H CPU processing units, so there could be numerous small frames // as W x H CPU processing units, so there could be numerous small frames
// or few larger ones. // or few larger ones.
@ -746,9 +756,10 @@ JxlDecoder* JxlDecoderCreate(const JxlMemoryManager* memory_manager) {
void JxlDecoderDestroy(JxlDecoder* dec) { void JxlDecoderDestroy(JxlDecoder* dec) {
if (dec) { if (dec) {
JxlMemoryManager local_memory_manager = dec->memory_manager;
// Call destructor directly since custom free function is used. // Call destructor directly since custom free function is used.
dec->~JxlDecoder(); dec->~JxlDecoder();
jxl::MemoryManagerFree(&dec->memory_manager, dec); jxl::MemoryManagerFree(&local_memory_manager, dec);
} }
} }
@ -1051,7 +1062,7 @@ static JxlDecoderStatus ConvertImageInternal(
const JxlDecoder* dec, const jxl::ImageBundle& frame, const JxlDecoder* dec, const jxl::ImageBundle& frame,
const JxlPixelFormat& format, bool want_extra_channel, const JxlPixelFormat& format, bool want_extra_channel,
size_t extra_channel_index, void* out_image, size_t out_size, size_t extra_channel_index, void* out_image, size_t out_size,
JxlImageOutCallback out_callback, void* out_opaque) { const PixelCallback& out_callback) {
// TODO(lode): handle mismatch of RGB/grayscale color profiles and pixel data // TODO(lode): handle mismatch of RGB/grayscale color profiles and pixel data
// color/grayscale format // color/grayscale format
const size_t stride = GetStride(dec, format); const size_t stride = GetStride(dec, format);
@ -1065,19 +1076,17 @@ static JxlDecoderStatus ConvertImageInternal(
jxl::Status status(true); jxl::Status status(true);
if (want_extra_channel) { if (want_extra_channel) {
status = jxl::ConvertToExternal( JXL_ASSERT(extra_channel_index < frame.extra_channels().size());
frame.extra_channels()[extra_channel_index], status = jxl::ConvertToExternal(frame.extra_channels()[extra_channel_index],
BitsPerChannel(format.data_type), float_format, format.endianness, BitsPerChannel(format.data_type),
stride, dec->thread_pool.get(), out_image, out_size, float_format, format.endianness, stride,
/*out_callback=*/out_callback, dec->thread_pool.get(), out_image, out_size,
/*out_opaque=*/out_opaque, undo_orientation); out_callback, undo_orientation);
} else { } else {
status = jxl::ConvertToExternal( status = jxl::ConvertToExternal(
frame, BitsPerChannel(format.data_type), float_format, frame, BitsPerChannel(format.data_type), float_format,
format.num_channels, format.endianness, stride, dec->thread_pool.get(), format.num_channels, format.endianness, stride, dec->thread_pool.get(),
out_image, out_size, out_image, out_size, out_callback, undo_orientation);
/*out_callback=*/out_callback,
/*out_opaque=*/out_opaque, undo_orientation);
} }
return status ? JXL_DEC_SUCCESS : JXL_DEC_ERROR; return status ? JXL_DEC_SUCCESS : JXL_DEC_ERROR;
@ -1252,8 +1261,8 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
JxlDecoderStatus status = ConvertImageInternal( JxlDecoderStatus status = ConvertImageInternal(
dec, ib, dec->preview_out_format, /*want_extra_channel=*/false, dec, ib, dec->preview_out_format, /*want_extra_channel=*/false,
/*extra_channel_index=*/0, dec->preview_out_buffer, /*extra_channel_index=*/0, dec->preview_out_buffer,
dec->preview_out_size, /*out_callback=*/nullptr, dec->preview_out_size,
/*out_opaque=*/nullptr); /*out_callback=*/{});
if (status != JXL_DEC_SUCCESS) return status; if (status != JXL_DEC_SUCCESS) return status;
} }
return JXL_DEC_PREVIEW_IMAGE; return JXL_DEC_PREVIEW_IMAGE;
@ -1453,17 +1462,16 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
// TODO(lode): Support more formats than just native endian float32 for // TODO(lode): Support more formats than just native endian float32 for
// the low-memory callback path // the low-memory callback path
if (dec->image_out_buffer_set && !!dec->image_out_callback && if (dec->image_out_buffer_set && !!dec->image_out_init_callback &&
!!dec->image_out_run_callback &&
dec->image_out_format.data_type == JXL_TYPE_FLOAT && dec->image_out_format.data_type == JXL_TYPE_FLOAT &&
dec->image_out_format.num_channels >= 3 && !swap_endianness && dec->image_out_format.num_channels >= 3 && !swap_endianness &&
dec->frame_dec_in_progress) { dec->frame_dec_in_progress) {
bool is_rgba = dec->image_out_format.num_channels == 4; bool is_rgba = dec->image_out_format.num_channels == 4;
dec->frame_dec->MaybeSetFloatCallback( dec->frame_dec->MaybeSetFloatCallback(
[dec](const float* pixels, size_t x, size_t y, size_t num_pixels) { PixelCallback{
JXL_DASSERT(num_pixels > 0); dec->image_out_init_callback, dec->image_out_run_callback,
dec->image_out_callback(dec->image_out_opaque, x, y, num_pixels, dec->image_out_destroy_callback, dec->image_out_init_opaque},
pixels);
},
is_rgba, !dec->keep_orientation); is_rgba, !dec->keep_orientation);
} }
@ -1557,21 +1565,30 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
dec, *dec->ib, dec->image_out_format, dec, *dec->ib, dec->image_out_format,
/*want_extra_channel=*/false, /*want_extra_channel=*/false,
/*extra_channel_index=*/0, dec->image_out_buffer, /*extra_channel_index=*/0, dec->image_out_buffer,
dec->image_out_size, dec->image_out_callback, dec->image_out_size,
dec->image_out_opaque); PixelCallback{dec->image_out_init_callback,
dec->image_out_run_callback,
dec->image_out_destroy_callback,
dec->image_out_init_opaque});
if (status != JXL_DEC_SUCCESS) return status; if (status != JXL_DEC_SUCCESS) return status;
} }
dec->image_out_buffer_set = false; dec->image_out_buffer_set = false;
bool has_ec = !dec->ib->extra_channels().empty();
for (size_t i = 0; i < dec->extra_channel_output.size(); ++i) { for (size_t i = 0; i < dec->extra_channel_output.size(); ++i) {
void* buffer = dec->extra_channel_output[i].buffer; void* buffer = dec->extra_channel_output[i].buffer;
// buffer nullptr indicates this extra channel is not requested // buffer nullptr indicates this extra channel is not requested
if (!buffer) continue; if (!buffer) continue;
if (!has_ec) {
JXL_WARNING(
"Extra channels are not supported when callback is used");
return JXL_DEC_ERROR;
}
const JxlPixelFormat* format = &dec->extra_channel_output[i].format; const JxlPixelFormat* format = &dec->extra_channel_output[i].format;
JxlDecoderStatus status = ConvertImageInternal( JxlDecoderStatus status = ConvertImageInternal(
dec, *dec->ib, *format, dec, *dec->ib, *format,
/*want_extra_channel=*/true, i, buffer, /*want_extra_channel=*/true, /*extra_channel_index=*/i, buffer,
dec->extra_channel_output[i].buffer_size, nullptr, nullptr); dec->extra_channel_output[i].buffer_size, /*out_callback=*/{});
if (status != JXL_DEC_SUCCESS) return status; if (status != JXL_DEC_SUCCESS) return status;
} }
@ -2385,17 +2402,11 @@ JxlDecoderStatus PrepareSizeCheck(const JxlDecoder* dec,
if (format->num_channels > 4) { if (format->num_channels > 4) {
return JXL_API_ERROR("More than 4 channels not supported"); return JXL_API_ERROR("More than 4 channels not supported");
} }
if (format->data_type == JXL_TYPE_BOOLEAN) {
return JXL_API_ERROR("Boolean data type not yet supported");
}
if (format->data_type == JXL_TYPE_UINT32) {
return JXL_API_ERROR("uint32 data type not yet supported");
}
*bits = BitsPerChannel(format->data_type); *bits = BitsPerChannel(format->data_type);
if (*bits == 0) { if (*bits == 0) {
return JXL_API_ERROR("Invalid data type"); return JXL_API_ERROR("Invalid/unsupported data type");
} }
return JXL_DEC_SUCCESS; return JXL_DEC_SUCCESS;
@ -2440,7 +2451,7 @@ JxlDecoderStatus JxlDecoderFlushImage(JxlDecoder* dec) {
dec, *dec->ib, dec->image_out_format, dec, *dec->ib, dec->image_out_format,
/*want_extra_channel=*/false, /*want_extra_channel=*/false,
/*extra_channel_index=*/0, dec->image_out_buffer, dec->image_out_size, /*extra_channel_index=*/0, dec->image_out_buffer, dec->image_out_size,
/*out_callback=*/nullptr, /*out_opaque=*/nullptr); /*out_callback=*/{});
dec->ib->ShrinkTo(xsize, ysize); dec->ib->ShrinkTo(xsize, ysize);
if (status != JXL_DEC_SUCCESS) return status; if (status != JXL_DEC_SUCCESS) return status;
return JXL_DEC_SUCCESS; return JXL_DEC_SUCCESS;
@ -2548,7 +2559,7 @@ JxlDecoderStatus JxlDecoderSetImageOutBuffer(JxlDecoder* dec,
if (!dec->got_basic_info || !(dec->orig_events_wanted & JXL_DEC_FULL_IMAGE)) { if (!dec->got_basic_info || !(dec->orig_events_wanted & JXL_DEC_FULL_IMAGE)) {
return JXL_API_ERROR("No image out buffer needed at this time"); return JXL_API_ERROR("No image out buffer needed at this time");
} }
if (dec->image_out_buffer_set && !!dec->image_out_callback) { if (dec->image_out_buffer_set && !!dec->image_out_run_callback) {
return JXL_API_ERROR( return JXL_API_ERROR(
"Cannot change from image out callback to image out buffer"); "Cannot change from image out callback to image out buffer");
} }
@ -2634,19 +2645,51 @@ JxlDecoderStatus JxlDecoderSetImageOutCallback(JxlDecoder* dec,
const JxlPixelFormat* format, const JxlPixelFormat* format,
JxlImageOutCallback callback, JxlImageOutCallback callback,
void* opaque) { void* opaque) {
dec->simple_image_out_callback.callback = callback;
dec->simple_image_out_callback.opaque = opaque;
const auto init_callback =
+[](void* init_opaque, size_t num_threads, size_t num_pixels_per_thread) {
// No initialization to do, just reuse init_opaque as run_opaque.
return init_opaque;
};
const auto run_callback =
+[](void* run_opaque, size_t thread_id, size_t x, size_t y,
size_t num_pixels, const void* pixels) {
const auto* const simple_callback =
static_cast<const JxlDecoder::SimpleImageOutCallback*>(run_opaque);
simple_callback->callback(simple_callback->opaque, x, y, num_pixels,
pixels);
};
const auto destroy_callback = +[](void* run_opaque) {};
return JxlDecoderSetMultithreadedImageOutCallback(
dec, format, init_callback, run_callback,
/*destroy_callback=*/destroy_callback, &dec->simple_image_out_callback);
}
JxlDecoderStatus JxlDecoderSetMultithreadedImageOutCallback(
JxlDecoder* dec, const JxlPixelFormat* format,
JxlImageOutInitCallback init_callback, JxlImageOutRunCallback run_callback,
JxlImageOutDestroyCallback destroy_callback, void* init_opaque) {
if (dec->image_out_buffer_set && !!dec->image_out_buffer) { if (dec->image_out_buffer_set && !!dec->image_out_buffer) {
return JXL_API_ERROR( return JXL_API_ERROR(
"Cannot change from image out buffer to image out callback"); "Cannot change from image out buffer to image out callback");
} }
if (init_callback == nullptr || run_callback == nullptr ||
destroy_callback == nullptr) {
return JXL_API_ERROR("All callbacks are required");
}
// Perform error checking for invalid format. // Perform error checking for invalid format.
size_t bits_dummy; size_t bits_dummy;
JxlDecoderStatus status = PrepareSizeCheck(dec, format, &bits_dummy); JxlDecoderStatus status = PrepareSizeCheck(dec, format, &bits_dummy);
if (status != JXL_DEC_SUCCESS) return status; if (status != JXL_DEC_SUCCESS) return status;
dec->image_out_buffer_set = true; dec->image_out_buffer_set = true;
dec->image_out_callback = callback; dec->image_out_init_callback = init_callback;
dec->image_out_opaque = opaque; dec->image_out_run_callback = run_callback;
dec->image_out_destroy_callback = destroy_callback;
dec->image_out_init_opaque = init_opaque;
dec->image_out_format = *format; dec->image_out_format = *format;
return JXL_DEC_SUCCESS; return JXL_DEC_SUCCESS;

View File

@ -211,7 +211,7 @@ PaddedBytes CreateTestJXLCodestream(
jpeg_bytes.data() + jpeg_bytes.size()); jpeg_bytes.data() + jpeg_bytes.size());
EXPECT_TRUE(jxl::jpeg::DecodeImageJPG( EXPECT_TRUE(jxl::jpeg::DecodeImageJPG(
jxl::Span<const uint8_t>(jpeg_bytes.data(), jpeg_bytes.size()), &io)); jxl::Span<const uint8_t>(jpeg_bytes.data(), jpeg_bytes.size()), &io));
EXPECT_TRUE(EncodeJPEGData(*io.Main().jpeg_data, &jpeg_data)); EXPECT_TRUE(EncodeJPEGData(*io.Main().jpeg_data, &jpeg_data, cparams));
io.metadata.m.xyb_encoded = false; io.metadata.m.xyb_encoded = false;
#else // JPEGXL_ENABLE_JPEG #else // JPEGXL_ENABLE_JPEG
JXL_ABORT( JXL_ABORT(
@ -1234,7 +1234,8 @@ TEST_P(DecodeTestParam, PixelTest) {
io.Main(), 16, io.Main(), 16,
/*float_out=*/false, orig_channels, JXL_BIG_ENDIAN, /*float_out=*/false, orig_channels, JXL_BIG_ENDIAN,
xsize * 2 * orig_channels, nullptr, pixels.data(), pixels.size(), xsize * 2 * orig_channels, nullptr, pixels.data(), pixels.size(),
nullptr, nullptr, static_cast<jxl::Orientation>(config.orientation))); /*out_callback=*/{},
static_cast<jxl::Orientation>(config.orientation)));
} }
if (config.upsampling == 1) { if (config.upsampling == 1) {
EXPECT_EQ(0u, jxl::test::ComparePixels(pixels.data(), pixels2.data(), xsize, EXPECT_EQ(0u, jxl::test::ComparePixels(pixels.data(), pixels2.data(), xsize,
@ -1406,12 +1407,8 @@ std::ostream& operator<<(std::ostream& os, const PixelTestConfig& c) {
case JXL_TYPE_FLOAT16: case JXL_TYPE_FLOAT16:
os << "f16"; os << "f16";
break; break;
case JXL_TYPE_UINT32: default:
os << "u32"; JXL_ASSERT(false);
break;
case JXL_TYPE_BOOLEAN:
os << "b";
break;
}; };
if (jxl::test::GetDataBits(c.data_type) > jxl::kBitsPerByte) { if (jxl::test::GetDataBits(c.data_type) > jxl::kBitsPerByte) {
if (c.endianness == JXL_NATIVE_ENDIAN) { if (c.endianness == JXL_NATIVE_ENDIAN) {
@ -3568,7 +3565,7 @@ TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructTestCodestream)) {
TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) { TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) {
const std::string jpeg_path = const std::string jpeg_path =
"imagecompression.info/flower_foveon.png.im_q85_420.jpg"; "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg";
const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path); const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path);
jxl::CodecInOut orig_io; jxl::CodecInOut orig_io;
ASSERT_TRUE( ASSERT_TRUE(
@ -3586,7 +3583,8 @@ TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) {
/*aux_out=*/nullptr)); /*aux_out=*/nullptr));
jxl::PaddedBytes jpeg_data; jxl::PaddedBytes jpeg_data;
ASSERT_TRUE(EncodeJPEGData(*orig_io.Main().jpeg_data.get(), &jpeg_data)); ASSERT_TRUE(
EncodeJPEGData(*orig_io.Main().jpeg_data.get(), &jpeg_data, cparams));
jxl::PaddedBytes container; jxl::PaddedBytes container;
container.append(jxl::kContainerHeader, container.append(jxl::kContainerHeader,
jxl::kContainerHeader + sizeof(jxl::kContainerHeader)); jxl::kContainerHeader + sizeof(jxl::kContainerHeader));
@ -4100,7 +4098,7 @@ TEST(DecodeTest, SpotColorTest) {
cparams.speed_tier = jxl::SpeedTier::kLightning; cparams.speed_tier = jxl::SpeedTier::kLightning;
cparams.modular_mode = true; cparams.modular_mode = true;
cparams.color_transform = jxl::ColorTransform::kNone; cparams.color_transform = jxl::ColorTransform::kNone;
cparams.quality_pair = {100, 100}; cparams.butteraugli_distance = 0.f;
jxl::PaddedBytes compressed; jxl::PaddedBytes compressed;
std::unique_ptr<jxl::PassesEncoderState> enc_state = std::unique_ptr<jxl::PassesEncoderState> enc_state =

View File

@ -1102,9 +1102,10 @@ ImageBundle RoundtripImage(const Image3F& opsin, PassesEncoderState* enc_state,
JXL_CHECK(dec_state->PreparePipeline(&decoded, options)); JXL_CHECK(dec_state->PreparePipeline(&decoded, options));
hwy::AlignedUniquePtr<GroupDecCache[]> group_dec_caches; hwy::AlignedUniquePtr<GroupDecCache[]> group_dec_caches;
const auto allocate_storage = [&](const size_t num_threads) { const auto allocate_storage = [&](const size_t num_threads) -> Status {
dec_state->render_pipeline->PrepareForThreads(num_threads, JXL_RETURN_IF_ERROR(
/*use_group_ids=*/false); dec_state->render_pipeline->PrepareForThreads(num_threads,
/*use_group_ids=*/false));
group_dec_caches = hwy::MakeUniqueAlignedArray<GroupDecCache>(num_threads); group_dec_caches = hwy::MakeUniqueAlignedArray<GroupDecCache>(num_threads);
return true; return true;
}; };

View File

@ -98,8 +98,10 @@ Status InitializePassesEncoder(const Image3F& opsin, const JxlCmsInterface& cms,
// and kModular for the smallest DC (first in the bitstream) // and kModular for the smallest DC (first in the bitstream)
if (cparams.progressive_dc == 0) { if (cparams.progressive_dc == 0) {
cparams.modular_mode = true; cparams.modular_mode = true;
cparams.quality_pair.first = cparams.quality_pair.second = // TODO(jon): tweak mapping from image dist to dist for modular DC
99.f - enc_state->cparams.butteraugli_distance * 0.2f; cparams.butteraugli_distance =
std::max(kMinButteraugliDistance,
enc_state->cparams.butteraugli_distance * 0.03f);
} }
ImageBundle ib(&shared.metadata->m); ImageBundle ib(&shared.metadata->m);
// This is a lie - dc is in XYB // This is a lie - dc is in XYB

View File

@ -86,7 +86,6 @@ uint32_t JXL_INLINE Load8(const uint8_t* p) { return *p; }
Status PixelFormatToExternal(const JxlPixelFormat& pixel_format, Status PixelFormatToExternal(const JxlPixelFormat& pixel_format,
size_t* bitdepth, bool* float_in) { size_t* bitdepth, bool* float_in) {
// TODO(zond): Make this accept uint32.
if (pixel_format.data_type == JXL_TYPE_FLOAT) { if (pixel_format.data_type == JXL_TYPE_FLOAT) {
*bitdepth = 32; *bitdepth = 32;
*float_in = true; *float_in = true;
@ -100,7 +99,7 @@ Status PixelFormatToExternal(const JxlPixelFormat& pixel_format,
*bitdepth = 16; *bitdepth = 16;
*float_in = false; *float_in = false;
} else { } else {
return JXL_FAILURE("unsupported bitdepth"); return JXL_FAILURE("unsupported pixel format data type");
} }
return true; return true;
} }
@ -111,20 +110,9 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
JxlEndianness endianness, ThreadPool* pool, JxlEndianness endianness, ThreadPool* pool,
ImageF* channel, bool float_in, size_t align) { ImageF* channel, bool float_in, size_t align) {
// TODO(firsching): Avoid code duplication with the function below. // TODO(firsching): Avoid code duplication with the function below.
if (bits_per_sample < 1 || bits_per_sample > 32) { JXL_CHECK(float_in ? bits_per_sample == 16 || bits_per_sample == 32
return JXL_FAILURE("Invalid bits_per_sample value."); : bits_per_sample > 0 && bits_per_sample <= 16);
}
// TODO(deymo): Implement 1-bit per sample as 8 samples per byte. In
// any other case we use DivCeil(bits_per_sample, 8) bytes per pixel per
// channel.
if (bits_per_sample == 1) {
return JXL_FAILURE("packed 1-bit per sample is not yet supported");
}
// bytes_per_pixel are only valid for
// bits_per_sample > 1.
const size_t bytes_per_pixel = DivCeil(bits_per_sample, jxl::kBitsPerByte); const size_t bytes_per_pixel = DivCeil(bits_per_sample, jxl::kBitsPerByte);
const size_t last_row_size = xsize * bytes_per_pixel; const size_t last_row_size = xsize * bytes_per_pixel;
const size_t row_size = const size_t row_size =
(align > 1 ? jxl::DivCeil(last_row_size, align) * align : last_row_size); (align > 1 ? jxl::DivCeil(last_row_size, align) * align : last_row_size);
@ -153,7 +141,7 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
const size_t y = task; const size_t y = task;
size_t i = row_size * task; size_t i = row_size * task;
float* JXL_RESTRICT row_out = channel->Row(y); float* JXL_RESTRICT row_out = channel->Row(y);
if (bits_per_sample <= 16) { if (bits_per_sample == 16) {
if (little_endian) { if (little_endian) {
for (size_t x = 0; x < xsize; ++x) { for (size_t x = 0; x < xsize; ++x) {
row_out[x] = LoadLEFloat16(in + i); row_out[x] = LoadLEFloat16(in + i);
@ -188,11 +176,9 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
const size_t y = task; const size_t y = task;
size_t i = row_size * task; size_t i = row_size * task;
float* JXL_RESTRICT row_out = channel->Row(y); float* JXL_RESTRICT row_out = channel->Row(y);
// TODO(deymo): add bits_per_sample == 1 case here. Also maybe
// implement masking if bits_per_sample is not a multiple of 8.
if (bits_per_sample <= 8) { if (bits_per_sample <= 8) {
LoadFloatRow<Load8>(row_out, in + i, mul, xsize, bytes_per_pixel); LoadFloatRow<Load8>(row_out, in + i, mul, xsize, bytes_per_pixel);
} else if (bits_per_sample <= 16) { } else {
if (little_endian) { if (little_endian) {
LoadFloatRow<LoadLE16>(row_out, in + i, mul, xsize, LoadFloatRow<LoadLE16>(row_out, in + i, mul, xsize,
bytes_per_pixel); bytes_per_pixel);
@ -200,14 +186,6 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
LoadFloatRow<LoadBE16>(row_out, in + i, mul, xsize, LoadFloatRow<LoadBE16>(row_out, in + i, mul, xsize,
bytes_per_pixel); bytes_per_pixel);
} }
} else {
if (little_endian) {
LoadFloatRow<LoadLE32>(row_out, in + i, mul, xsize,
bytes_per_pixel);
} else {
LoadFloatRow<LoadBE32>(row_out, in + i, mul, xsize,
bytes_per_pixel);
}
} }
}, },
"ConvertExtraChannelUint")); "ConvertExtraChannelUint"));
@ -221,15 +199,8 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
size_t bits_per_sample, JxlEndianness endianness, size_t bits_per_sample, JxlEndianness endianness,
bool flipped_y, ThreadPool* pool, ImageBundle* ib, bool flipped_y, ThreadPool* pool, ImageBundle* ib,
bool float_in, size_t align) { bool float_in, size_t align) {
if (bits_per_sample < 1 || bits_per_sample > 32) { JXL_CHECK(float_in ? bits_per_sample == 16 || bits_per_sample == 32
return JXL_FAILURE("Invalid bits_per_sample value."); : bits_per_sample > 0 && bits_per_sample <= 16);
}
// TODO(deymo): Implement 1-bit per sample as 8 samples per byte. In
// any other case we use DivCeil(bits_per_sample, 8) bytes per pixel per
// channel.
if (bits_per_sample == 1) {
return JXL_FAILURE("packed 1-bit per sample is not yet supported");
}
const size_t color_channels = c_current.Channels(); const size_t color_channels = c_current.Channels();
bool has_alpha = channels == 2 || channels == 4; bool has_alpha = channels == 2 || channels == 4;
@ -239,8 +210,6 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
color_channels, channels); color_channels, channels);
} }
// bytes_per_channel and bytes_per_pixel are only valid for
// bits_per_sample > 1.
const size_t bytes_per_channel = DivCeil(bits_per_sample, jxl::kBitsPerByte); const size_t bytes_per_channel = DivCeil(bits_per_sample, jxl::kBitsPerByte);
const size_t bytes_per_pixel = channels * bytes_per_channel; const size_t bytes_per_pixel = channels * bytes_per_channel;
if (bits_per_sample > 16 && bits_per_sample < 32) { if (bits_per_sample > 16 && bits_per_sample < 32) {
@ -287,7 +256,7 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
size_t i = size_t i =
row_size * task + (c * bits_per_sample / jxl::kBitsPerByte); row_size * task + (c * bits_per_sample / jxl::kBitsPerByte);
float* JXL_RESTRICT row_out = color.PlaneRow(c, y); float* JXL_RESTRICT row_out = color.PlaneRow(c, y);
if (bits_per_sample <= 16) { if (bits_per_sample == 16) {
if (little_endian) { if (little_endian) {
for (size_t x = 0; x < xsize; ++x) { for (size_t x = 0; x < xsize; ++x) {
row_out[x] = LoadLEFloat16(in + i); row_out[x] = LoadLEFloat16(in + i);
@ -325,11 +294,9 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
const size_t y = get_y(task); const size_t y = get_y(task);
size_t i = row_size * task + c * bytes_per_channel; size_t i = row_size * task + c * bytes_per_channel;
float* JXL_RESTRICT row_out = color.PlaneRow(c, y); float* JXL_RESTRICT row_out = color.PlaneRow(c, y);
// TODO(deymo): add bits_per_sample == 1 case here. Also maybe
// implement masking if bits_per_sample is not a multiple of 8.
if (bits_per_sample <= 8) { if (bits_per_sample <= 8) {
LoadFloatRow<Load8>(row_out, in + i, mul, xsize, bytes_per_pixel); LoadFloatRow<Load8>(row_out, in + i, mul, xsize, bytes_per_pixel);
} else if (bits_per_sample <= 16) { } else {
if (little_endian) { if (little_endian) {
LoadFloatRow<LoadLE16>(row_out, in + i, mul, xsize, LoadFloatRow<LoadLE16>(row_out, in + i, mul, xsize,
bytes_per_pixel); bytes_per_pixel);
@ -337,14 +304,6 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
LoadFloatRow<LoadBE16>(row_out, in + i, mul, xsize, LoadFloatRow<LoadBE16>(row_out, in + i, mul, xsize,
bytes_per_pixel); bytes_per_pixel);
} }
} else {
if (little_endian) {
LoadFloatRow<LoadLE32>(row_out, in + i, mul, xsize,
bytes_per_pixel);
} else {
LoadFloatRow<LoadBE32>(row_out, in + i, mul, xsize,
bytes_per_pixel);
}
} }
}, },
"ConvertRGBUint")); "ConvertRGBUint"));
@ -371,7 +330,7 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
size_t i = row_size * task + size_t i = row_size * task +
((channels - 1) * bits_per_sample / jxl::kBitsPerByte); ((channels - 1) * bits_per_sample / jxl::kBitsPerByte);
float* JXL_RESTRICT row_out = alpha.Row(y); float* JXL_RESTRICT row_out = alpha.Row(y);
if (bits_per_sample <= 16) { if (bits_per_sample == 16) {
if (little_endian) { if (little_endian) {
for (size_t x = 0; x < xsize; ++x) { for (size_t x = 0; x < xsize; ++x) {
row_out[x] = LoadLEFloat16(in + i); row_out[x] = LoadLEFloat16(in + i);
@ -406,11 +365,9 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
const size_t y = get_y(task); const size_t y = get_y(task);
size_t i = row_size * task + (channels - 1) * bytes_per_channel; size_t i = row_size * task + (channels - 1) * bytes_per_channel;
float* JXL_RESTRICT row_out = alpha.Row(y); float* JXL_RESTRICT row_out = alpha.Row(y);
// TODO(deymo): add bits_per_sample == 1 case here. Also maybe
// implement masking if bits_per_sample is not a multiple of 8.
if (bits_per_sample <= 8) { if (bits_per_sample <= 8) {
LoadFloatRow<Load8>(row_out, in + i, mul, xsize, bytes_per_pixel); LoadFloatRow<Load8>(row_out, in + i, mul, xsize, bytes_per_pixel);
} else if (bits_per_sample <= 16) { } else {
if (little_endian) { if (little_endian) {
LoadFloatRow<LoadLE16>(row_out, in + i, mul, xsize, LoadFloatRow<LoadLE16>(row_out, in + i, mul, xsize,
bytes_per_pixel); bytes_per_pixel);
@ -418,19 +375,17 @@ Status ConvertFromExternal(Span<const uint8_t> bytes, size_t xsize,
LoadFloatRow<LoadBE16>(row_out, in + i, mul, xsize, LoadFloatRow<LoadBE16>(row_out, in + i, mul, xsize,
bytes_per_pixel); bytes_per_pixel);
} }
} else {
if (little_endian) {
LoadFloatRow<LoadLE32>(row_out, in + i, mul, xsize,
bytes_per_pixel);
} else {
LoadFloatRow<LoadBE32>(row_out, in + i, mul, xsize,
bytes_per_pixel);
}
} }
}, },
"ConvertAlphaUint")); "ConvertAlphaUint"));
} }
ib->SetAlpha(std::move(alpha), alpha_is_premultiplied);
} else if (!has_alpha && ib->HasAlpha()) {
// if alpha is not passed, but it is expected, then assume
// it is all-opaque
ImageF alpha(xsize, ysize);
FillImage(1.0f, &alpha);
ib->SetAlpha(std::move(alpha), alpha_is_premultiplied); ib->SetAlpha(std::move(alpha), alpha_is_premultiplied);
} }

View File

@ -20,6 +20,7 @@
#include "lib/jxl/enc_cache.h" #include "lib/jxl/enc_cache.h"
#include "lib/jxl/enc_frame.h" #include "lib/jxl/enc_frame.h"
#include "lib/jxl/enc_icc_codec.h" #include "lib/jxl/enc_icc_codec.h"
#include "lib/jxl/exif.h"
#include "lib/jxl/frame_header.h" #include "lib/jxl/frame_header.h"
#include "lib/jxl/headers.h" #include "lib/jxl/headers.h"
#include "lib/jxl/image_bundle.h" #include "lib/jxl/image_bundle.h"
@ -64,51 +65,6 @@ PassDefinition progressive_passes_dc_quant_ac_full_ac[] = {
/*suitable_for_downsampling_of_at_least=*/0}, /*suitable_for_downsampling_of_at_least=*/0},
}; };
constexpr uint16_t kExifOrientationTag = 274;
// Parses the Exif data just enough to extract any render-impacting info.
// If the Exif data is invalid or could not be parsed, then it is treated
// as a no-op.
// TODO (jon): tag 1 can be used to represent Adobe RGB 1998 if it has value
// "R03"
// TODO (jon): set intrinsic dimensions according to
// https://discourse.wicg.io/t/proposal-exif-image-resolution-auto-and-from-image/4326/24
void InterpretExif(const PaddedBytes& exif, CodecMetadata* metadata) {
if (exif.size() < 12) return; // not enough bytes for a valid exif blob
const uint8_t* t = exif.data();
bool bigendian = false;
if (LoadLE32(t) == 0x2A004D4D) {
bigendian = true;
} else if (LoadLE32(t) != 0x002A4949) {
return; // not a valid tiff header
}
t += 4;
uint32_t offset = (bigendian ? LoadBE32(t) : LoadLE32(t));
if (exif.size() < 12 + offset + 2 || offset < 8) return;
t += offset - 4;
uint16_t nb_tags = (bigendian ? LoadBE16(t) : LoadLE16(t));
t += 2;
while (nb_tags > 0) {
if (t + 12 >= exif.data() + exif.size()) return;
uint16_t tag = (bigendian ? LoadBE16(t) : LoadLE16(t));
t += 2;
uint16_t type = (bigendian ? LoadBE16(t) : LoadLE16(t));
t += 2;
uint32_t count = (bigendian ? LoadBE32(t) : LoadLE32(t));
t += 4;
uint16_t value = (bigendian ? LoadBE16(t) : LoadLE16(t));
t += 4;
if (tag == kExifOrientationTag) {
if (type == 3 && count == 1) {
if (value >= 1 && value <= 8) {
metadata->m.orientation = value;
}
}
}
nb_tags--;
}
}
Status PrepareCodecMetadataFromIO(const CompressParams& cparams, Status PrepareCodecMetadataFromIO(const CompressParams& cparams,
const CodecInOut* io, const CodecInOut* io,
CodecMetadata* metadata) { CodecMetadata* metadata) {
@ -121,9 +77,7 @@ Status PrepareCodecMetadataFromIO(const CompressParams& cparams,
// Keep ICC profile in lossless modes because a reconstructed profile may be // Keep ICC profile in lossless modes because a reconstructed profile may be
// slightly different (quantization). // slightly different (quantization).
// Also keep ICC in JPEG reconstruction mode as we need byte-exact profiles. // Also keep ICC in JPEG reconstruction mode as we need byte-exact profiles.
const bool lossless_modular = if (!cparams.IsLossless() && !io->Main().IsJPEG()) {
cparams.modular_mode && cparams.quality_pair.first == 100.0f;
if (!lossless_modular && !io->Main().IsJPEG()) {
metadata->m.color_encoding.DecideIfWantICC(); metadata->m.color_encoding.DecideIfWantICC();
} }

View File

@ -254,10 +254,9 @@ Status LoopFilterFromParams(const CompressParams& cparams,
} }
// Strength of EPF in modular mode. // Strength of EPF in modular mode.
if (frame_header->encoding == FrameEncoding::kModular && if (frame_header->encoding == FrameEncoding::kModular &&
cparams.quality_pair.first < 100) { !cparams.IsLossless()) {
// TODO(veluca): this formula is nonsense. // TODO(veluca): this formula is nonsense.
loop_filter->epf_sigma_for_modular = loop_filter->epf_sigma_for_modular = cparams.butteraugli_distance;
20.0f * (1.0f - cparams.quality_pair.first / 100);
} }
if (frame_header->encoding == FrameEncoding::kModular && if (frame_header->encoding == FrameEncoding::kModular &&
cparams.lossy_palette) { cparams.lossy_palette) {
@ -1105,12 +1104,6 @@ Status EncodeFrame(const CompressParams& cparams_orig,
return JXL_FAILURE("Butteraugli distance is too low (%f)", return JXL_FAILURE("Butteraugli distance is too low (%f)",
cparams.butteraugli_distance); cparams.butteraugli_distance);
} }
if (cparams.butteraugli_distance > 0.9f && cparams.modular_mode == false &&
cparams.quality_pair.first == 100) {
// in case the color image is lossy, make the alpha slightly lossy too
cparams.quality_pair.first =
std::max(90.f, 99.f - 0.3f * cparams.butteraugli_distance);
}
if (ib.IsJPEG()) { if (ib.IsJPEG()) {
cparams.gaborish = Override::kOff; cparams.gaborish = Override::kOff;
@ -1228,8 +1221,7 @@ Status EncodeFrame(const CompressParams& cparams_orig,
// input is already in XYB. // input is already in XYB.
CopyImageTo(ib.color(), &opsin); CopyImageTo(ib.color(), &opsin);
} }
bool lossless = (frame_header->encoding == FrameEncoding::kModular && bool lossless = cparams.IsLossless();
cparams.quality_pair.first == 100);
if (ib.HasAlpha() && !ib.AlphaIsPremultiplied() && if (ib.HasAlpha() && !ib.AlphaIsPremultiplied() &&
frame_header->frame_type == FrameType::kRegularFrame && frame_header->frame_type == FrameType::kRegularFrame &&
!ApplyOverride(cparams.keep_invisible, lossless) && !ApplyOverride(cparams.keep_invisible, lossless) &&

View File

@ -22,29 +22,6 @@
namespace jxl { namespace jxl {
namespace { namespace {
bool EncodeVarInt(uint64_t value, size_t output_size, size_t* output_pos,
uint8_t* output) {
// While more than 7 bits of data are left,
// store 7 bits and set the next byte flag
while (value > 127) {
if (*output_pos > output_size) return false;
// |128: Set the next byte flag
output[(*output_pos)++] = ((uint8_t)(value & 127)) | 128;
// Remove the seven bits we just wrote
value >>= 7;
}
if (*output_pos > output_size) return false;
output[(*output_pos)++] = ((uint8_t)value) & 127;
return true;
}
void EncodeVarInt(uint64_t value, PaddedBytes* data) {
size_t pos = data->size();
data->resize(data->size() + 9);
JXL_CHECK(EncodeVarInt(value, data->size(), &pos, data->data()));
data->resize(pos);
}
// Unshuffles or de-interleaves bytes, for example with width 2, turns // Unshuffles or de-interleaves bytes, for example with width 2, turns
// "AaBbCcDc" into "ABCDabcd", this for example de-interleaves UTF-16 bytes into // "AaBbCcDc" into "ABCDabcd", this for example de-interleaves UTF-16 bytes into
// first all the high order bytes, then all the low order bytes. // first all the high order bytes, then all the low order bytes.
@ -159,9 +136,9 @@ Status PredictICC(const uint8_t* icc, size_t size, PaddedBytes* result) {
ok &= DecodeKeyword(icc, size, pos + 0) == kGtrcTag; ok &= DecodeKeyword(icc, size, pos + 0) == kGtrcTag;
ok &= DecodeKeyword(icc, size, pos + 12) == kBtrcTag; ok &= DecodeKeyword(icc, size, pos + 12) == kBtrcTag;
if (ok) { if (ok) {
for (size_t i = 0; i < 8; i++) { for (size_t kk = 0; kk < 8; kk++) {
if (icc[pos - 8 + i] != icc[pos + 4 + i]) ok = false; if (icc[pos - 8 + kk] != icc[pos + 4 + kk]) ok = false;
if (icc[pos - 8 + i] != icc[pos + 16 + i]) ok = false; if (icc[pos - 8 + kk] != icc[pos + 16 + kk]) ok = false;
} }
} }
if (ok) { if (ok) {

View File

@ -228,11 +228,18 @@ void QuantizeChannel(Channel& ch, const int q) {
// fit in pixel_type // fit in pixel_type
Status float_to_int(const float* const row_in, pixel_type* const row_out, Status float_to_int(const float* const row_in, pixel_type* const row_out,
size_t xsize, unsigned int bits, unsigned int exp_bits, size_t xsize, unsigned int bits, unsigned int exp_bits,
bool fp, float factor) { bool fp, double dfactor) {
JXL_ASSERT(sizeof(pixel_type) * 8 >= bits); JXL_ASSERT(sizeof(pixel_type) * 8 >= bits);
if (!fp) { if (!fp) {
for (size_t x = 0; x < xsize; ++x) { if (bits > 22) {
row_out[x] = row_in[x] * factor + (row_in[x] < 0 ? -0.5f : 0.5f); for (size_t x = 0; x < xsize; ++x) {
row_out[x] = row_in[x] * dfactor + (row_in[x] < 0 ? -0.5 : 0.5);
}
} else {
float factor = dfactor;
for (size_t x = 0; x < xsize; ++x) {
row_out[x] = row_in[x] * factor + (row_in[x] < 0 ? -0.5f : 0.5f);
}
} }
return true; return true;
} }
@ -296,8 +303,7 @@ ModularFrameEncoder::ModularFrameEncoder(const FrameHeader& frame_header,
: frame_dim(frame_header.ToFrameDimensions()), cparams(cparams_orig) { : frame_dim(frame_header.ToFrameDimensions()), cparams(cparams_orig) {
size_t num_streams = size_t num_streams =
ModularStreamId::Num(frame_dim, frame_header.passes.num_passes); ModularStreamId::Num(frame_dim, frame_header.passes.num_passes);
if (cparams.modular_mode && if (cparams.IsLossless()) {
cparams.quality_pair == std::pair<float, float>{100.0, 100.0}) {
switch (cparams.decoding_speed_tier) { switch (cparams.decoding_speed_tier) {
case 0: case 0:
break; break;
@ -322,18 +328,17 @@ ModularFrameEncoder::ModularFrameEncoder(const FrameHeader& frame_header,
} }
} }
if (cparams.decoding_speed_tier >= 1 && cparams.responsive && if (cparams.decoding_speed_tier >= 1 && cparams.responsive &&
cparams.quality_pair == std::make_pair(100.f, 100.f)) { cparams.IsLossless()) {
cparams.options.tree_kind = cparams.options.tree_kind =
ModularOptions::TreeKind::kTrivialTreeNoPredictor; ModularOptions::TreeKind::kTrivialTreeNoPredictor;
cparams.options.nb_repeats = 0; cparams.options.nb_repeats = 0;
} }
stream_images.resize(num_streams); stream_images.resize(num_streams);
if (cquality > 100) cquality = quality;
// use a sensible default if nothing explicit is specified: // use a sensible default if nothing explicit is specified:
// Squeeze for lossy, no squeeze for lossless // Squeeze for lossy, no squeeze for lossless
if (cparams.responsive < 0) { if (cparams.responsive < 0) {
if (quality == 100) { if (cparams.IsLossless()) {
cparams.responsive = 0; cparams.responsive = 0;
} else { } else {
cparams.responsive = 1; cparams.responsive = 1;
@ -396,14 +401,14 @@ ModularFrameEncoder::ModularFrameEncoder(const FrameHeader& frame_header,
// no explicit predictor(s) given, set a good default // no explicit predictor(s) given, set a good default
if ((cparams.speed_tier <= SpeedTier::kTortoise || if ((cparams.speed_tier <= SpeedTier::kTortoise ||
cparams.modular_mode == false) && cparams.modular_mode == false) &&
quality == 100 && cparams.responsive == false) { cparams.IsLossless() && cparams.responsive == false) {
// TODO(veluca): allow all predictors that don't break residual // TODO(veluca): allow all predictors that don't break residual
// multipliers in lossy mode. // multipliers in lossy mode.
cparams.options.predictor = Predictor::Variable; cparams.options.predictor = Predictor::Variable;
} else if (cparams.responsive || cparams.lossy_palette) { } else if (cparams.responsive || cparams.lossy_palette) {
// zero predictor for Squeeze residues and lossy palette // zero predictor for Squeeze residues and lossy palette
cparams.options.predictor = Predictor::Zero; cparams.options.predictor = Predictor::Zero;
} else if (quality < 100) { } else if (!cparams.IsLossless()) {
// If not responsive and lossy. TODO(veluca): use near_lossless instead? // If not responsive and lossy. TODO(veluca): use near_lossless instead?
cparams.options.predictor = Predictor::Gradient; cparams.options.predictor = Predictor::Gradient;
} else if (cparams.speed_tier < SpeedTier::kFalcon) { } else if (cparams.speed_tier < SpeedTier::kFalcon) {
@ -420,6 +425,12 @@ ModularFrameEncoder::ModularFrameEncoder(const FrameHeader& frame_header,
delta_pred = cparams.options.predictor; delta_pred = cparams.options.predictor;
if (cparams.lossy_palette) cparams.options.predictor = Predictor::Zero; if (cparams.lossy_palette) cparams.options.predictor = Predictor::Zero;
} }
if (!cparams.IsLossless()) {
if (cparams.options.predictor == Predictor::Weighted ||
cparams.options.predictor == Predictor::Variable ||
cparams.options.predictor == Predictor::Best)
cparams.options.predictor = Predictor::Zero;
}
tree_splits.push_back(0); tree_splits.push_back(0);
if (cparams.modular_mode == false) { if (cparams.modular_mode == false) {
cparams.options.fast_decode_multiplier = 1.0f; cparams.options.fast_decode_multiplier = 1.0f;
@ -499,17 +510,29 @@ Status ModularFrameEncoder::ComputeEncodingData(
} }
} }
// in the non-float case, there is an implicit 0 sign bit
int max_bitdepth =
do_color ? metadata.bit_depth.bits_per_sample + (fp ? 0 : 1) : 0;
Image& gi = stream_images[0]; Image& gi = stream_images[0];
gi = Image(xsize, ysize, metadata.bit_depth.bits_per_sample, nb_chans); gi = Image(xsize, ysize, metadata.bit_depth.bits_per_sample, nb_chans);
int c = 0; int c = 0;
if (cparams.color_transform == ColorTransform::kXYB && if (cparams.color_transform == ColorTransform::kXYB &&
cparams.modular_mode == true) { cparams.modular_mode == true) {
static const float enc_factors[3] = {32768.0f, 2048.0f, 2048.0f}; float enc_factors[3] = {32768.0f, 2048.0f, 2048.0f};
if (cparams.butteraugli_distance > 0 && !cparams.responsive) {
// quantize XYB here and then treat it as a lossless image
enc_factors[0] *= 1.f / (1.f + 23.f * cparams.butteraugli_distance);
enc_factors[1] *= 1.f / (1.f + 14.f * cparams.butteraugli_distance);
enc_factors[2] *= 1.f / (1.f + 14.f * cparams.butteraugli_distance);
cparams.butteraugli_distance = 0;
}
if (cparams.manual_xyb_factors.size() == 3) { if (cparams.manual_xyb_factors.size() == 3) {
DequantMatricesSetCustomDC(&enc_state->shared.matrices, DequantMatricesSetCustomDC(&enc_state->shared.matrices,
cparams.manual_xyb_factors.data()); cparams.manual_xyb_factors.data());
// TODO(jon): update max_bitdepth in this case
} else { } else {
DequantMatricesSetCustomDC(&enc_state->shared.matrices, enc_factors); DequantMatricesSetCustomDC(&enc_state->shared.matrices, enc_factors);
max_bitdepth = 12;
} }
} }
pixel_type maxval = gi.bitdepth < 32 ? (1u << gi.bitdepth) - 1 : 0; pixel_type maxval = gi.bitdepth < 32 ? (1u << gi.bitdepth) - 1 : 0;
@ -523,7 +546,7 @@ Status ModularFrameEncoder::ComputeEncodingData(
// XYB is encoded as YX(B-Y) // XYB is encoded as YX(B-Y)
if (cparams.color_transform == ColorTransform::kXYB && c < 2) if (cparams.color_transform == ColorTransform::kXYB && c < 2)
c_out = 1 - c_out; c_out = 1 - c_out;
float factor = maxval; double factor = maxval;
if (cparams.color_transform == ColorTransform::kXYB) if (cparams.color_transform == ColorTransform::kXYB)
factor = enc_state->shared.matrices.InvDCQuant(c); factor = enc_state->shared.matrices.InvDCQuant(c);
if (c == 2 && cparams.color_transform == ColorTransform::kXYB) { if (c == 2 && cparams.color_transform == ColorTransform::kXYB) {
@ -535,6 +558,8 @@ Status ModularFrameEncoder::ComputeEncodingData(
for (size_t x = 0; x < xsize; ++x) { for (size_t x = 0; x < xsize; ++x) {
row_out[x] = row_in[x] * factor + 0.5f; row_out[x] = row_in[x] * factor + 0.5f;
row_out[x] -= row_Y[x]; row_out[x] -= row_Y[x];
// zero the lsb of B
row_out[x] = row_out[x] / 2 * 2;
} }
} }
} else { } else {
@ -581,7 +606,8 @@ Status ModularFrameEncoder::ComputeEncodingData(
int bits = eci.bit_depth.bits_per_sample; int bits = eci.bit_depth.bits_per_sample;
int exp_bits = eci.bit_depth.exponent_bits_per_sample; int exp_bits = eci.bit_depth.exponent_bits_per_sample;
bool fp = eci.bit_depth.floating_point_sample; bool fp = eci.bit_depth.floating_point_sample;
float factor = (fp ? 1 : ((1u << eci.bit_depth.bits_per_sample) - 1)); double factor = (fp ? 1 : ((1u << eci.bit_depth.bits_per_sample) - 1));
if (bits + (fp ? 0 : 1) > max_bitdepth) max_bitdepth = bits + (fp ? 0 : 1);
std::atomic<bool> has_error{false}; std::atomic<bool> has_error{false};
JXL_RETURN_IF_ERROR(RunOnPool( JXL_RETURN_IF_ERROR(RunOnPool(
pool, 0, gi.channel[c].plane.ysize(), ThreadPool::NoInit, pool, 0, gi.channel[c].plane.ysize(), ThreadPool::NoInit,
@ -599,9 +625,16 @@ Status ModularFrameEncoder::ComputeEncodingData(
} }
JXL_ASSERT(c == nb_chans); JXL_ASSERT(c == nb_chans);
int level_max_bitdepth = (cparams.level == 5 ? 16 : 32);
if (max_bitdepth > level_max_bitdepth)
return JXL_FAILURE(
"Bitdepth too high for level %i (need %i bits, have only %i in this "
"level)",
cparams.level, max_bitdepth, level_max_bitdepth);
// Set options and apply transformations // Set options and apply transformations
if (quality < 100) { if (cparams.butteraugli_distance > 0) {
if (cparams.palette_colors != 0) { if (cparams.palette_colors != 0) {
JXL_DEBUG_V(3, "Lossy encode, not doing palette transforms"); JXL_DEBUG_V(3, "Lossy encode, not doing palette transforms");
} }
@ -704,65 +737,59 @@ Status ModularFrameEncoder::ComputeEncodingData(
} }
} }
// don't do an RCT if we're short on bits
if (cparams.color_transform == ColorTransform::kNone && do_color && !fp && if (cparams.color_transform == ColorTransform::kNone && do_color && !fp &&
gi.channel.size() - gi.nb_meta_channels >= 3) { gi.channel.size() - gi.nb_meta_channels >= 3 &&
max_bitdepth + 1 < level_max_bitdepth) {
if (cparams.colorspace == 1 || if (cparams.colorspace == 1 ||
(cparams.colorspace < 0 && (cparams.colorspace < 0 &&
(quality < 100 || cparams.speed_tier > SpeedTier::kHare))) { (!cparams.IsLossless() || cparams.speed_tier > SpeedTier::kHare))) {
Transform ycocg{TransformId::kRCT}; Transform ycocg{TransformId::kRCT};
ycocg.rct_type = 6; ycocg.rct_type = 6;
ycocg.begin_c = gi.nb_meta_channels; ycocg.begin_c = gi.nb_meta_channels;
do_transform(gi, ycocg, weighted::Header(), pool); do_transform(gi, ycocg, weighted::Header(), pool);
max_bitdepth++;
} else if (cparams.colorspace >= 2) { } else if (cparams.colorspace >= 2) {
Transform sg(TransformId::kRCT); Transform sg(TransformId::kRCT);
sg.begin_c = gi.nb_meta_channels; sg.begin_c = gi.nb_meta_channels;
sg.rct_type = cparams.colorspace - 2; sg.rct_type = cparams.colorspace - 2;
do_transform(gi, sg, weighted::Header(), pool); do_transform(gi, sg, weighted::Header(), pool);
max_bitdepth++;
} }
} }
if (cparams.responsive && !gi.channel.empty()) { // don't do squeeze if we don't have some spare bits
if (cparams.responsive && !gi.channel.empty() &&
max_bitdepth + 2 < level_max_bitdepth) {
Transform t(TransformId::kSqueeze); Transform t(TransformId::kSqueeze);
t.squeezes = cparams.squeezes; t.squeezes = cparams.squeezes;
do_transform(gi, t, weighted::Header(), pool); do_transform(gi, t, weighted::Header(), pool);
max_bitdepth += 2;
} }
if (max_bitdepth + 1 > level_max_bitdepth) {
// force no group RCTs if we don't have a spare bit
cparams.colorspace = 0;
}
JXL_ASSERT(max_bitdepth <= level_max_bitdepth);
std::vector<uint32_t> quants; std::vector<uint32_t> quants;
if (quality < 100 || cquality < 100) { if (cparams.butteraugli_distance > 0) {
quants.resize(gi.channel.size(), 1); quants.resize(gi.channel.size(), 1);
JXL_DEBUG_V( float quality = 0.25f * cparams.butteraugli_distance;
2, JXL_DEBUG_V(2,
"Adding quantization constants corresponding to luma quality %.2f " "Adding quantization constants corresponding to distance %.3f ",
"and chroma quality %.2f", quality);
quality, cquality);
if (!cparams.responsive) { if (!cparams.responsive) {
JXL_DEBUG_V(1, JXL_DEBUG_V(1,
"Warning: lossy compression without Squeeze " "Warning: lossy compression without Squeeze "
"transform is just color quantization."); "transform is just color quantization.");
quality = (400 + quality) / 5; quality *= 0.1f;
cquality = (400 + cquality) / 5;
}
// convert 'quality' to quantization scaling factor
if (quality > 50) {
quality = 200.0 - quality * 2.0;
} else {
quality = 900.0 - quality * 16.0;
}
if (cquality > 50) {
cquality = 200.0 - cquality * 2.0;
} else {
cquality = 900.0 - cquality * 16.0;
} }
if (cparams.color_transform != ColorTransform::kXYB) { if (cparams.color_transform != ColorTransform::kXYB) {
quality *= 0.01f * maxval / 255.f; quality *= maxval / 255.f;
cquality *= 0.01f * maxval / 255.f;
} else {
quality *= 0.01f;
cquality *= 0.01f;
} }
if (cparams.options.nb_repeats == 0) { if (cparams.options.nb_repeats == 0) {
return JXL_FAILURE("nb_repeats = 0 not supported with modular lossy!"); return JXL_FAILURE("nb_repeats = 0 not supported with modular lossy!");
} }
@ -773,17 +800,18 @@ Status ModularFrameEncoder::ComputeEncodingData(
if (shift > 0) shift--; if (shift > 0) shift--;
int q; int q;
// assuming default Squeeze here // assuming default Squeeze here
int component = ((i - gi.nb_meta_channels) % nb_chans); int component =
(do_color ? 0 : 3) + ((i - gi.nb_meta_channels) % nb_chans);
// last 4 channels are final chroma residuals // last 4 channels are final chroma residuals
if (nb_chans > 2 && i >= gi.channel.size() - 4 && cparams.responsive) { if (nb_chans > 2 && i >= gi.channel.size() - 4 && cparams.responsive) {
component = 1; component = 1;
} }
if (cparams.color_transform == ColorTransform::kXYB && component < 3) { if (cparams.color_transform == ColorTransform::kXYB && component < 3) {
q = (component == 0 ? quality : cquality) * squeeze_quality_factor_xyb * q = quality * squeeze_quality_factor_xyb *
squeeze_xyb_qtable[component][shift]; squeeze_xyb_qtable[component][shift];
} else { } else {
if (cparams.colorspace != 0 && component > 0 && component < 3) { if (cparams.colorspace != 0 && component > 0 && component < 3) {
q = cquality * squeeze_quality_factor * squeeze_chroma_qtable[shift]; q = quality * squeeze_quality_factor * squeeze_chroma_qtable[shift];
} else { } else {
q = quality * squeeze_quality_factor * squeeze_luma_factor * q = quality * squeeze_quality_factor * squeeze_luma_factor *
squeeze_luma_qtable[shift]; squeeze_luma_qtable[shift];
@ -1288,12 +1316,10 @@ Status ModularFrameEncoder::PrepareStreamParams(const Rect& rect,
if (gi.channel.empty()) return true; if (gi.channel.empty()) return true;
// Do some per-group transforms // Do some per-group transforms
float quality = cparams.quality_pair.first;
// Local palette // Local palette
// TODO(veluca): make this work with quantize-after-prediction in lossy // TODO(veluca): make this work with quantize-after-prediction in lossy
// mode. // mode.
if (quality == 100 && cparams.palette_colors != 0 && if (cparams.butteraugli_distance == 0.f && cparams.palette_colors != 0 &&
cparams.speed_tier < SpeedTier::kCheetah) { cparams.speed_tier < SpeedTier::kCheetah) {
// all-channel palette (e.g. RGBA) // all-channel palette (e.g. RGBA)
if (gi.channel.size() - gi.nb_meta_channels > 1) { if (gi.channel.size() - gi.nb_meta_channels > 1) {
@ -1321,8 +1347,9 @@ Status ModularFrameEncoder::PrepareStreamParams(const Rect& rect,
} }
// Local channel palette // Local channel palette
if (cparams.channel_colors_percent > 0 && quality == 100 && if (cparams.channel_colors_percent > 0 &&
!cparams.lossy_palette && cparams.speed_tier < SpeedTier::kCheetah && cparams.butteraugli_distance == 0.f && !cparams.lossy_palette &&
cparams.speed_tier < SpeedTier::kCheetah &&
!(cparams.responsive && cparams.decoding_speed_tier >= 1)) { !(cparams.responsive && cparams.decoding_speed_tier >= 1)) {
// single channel palette (like FLIF's ChannelCompact) // single channel palette (like FLIF's ChannelCompact)
size_t nb_channels = gi.channel.size() - gi.nb_meta_channels; size_t nb_channels = gi.channel.size() - gi.nb_meta_channels;
@ -1348,8 +1375,9 @@ Status ModularFrameEncoder::PrepareStreamParams(const Rect& rect,
// lossless and no specific color transform specified: try Nothing, YCoCg, // lossless and no specific color transform specified: try Nothing, YCoCg,
// and 17 RCTs // and 17 RCTs
if (cparams.color_transform == ColorTransform::kNone && quality == 100 && if (cparams.color_transform == ColorTransform::kNone &&
cparams.colorspace < 0 && gi.channel.size() - gi.nb_meta_channels >= 3 && cparams.IsLossless() && cparams.colorspace < 0 &&
gi.channel.size() - gi.nb_meta_channels >= 3 &&
cparams.responsive == false && do_color && cparams.responsive == false && do_color &&
cparams.speed_tier <= SpeedTier::kHare) { cparams.speed_tier <= SpeedTier::kHare) {
Transform sg(TransformId::kRCT); Transform sg(TransformId::kRCT);

View File

@ -80,8 +80,7 @@ class ModularFrameEncoder {
std::vector<uint8_t> context_map; std::vector<uint8_t> context_map;
FrameDimensions frame_dim; FrameDimensions frame_dim;
CompressParams cparams; CompressParams cparams;
float quality = cparams.quality_pair.first; float quality = 100.f;
float cquality = cparams.quality_pair.second;
std::vector<size_t> tree_splits; std::vector<size_t> tree_splits;
std::vector<ModularMultiplierInfo> multiplier_info; std::vector<ModularMultiplierInfo> multiplier_info;
std::vector<std::vector<uint32_t>> gi_channel; std::vector<std::vector<uint32_t>> gi_channel;

View File

@ -127,6 +127,7 @@ struct CompressParams {
float max_error[3] = {0.0, 0.0, 0.0}; float max_error[3] = {0.0, 0.0, 0.0};
SpeedTier speed_tier = SpeedTier::kSquirrel; SpeedTier speed_tier = SpeedTier::kSquirrel;
int brotli_effort = -1;
// 0 = default. // 0 = default.
// 1 = slightly worse quality. // 1 = slightly worse quality.
@ -218,8 +219,6 @@ struct CompressParams {
int responsive = -1; int responsive = -1;
// empty for default squeeze // empty for default squeeze
std::vector<SqueezeParams> squeezes; std::vector<SqueezeParams> squeezes;
// A pair of <quality, cquality>.
std::pair<float, float> quality_pair{100.f, 100.f};
int colorspace = -1; int colorspace = -1;
// Use Global channel palette if #colors < this percentage of range // Use Global channel palette if #colors < this percentage of range
float channel_colors_pre_transform_percent = 95.f; float channel_colors_pre_transform_percent = 95.f;
@ -230,16 +229,16 @@ struct CompressParams {
// Returns whether these params are lossless as defined by SetLossless(); // Returns whether these params are lossless as defined by SetLossless();
bool IsLossless() const { bool IsLossless() const {
return modular_mode && quality_pair.first == 100 && // YCbCr is also considered lossless here since it's intended for
quality_pair.second == 100 && // source material that is already YCbCr (we don't do the fwd transform)
color_transform == jxl::ColorTransform::kNone; return modular_mode && butteraugli_distance == 0.0f &&
color_transform != jxl::ColorTransform::kXYB;
} }
// Sets the parameters required to make the codec lossless. // Sets the parameters required to make the codec lossless.
void SetLossless() { void SetLossless() {
modular_mode = true; modular_mode = true;
quality_pair.first = 100; butteraugli_distance = 0.0f;
quality_pair.second = 100;
color_transform = jxl::ColorTransform::kNone; color_transform = jxl::ColorTransform::kNone;
} }
@ -255,6 +254,10 @@ struct CompressParams {
// Skip the downsampling before encoding if this is true. // Skip the downsampling before encoding if this is true.
bool already_downsampled = false; bool already_downsampled = false;
// Codestream level to conform to.
// -1: don't care
int level = -1;
std::vector<float> manual_noise; std::vector<float> manual_noise;
std::vector<float> manual_xyb_factors; std::vector<float> manual_xyb_factors;
}; };

View File

@ -710,11 +710,6 @@ void FindBestPatchDictionary(const Image3F& opsin,
CompressParams cparams = state->cparams; CompressParams cparams = state->cparams;
// Recursive application of patches could create very weird issues. // Recursive application of patches could create very weird issues.
cparams.patches = Override::kOff; cparams.patches = Override::kOff;
// TODO(veluca): possibly change heuristics here.
if (!cparams.modular_mode) {
cparams.quality_pair.first = cparams.quality_pair.second =
90.f - cparams.butteraugli_distance * 5.f;
}
RoundtripPatchFrame(&reference_frame, state, 0, cparams, cms, pool, true); RoundtripPatchFrame(&reference_frame, state, 0, cparams, cms, pool, true);

View File

@ -146,6 +146,10 @@ int VerifyLevelSettings(const JxlEncoder* enc, std::string* debug_string) {
// Level 5 checks // Level 5 checks
if (!m.modular_16_bit_buffer_sufficient) {
if (debug_string) *debug_string = "Too high modular bit depth";
return 10;
}
if (xsize > (1ull << 18ull) || ysize > (1ull << 18ull) || if (xsize > (1ull << 18ull) || ysize > (1ull << 18ull) ||
xsize * ysize > (1ull << 28ull)) { xsize * ysize > (1ull << 28ull)) {
if (debug_string) *debug_string = "Too large image dimensions"; if (debug_string) *debug_string = "Too large image dimensions";
@ -248,6 +252,12 @@ JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() {
// TODO(lode): preview should be added here if a preview image is added // TODO(lode): preview should be added here if a preview image is added
writer.ZeroPadToByte(); writer.ZeroPadToByte();
// Not actually the end of frame, but the end of metadata/ICC, but helps
// the next frame to start here for indexing purposes.
codestream_bytes_written_end_of_frame +=
jxl::DivCeil(writer.BitsWritten(), 8);
bytes = std::move(writer).TakeBytes(); bytes = std::move(writer).TakeBytes();
if (MustUseContainer()) { if (MustUseContainer()) {
@ -363,9 +373,10 @@ JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() {
input_frame->option_values.header.layer_info.blend_info.alpha; input_frame->option_values.header.layer_info.blend_info.alpha;
frame_info.extra_channel_blending_info.resize( frame_info.extra_channel_blending_info.resize(
metadata.m.num_extra_channels); metadata.m.num_extra_channels);
// If extra channel blend info has not been set, use the default values. // If extra channel blend info has not been set, use the blend mode from the
JxlBlendInfo default_blend_info; // layer_info.
JxlEncoderInitBlendInfo(&default_blend_info); JxlBlendInfo default_blend_info =
input_frame->option_values.header.layer_info.blend_info;
for (size_t i = 0; i < metadata.m.num_extra_channels; ++i) { for (size_t i = 0; i < metadata.m.num_extra_channels; ++i) {
auto& to = frame_info.extra_channel_blending_info[i]; auto& to = frame_info.extra_channel_blending_info[i];
const auto& from = const auto& from =
@ -375,23 +386,28 @@ JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() {
to.mode = static_cast<jxl::BlendMode>(from.blendmode); to.mode = static_cast<jxl::BlendMode>(from.blendmode);
to.source = from.source; to.source = from.source;
to.alpha_channel = from.alpha; to.alpha_channel = from.alpha;
to.clamp = (from.clamp != 0);
} }
if (input_frame->option_values.header.layer_info.have_crop) { if (input_frame->option_values.header.layer_info.have_crop) {
ib.origin.x0 = input_frame->option_values.header.layer_info.crop_x0; ib.origin.x0 = input_frame->option_values.header.layer_info.crop_x0;
ib.origin.y0 = input_frame->option_values.header.layer_info.crop_y0; ib.origin.y0 = input_frame->option_values.header.layer_info.crop_y0;
} }
JXL_ASSERT(writer.BitsWritten() == 0);
if (!jxl::EncodeFrame(input_frame->option_values.cparams, frame_info, if (!jxl::EncodeFrame(input_frame->option_values.cparams, frame_info,
&metadata, input_frame->frame, &enc_state, cms, &metadata, input_frame->frame, &enc_state, cms,
thread_pool.get(), &writer, thread_pool.get(), &writer,
/*aux_out=*/nullptr)) { /*aux_out=*/nullptr)) {
return JXL_API_ERROR("Failed to encode frame"); return JXL_API_ERROR("Failed to encode frame");
} }
codestream_bytes_written_beginning_of_frame =
codestream_bytes_written_end_of_frame;
codestream_bytes_written_end_of_frame +=
jxl::DivCeil(writer.BitsWritten(), 8);
// Possibly bytes already contains the codestream header: in case this is // Possibly bytes already contains the codestream header: in case this is
// the first frame, and the codestream header was not encoded as jxlp above. // the first frame, and the codestream header was not encoded as jxlp above.
bytes.append(std::move(writer).TakeBytes()); bytes.append(std::move(writer).TakeBytes());
if (MustUseContainer()) { if (MustUseContainer()) {
if (last_frame && jxlp_counter == 0) { if (last_frame && jxlp_counter == 0) {
// If this is the last frame and no jxlp boxes were used yet, it's // If this is the last frame and no jxlp boxes were used yet, it's
@ -423,9 +439,10 @@ JxlEncoderStatus JxlEncoderStruct::RefillOutputByteQueue() {
for (size_t i = 0; i < 4; i++) { for (size_t i = 0; i < 4; i++) {
compressed[i] = static_cast<uint8_t>(box->type[i]); compressed[i] = static_cast<uint8_t>(box->type[i]);
} }
if (JXL_ENC_SUCCESS != BrotliCompress(9, box->contents.data(), if (JXL_ENC_SUCCESS !=
box->contents.size(), BrotliCompress((brotli_effort >= 0 ? brotli_effort : 4),
&compressed)) { box->contents.data(), box->contents.size(),
&compressed)) {
return JXL_API_ERROR("Brotli compression for brob box failed"); return JXL_API_ERROR("Brotli compression for brob box failed");
} }
jxl::AppendBoxHeader(jxl::MakeBoxType("brob"), compressed.size(), false, jxl::AppendBoxHeader(jxl::MakeBoxType("brob"), compressed.size(), false,
@ -561,7 +578,8 @@ JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
enc->metadata.m.bit_depth.floating_point_sample = enc->metadata.m.bit_depth.floating_point_sample =
(info->exponent_bits_per_sample != 0u); (info->exponent_bits_per_sample != 0u);
enc->metadata.m.modular_16_bit_buffer_sufficient = enc->metadata.m.modular_16_bit_buffer_sufficient =
(info->exponent_bits_per_sample == 0u) && info->bits_per_sample <= 12; (!info->uses_original_profile || info->bits_per_sample <= 12) &&
info->alpha_bits <= 12;
// The number of extra channels includes the alpha channel, so for example and // The number of extra channels includes the alpha channel, so for example and
// RGBA with no other extra channels, has exactly num_extra_channels == 1 // RGBA with no other extra channels, has exactly num_extra_channels == 1
@ -620,7 +638,15 @@ JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
enc->metadata.m.animation.num_loops = info->animation.num_loops; enc->metadata.m.animation.num_loops = info->animation.num_loops;
enc->metadata.m.animation.have_timecodes = info->animation.have_timecodes; enc->metadata.m.animation.have_timecodes = info->animation.have_timecodes;
} }
std::string level_message;
int required_level = VerifyLevelSettings(enc, &level_message);
if (required_level == -1 ||
static_cast<int>(enc->codestream_level) < required_level) {
return JXL_API_ERROR("%s", ("Codestream level verification for level " +
std::to_string(enc->codestream_level) +
" failed: " + level_message)
.c_str());
}
return JXL_ENC_SUCCESS; return JXL_ENC_SUCCESS;
} }
@ -652,6 +678,8 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelInfo(
jxl::ExtraChannelInfo& channel = enc->metadata.m.extra_channel_info[index]; jxl::ExtraChannelInfo& channel = enc->metadata.m.extra_channel_info[index];
channel.type = static_cast<jxl::ExtraChannel>(info->type); channel.type = static_cast<jxl::ExtraChannel>(info->type);
channel.bit_depth.bits_per_sample = info->bits_per_sample; channel.bit_depth.bits_per_sample = info->bits_per_sample;
enc->metadata.m.modular_16_bit_buffer_sufficient &=
info->bits_per_sample <= 12;
channel.bit_depth.exponent_bits_per_sample = info->exponent_bits_per_sample; channel.bit_depth.exponent_bits_per_sample = info->exponent_bits_per_sample;
channel.bit_depth.floating_point_sample = info->exponent_bits_per_sample != 0; channel.bit_depth.floating_point_sample = info->exponent_bits_per_sample != 0;
channel.dim_shift = info->dim_shift; channel.dim_shift = info->dim_shift;
@ -662,6 +690,15 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelInfo(
channel.spot_color[1] = info->spot_color[1]; channel.spot_color[1] = info->spot_color[1];
channel.spot_color[2] = info->spot_color[2]; channel.spot_color[2] = info->spot_color[2];
channel.spot_color[3] = info->spot_color[3]; channel.spot_color[3] = info->spot_color[3];
std::string level_message;
int required_level = VerifyLevelSettings(enc, &level_message);
if (required_level == -1 ||
static_cast<int>(enc->codestream_level) < required_level) {
return JXL_API_ERROR("%s", ("Codestream level verification for level " +
std::to_string(enc->codestream_level) +
" failed: " + level_message)
.c_str());
}
return JXL_ENC_SUCCESS; return JXL_ENC_SUCCESS;
} }
@ -688,6 +725,7 @@ JxlEncoderFrameSettings* JxlEncoderFrameSettingsCreate(
} else { } else {
opts->values.lossless = false; opts->values.lossless = false;
} }
opts->values.cparams.level = enc->codestream_level;
JxlEncoderFrameSettings* ret = opts.get(); JxlEncoderFrameSettings* ret = opts.get();
enc->encoder_options.emplace_back(std::move(opts)); enc->encoder_options.emplace_back(std::move(opts));
return ret; return ret;
@ -701,6 +739,10 @@ JxlEncoderFrameSettings* JxlEncoderOptionsCreate(
JxlEncoderStatus JxlEncoderSetFrameLossless( JxlEncoderStatus JxlEncoderSetFrameLossless(
JxlEncoderFrameSettings* frame_settings, const JXL_BOOL lossless) { JxlEncoderFrameSettings* frame_settings, const JXL_BOOL lossless) {
if (lossless && frame_settings->enc->basic_info_set &&
frame_settings->enc->metadata.m.xyb_encoded) {
return JXL_API_ERROR("Set use_original_profile=true for lossless encoding");
}
frame_settings->values.lossless = lossless; frame_settings->values.lossless = lossless;
return JXL_ENC_SUCCESS; return JXL_ENC_SUCCESS;
} }
@ -726,29 +768,6 @@ JxlEncoderStatus JxlEncoderSetFrameDistance(
distance = 0.01f; distance = 0.01f;
} }
frame_settings->values.cparams.butteraugli_distance = distance; frame_settings->values.cparams.butteraugli_distance = distance;
float jpeg_quality;
// Formula to translate butteraugli distance roughly into JPEG 0-100 quality.
// This is the inverse of the formula in cjxl.cc to translate JPEG quality
// into butteraugli distance.
if (distance > 6.56f) {
jpeg_quality = -5.456783f * std::log(0.0256f * distance - 0.16384f);
} else {
jpeg_quality = -11.11111f * distance + 101.11111f;
}
// Translate JPEG quality into the quality_pair setting for modular encoding.
// This is the formula also used in cjxl.cc to convert the command line JPEG
// quality parameter to the quality_pair setting.
// TODO(lode): combine the distance -> quality_pair conversion into a single
// formula, possibly altering it to a more suitable heuristic.
float quality;
if (jpeg_quality < 7.f) {
quality = std::min<float>(35.f + (jpeg_quality - 7.f) * 3.0f, 100.0f);
} else {
quality =
std::min<float>(35.f + (jpeg_quality - 7.f) * 65.f / 93.f, 100.0f);
}
frame_settings->values.cparams.quality_pair.first =
frame_settings->values.cparams.quality_pair.second = quality;
return JXL_ENC_SUCCESS; return JXL_ENC_SUCCESS;
} }
@ -775,6 +794,15 @@ JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
frame_settings->values.cparams.speed_tier = frame_settings->values.cparams.speed_tier =
static_cast<jxl::SpeedTier>(10 - value); static_cast<jxl::SpeedTier>(10 - value);
return JXL_ENC_SUCCESS; return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_BROTLI_EFFORT:
if (value < -1 || value > 11) {
return JXL_ENC_ERROR;
}
// set cparams for brotli use in JPEG frames
frame_settings->values.cparams.brotli_effort = value;
// set enc option for brotli use in brob boxes
frame_settings->enc->brotli_effort = value;
return JXL_ENC_SUCCESS;
case JXL_ENC_FRAME_SETTING_DECODING_SPEED: case JXL_ENC_FRAME_SETTING_DECODING_SPEED:
if (value < 0 || value > 4) { if (value < 0 || value > 4) {
return JXL_ENC_ERROR; return JXL_ENC_ERROR;
@ -1008,7 +1036,8 @@ void JxlEncoderReset(JxlEncoder* enc) {
enc->num_queued_boxes = 0; enc->num_queued_boxes = 0;
enc->encoder_options.clear(); enc->encoder_options.clear();
enc->output_byte_queue.clear(); enc->output_byte_queue.clear();
enc->output_bytes_flushed = 0; enc->codestream_bytes_written_beginning_of_frame = 0;
enc->codestream_bytes_written_end_of_frame = 0;
enc->wrote_bytes = false; enc->wrote_bytes = false;
enc->jxlp_counter = 0; enc->jxlp_counter = 0;
enc->metadata = jxl::CodecMetadata(); enc->metadata = jxl::CodecMetadata();
@ -1025,9 +1054,10 @@ void JxlEncoderReset(JxlEncoder* enc) {
void JxlEncoderDestroy(JxlEncoder* enc) { void JxlEncoderDestroy(JxlEncoder* enc) {
if (enc) { if (enc) {
JxlMemoryManager local_memory_manager = enc->memory_manager;
// Call destructor directly since custom free function is used. // Call destructor directly since custom free function is used.
enc->~JxlEncoder(); enc->~JxlEncoder();
jxl::MemoryManagerFree(&enc->memory_manager, enc); jxl::MemoryManagerFree(&local_memory_manager, enc);
} }
} }
@ -1062,7 +1092,10 @@ int JxlEncoderGetRequiredCodestreamLevel(const JxlEncoder* enc) {
return VerifyLevelSettings(enc, nullptr); return VerifyLevelSettings(enc, nullptr);
} }
void JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms) { enc->cms = cms; } void JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms) {
jxl::msan::MemoryIsInitialized(&cms, sizeof(cms));
enc->cms = cms;
}
JxlEncoderStatus JxlEncoderSetParallelRunner(JxlEncoder* enc, JxlEncoderStatus JxlEncoderSetParallelRunner(JxlEncoder* enc,
JxlParallelRunner parallel_runner, JxlParallelRunner parallel_runner,
@ -1138,7 +1171,8 @@ JxlEncoderStatus JxlEncoderAddJPEGFrame(
if (frame_settings->enc->store_jpeg_metadata) { if (frame_settings->enc->store_jpeg_metadata) {
jxl::jpeg::JPEGData data_in = *io.Main().jpeg_data; jxl::jpeg::JPEGData data_in = *io.Main().jpeg_data;
jxl::PaddedBytes jpeg_data; jxl::PaddedBytes jpeg_data;
if (!jxl::jpeg::EncodeJPEGData(data_in, &jpeg_data)) { if (!jxl::jpeg::EncodeJPEGData(data_in, &jpeg_data,
frame_settings->values.cparams)) {
return JXL_ENC_ERROR; return JXL_ENC_ERROR;
} }
frame_settings->enc->jpeg_metadata = std::vector<uint8_t>( frame_settings->enc->jpeg_metadata = std::vector<uint8_t>(
@ -1247,15 +1281,31 @@ JxlEncoderStatus JxlEncoderAddImageFrame(
queued_frame->ec_initialized.push_back(0); queued_frame->ec_initialized.push_back(0);
} }
} }
queued_frame->frame.origin.x0 =
frame_settings->values.header.layer_info.crop_x0;
queued_frame->frame.origin.y0 =
frame_settings->values.header.layer_info.crop_y0;
queued_frame->frame.use_for_next_frame =
(frame_settings->values.header.layer_info.save_as_reference != 0u);
queued_frame->frame.blendmode =
frame_settings->values.header.layer_info.blend_info.blendmode ==
JXL_BLEND_REPLACE
? jxl::BlendMode::kReplace
: jxl::BlendMode::kBlend;
queued_frame->frame.blend =
frame_settings->values.header.layer_info.blend_info.source > 0;
if (!jxl::BufferToImageBundle(*pixel_format, xsize, ysize, buffer, size, if (!jxl::BufferToImageBundle(*pixel_format, xsize, ysize, buffer, size,
frame_settings->enc->thread_pool.get(), frame_settings->enc->thread_pool.get(),
c_current, &(queued_frame->frame))) { c_current, &(queued_frame->frame))) {
return JXL_ENC_ERROR; return JXL_ENC_ERROR;
} }
if (frame_settings->values.lossless) { if (frame_settings->values.lossless &&
queued_frame->option_values.cparams.SetLossless(); frame_settings->enc->metadata.m.xyb_encoded) {
return JXL_API_ERROR("Set use_original_profile=true for lossless encoding");
} }
queued_frame->option_values.cparams.level =
frame_settings->enc->codestream_level;
QueueFrame(frame_settings, queued_frame); QueueFrame(frame_settings, queued_frame);
return JXL_ENC_SUCCESS; return JXL_ENC_SUCCESS;
@ -1350,7 +1400,6 @@ JxlEncoderStatus JxlEncoderProcessOutput(JxlEncoder* enc, uint8_t** next_out,
std::copy_n(enc->output_byte_queue.begin(), to_copy, *next_out); std::copy_n(enc->output_byte_queue.begin(), to_copy, *next_out);
*next_out += to_copy; *next_out += to_copy;
*avail_out -= to_copy; *avail_out -= to_copy;
enc->output_bytes_flushed += to_copy;
enc->output_byte_queue.erase(enc->output_byte_queue.begin(), enc->output_byte_queue.erase(enc->output_byte_queue.begin(),
enc->output_byte_queue.begin() + to_copy); enc->output_byte_queue.begin() + to_copy);
} else if (!enc->input_queue.empty()) { } else if (!enc->input_queue.empty()) {
@ -1406,7 +1455,7 @@ JxlEncoderStatus JxlEncoderSetExtraChannelBlendInfo(
JxlEncoderStatus JxlEncoderSetFrameName(JxlEncoderFrameSettings* frame_settings, JxlEncoderStatus JxlEncoderSetFrameName(JxlEncoderFrameSettings* frame_settings,
const char* frame_name) { const char* frame_name) {
std::string str = frame_name; std::string str = frame_name ? frame_name : "";
if (str.size() > 1071) { if (str.size() > 1071) {
return JXL_API_ERROR("frame name can be max 1071 bytes long"); return JXL_API_ERROR("frame name can be max 1071 bytes long");
} }

View File

@ -117,12 +117,15 @@ struct JxlEncoderStruct {
size_t num_queued_boxes; size_t num_queued_boxes;
std::vector<jxl::JxlEncoderQueuedInput> input_queue; std::vector<jxl::JxlEncoderQueuedInput> input_queue;
std::deque<uint8_t> output_byte_queue; std::deque<uint8_t> output_byte_queue;
size_t output_bytes_flushed;
// Get the current write position in the stream (for indexing use). // How many codestream bytes have been written, i.e.,
size_t BytePosition() const { // content of jxlc and jxlp boxes. Frame index box jxli
return output_bytes_flushed + output_byte_queue.size(); // requires position indices to point to codestream bytes,
} // so we need to keep track of the total of flushed or queue
// codestream bytes. These bytes may be in a single jxlc box
// or accross multiple jxlp boxes.
size_t codestream_bytes_written_beginning_of_frame;
size_t codestream_bytes_written_end_of_frame;
// Force using the container even if not needed // Force using the container even if not needed
bool use_container; bool use_container;
@ -153,6 +156,7 @@ struct JxlEncoderStruct {
bool basic_info_set; bool basic_info_set;
bool color_encoding_set; bool color_encoding_set;
bool intensity_target_set; bool intensity_target_set;
int brotli_effort = -1;
// Takes the first frame in the input_queue, encodes it, and appends // Takes the first frame in the input_queue, encodes it, and appends
// the bytes to the output_byte_queue. // the bytes to the output_byte_queue.

View File

@ -38,6 +38,7 @@ TEST(EncodeTest, AddFrameAfterCloseInputTest) {
basic_info.xsize = xsize; basic_info.xsize = xsize;
basic_info.ysize = ysize; basic_info.ysize = ysize;
basic_info.uses_original_profile = false; basic_info.uses_original_profile = false;
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
JxlColorEncoding color_encoding; JxlColorEncoding color_encoding;
JxlColorEncodingSetToSRGB(&color_encoding, JxlColorEncodingSetToSRGB(&color_encoding,
@ -58,7 +59,7 @@ TEST(EncodeTest, AddJPEGAfterCloseTest) {
JxlEncoderCloseInput(enc.get()); JxlEncoderCloseInput(enc.get());
const std::string jpeg_path = const std::string jpeg_path =
"imagecompression.info/flower_foveon.png.im_q85_420.jpg"; "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg";
const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path); const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path);
JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettings* frame_settings =
@ -85,6 +86,7 @@ TEST(EncodeTest, AddFrameBeforeColorEncodingTest) {
basic_info.xsize = xsize; basic_info.xsize = xsize;
basic_info.ysize = ysize; basic_info.ysize = ysize;
basic_info.uses_original_profile = true; basic_info.uses_original_profile = true;
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettings* frame_settings =
JxlEncoderFrameSettingsCreate(enc.get(), NULL); JxlEncoderFrameSettingsCreate(enc.get(), NULL);
@ -173,6 +175,8 @@ void VerifyFrameEncoding(size_t xsize, size_t ysize, JxlEncoder* enc,
} else { } else {
basic_info.uses_original_profile = false; basic_info.uses_original_profile = false;
} }
// 16-bit alpha means this requires level 10
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
JxlColorEncoding color_encoding; JxlColorEncoding color_encoding;
JxlColorEncodingSetToSRGB(&color_encoding, JxlColorEncodingSetToSRGB(&color_encoding,
@ -198,7 +202,6 @@ void VerifyFrameEncoding(size_t xsize, size_t ysize, JxlEncoder* enc,
} }
compressed.resize(next_out - compressed.data()); compressed.resize(next_out - compressed.data());
EXPECT_EQ(JXL_ENC_SUCCESS, process_result); EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
jxl::DecompressParams dparams; jxl::DecompressParams dparams;
jxl::CodecInOut decoded_io; jxl::CodecInOut decoded_io;
EXPECT_TRUE(jxl::DecodeFile( EXPECT_TRUE(jxl::DecodeFile(
@ -630,6 +633,7 @@ TEST(EncodeTest, SingleFrameBoundedJXLCTest) {
basic_info.xsize = xsize; basic_info.xsize = xsize;
basic_info.ysize = ysize; basic_info.ysize = ysize;
basic_info.uses_original_profile = false; basic_info.uses_original_profile = false;
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
JxlColorEncoding color_encoding; JxlColorEncoding color_encoding;
JxlColorEncodingSetToSRGB(&color_encoding, JxlColorEncodingSetToSRGB(&color_encoding,
@ -739,7 +743,7 @@ TEST(EncodeTest, CodestreamLevelTest) {
} }
TEST(EncodeTest, CodestreamLevelVerificationTest) { TEST(EncodeTest, CodestreamLevelVerificationTest) {
JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0};
JxlBasicInfo basic_info; JxlBasicInfo basic_info;
jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
@ -755,21 +759,20 @@ TEST(EncodeTest, CodestreamLevelVerificationTest) {
// Set an image dimension that is too large for level 5, but fits in level 10 // Set an image dimension that is too large for level 5, but fits in level 10
basic_info.xsize = 1ull << 30ull; basic_info.xsize = 1ull << 30ull;
EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
EXPECT_EQ(10, JxlEncoderGetRequiredCodestreamLevel(enc.get())); EXPECT_EQ(10, JxlEncoderGetRequiredCodestreamLevel(enc.get()));
// Set an image dimension that is too large even for level 10 // Set an image dimension that is too large even for level 10
basic_info.xsize = 1ull << 31ull; basic_info.xsize = 1ull << 31ull;
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
EXPECT_EQ(-1, JxlEncoderGetRequiredCodestreamLevel(enc.get()));
} }
TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) { TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) {
const std::string jpeg_path = const std::string jpeg_path =
"imagecompression.info/flower_foveon.png.im_q85_420.jpg"; "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg";
const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path); const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path);
JxlEncoderPtr enc = JxlEncoderMake(nullptr); JxlEncoderPtr enc = JxlEncoderMake(nullptr);
@ -900,6 +903,7 @@ TEST(EncodeTest, BasicInfoTest) {
basic_info.animation.tps_denominator = 77; basic_info.animation.tps_denominator = 77;
basic_info.animation.num_loops = 10; basic_info.animation.num_loops = 10;
basic_info.animation.have_timecodes = JXL_TRUE; basic_info.animation.have_timecodes = JXL_TRUE;
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
JxlColorEncoding color_encoding; JxlColorEncoding color_encoding;
JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/false); JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/false);
@ -1001,6 +1005,7 @@ TEST(EncodeTest, AnimationHeaderTest) {
basic_info.animation.tps_numerator = 1000; basic_info.animation.tps_numerator = 1000;
basic_info.animation.tps_denominator = 1; basic_info.animation.tps_denominator = 1;
basic_info.animation.have_timecodes = JXL_TRUE; basic_info.animation.have_timecodes = JXL_TRUE;
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
JxlColorEncoding color_encoding; JxlColorEncoding color_encoding;
JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/false); JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/false);
@ -1102,6 +1107,7 @@ TEST(EncodeTest, CroppedFrameTest) {
basic_info.xsize = 100; basic_info.xsize = 100;
basic_info.ysize = 100; basic_info.ysize = 100;
basic_info.uses_original_profile = JXL_TRUE; basic_info.uses_original_profile = JXL_TRUE;
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
JxlColorEncoding color_encoding; JxlColorEncoding color_encoding;
JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/false); JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/false);
@ -1194,6 +1200,7 @@ TEST(EncodeTest, BoxTest) {
basic_info.xsize = xsize; basic_info.xsize = xsize;
basic_info.ysize = ysize; basic_info.ysize = ysize;
basic_info.uses_original_profile = false; basic_info.uses_original_profile = false;
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info));
JxlColorEncoding color_encoding; JxlColorEncoding color_encoding;
JxlColorEncodingSetToSRGB(&color_encoding, JxlColorEncodingSetToSRGB(&color_encoding,
@ -1300,7 +1307,7 @@ TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGFrameTest)) {
for (int skip_color_encoding = 0; skip_color_encoding < 2; for (int skip_color_encoding = 0; skip_color_encoding < 2;
skip_color_encoding++) { skip_color_encoding++) {
const std::string jpeg_path = const std::string jpeg_path =
"imagecompression.info/flower_foveon_cropped.jpg"; "third_party/imagecompression.info/flower_foveon_cropped.jpg";
const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path); const jxl::PaddedBytes orig = jxl::ReadTestData(jpeg_path);
jxl::CodecInOut orig_io; jxl::CodecInOut orig_io;
ASSERT_TRUE(SetFromBytes(jxl::Span<const uint8_t>(orig), &orig_io, ASSERT_TRUE(SetFromBytes(jxl::Span<const uint8_t>(orig), &orig_io,

89
third_party/jpeg-xl/lib/jxl/exif.cc vendored Normal file
View File

@ -0,0 +1,89 @@
// Copyright (c) the JPEG XL Project 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/exif.h"
namespace jxl {
constexpr uint16_t kExifOrientationTag = 274;
// Checks if a blob looks like Exif, and if so, sets bigendian
// according to the tiff endianness
bool IsExif(const std::vector<uint8_t>& exif, bool* bigendian) {
if (exif.size() < 12) return false; // not enough bytes for a valid exif blob
const uint8_t* t = exif.data();
if (LoadLE32(t) == 0x2A004D4D) {
*bigendian = true;
return true;
} else if (LoadLE32(t) == 0x002A4949) {
*bigendian = false;
return true;
}
return false; // not a valid tiff header
}
// Finds the position of an Exif tag, or 0 if it is not found
size_t FindExifTagPosition(const std::vector<uint8_t>& exif, uint16_t tagname) {
bool bigendian;
if (!IsExif(exif, &bigendian)) return 0;
const uint8_t* t = exif.data() + 4;
uint32_t offset = (bigendian ? LoadBE32(t) : LoadLE32(t));
if (exif.size() < 12 + offset + 2 || offset < 8) return 0;
t += offset - 4;
uint16_t nb_tags = (bigendian ? LoadBE16(t) : LoadLE16(t));
t += 2;
while (nb_tags > 0) {
if (t + 12 >= exif.data() + exif.size()) return 0;
uint16_t tag = (bigendian ? LoadBE16(t) : LoadLE16(t));
t += 2;
if (tag == tagname) return static_cast<size_t>(t - exif.data());
t += 10;
nb_tags--;
}
return 0;
}
// TODO (jon): tag 1 can be used to represent Adobe RGB 1998 if it has value
// "R03"
// TODO (jon): set intrinsic dimensions according to
// https://discourse.wicg.io/t/proposal-exif-image-resolution-auto-and-from-image/4326/24
void InterpretExif(const std::vector<uint8_t>& exif, CodecMetadata* metadata) {
bool bigendian;
if (!IsExif(exif, &bigendian)) return;
size_t o_pos = FindExifTagPosition(exif, kExifOrientationTag);
if (o_pos) {
const uint8_t* t = exif.data() + o_pos;
uint16_t type = (bigendian ? LoadBE16(t) : LoadLE16(t));
t += 2;
uint32_t count = (bigendian ? LoadBE32(t) : LoadLE32(t));
t += 4;
uint16_t value = (bigendian ? LoadBE16(t) : LoadLE16(t));
t += 4;
if (type == 3 && count == 1 && value >= 1 && value <= 8) {
metadata->m.orientation = value;
}
}
}
void ResetExifOrientation(std::vector<uint8_t>& exif) {
bool bigendian;
if (!IsExif(exif, &bigendian)) return;
size_t o_pos = FindExifTagPosition(exif, kExifOrientationTag);
if (o_pos) {
uint8_t* t = exif.data() + o_pos;
uint16_t type = (bigendian ? LoadBE16(t) : LoadLE16(t));
t += 2;
uint32_t count = (bigendian ? LoadBE32(t) : LoadLE32(t));
t += 4;
if (type == 3 && count == 1) {
if (bigendian)
StoreBE16(1, t);
else
StoreLE16(1, t);
}
}
}
} // namespace jxl

27
third_party/jpeg-xl/lib/jxl/exif.h vendored Normal file
View File

@ -0,0 +1,27 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#ifndef LIB_JXL_EXIF_H_
#define LIB_JXL_EXIF_H_
// Basic parsing of Exif (just enough for the render-impacting things
// like orientation)
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/image_metadata.h"
namespace jxl {
// Parses the Exif data just enough to extract any render-impacting info.
// If the Exif data is invalid or could not be parsed, then it is treated
// as a no-op.
void InterpretExif(const std::vector<uint8_t>& exif, CodecMetadata* metadata);
// Sets the Exif orientation to the identity, to avoid repeated orientation
void ResetExifOrientation(std::vector<uint8_t>& exif);
} // namespace jxl
#endif // LIB_JXL_EXIF_H_

View File

@ -40,7 +40,7 @@ static inline Status VisitNameString(Visitor* JXL_RESTRICT visitor,
name->resize(name_length); name->resize(name_length);
} }
for (size_t i = 0; i < name_length; i++) { for (size_t i = 0; i < name_length; i++) {
uint32_t c = (*name)[i]; uint32_t c = static_cast<uint8_t>((*name)[i]);
JXL_QUIET_RETURN_IF_ERROR(visitor->Bits(8, 0, &c)); JXL_QUIET_RETURN_IF_ERROR(visitor->Bits(8, 0, &c));
(*name)[i] = static_cast<char>(c); (*name)[i] = static_cast<char>(c);
} }

View File

@ -22,19 +22,6 @@
namespace jxl { namespace jxl {
namespace { namespace {
uint64_t DecodeVarInt(const uint8_t* input, size_t inputSize, size_t* pos) {
size_t i;
uint64_t ret = 0;
for (i = 0; *pos + i < inputSize && i < 10; ++i) {
ret |= uint64_t(input[*pos + i] & 127) << uint64_t(7 * i);
// If the next-byte flag is not set, stop
if ((input[*pos + i] & 128) == 0) break;
}
// TODO: Return a decoding error if i == 10.
*pos += i + 1;
return ret;
}
// Shuffles or interleaves bytes, for example with width 2, turns "ABCDabcd" // Shuffles or interleaves bytes, for example with width 2, turns "ABCDabcd"
// into "AaBbCcDc". Transposes a matrix of ceil(size / width) columns and // into "AaBbCcDc". Transposes a matrix of ceil(size / width) columns and
// width rows. There are size elements, size may be < width * height, if so the // width rows. There are size elements, size may be < width * height, if so the

View File

@ -133,47 +133,6 @@ void PlaneBase::Swap(PlaneBase& other) {
std::swap(bytes_, other.bytes_); std::swap(bytes_, other.bytes_);
} }
ImageB ImageFromPacked(const uint8_t* packed, const size_t xsize,
const size_t ysize, const size_t bytes_per_row) {
JXL_ASSERT(bytes_per_row >= xsize);
ImageB image(xsize, ysize);
PROFILER_FUNC;
for (size_t y = 0; y < ysize; ++y) {
uint8_t* const JXL_RESTRICT row = image.Row(y);
const uint8_t* const JXL_RESTRICT packed_row = packed + y * bytes_per_row;
memcpy(row, packed_row, xsize);
}
return image;
}
// Note that using mirroring here gives slightly worse results.
ImageF PadImage(const ImageF& in, const size_t xsize, const size_t ysize) {
JXL_ASSERT(xsize >= in.xsize());
JXL_ASSERT(ysize >= in.ysize());
ImageF out(xsize, ysize);
size_t y = 0;
for (; y < in.ysize(); ++y) {
const float* JXL_RESTRICT row_in = in.ConstRow(y);
float* JXL_RESTRICT row_out = out.Row(y);
memcpy(row_out, row_in, in.xsize() * sizeof(row_in[0]));
const int lastcol = in.xsize() - 1;
const float lastval = row_out[lastcol];
for (size_t x = in.xsize(); x < xsize; ++x) {
row_out[x] = lastval;
}
}
// TODO(janwas): no need to copy if we can 'extend' image: if rows are
// pointers to any memory? Or allocate larger image before IO?
const int lastrow = in.ysize() - 1;
for (; y < ysize; ++y) {
const float* JXL_RESTRICT row_in = out.ConstRow(lastrow);
float* JXL_RESTRICT row_out = out.Row(y);
memcpy(row_out, row_in, xsize * sizeof(row_out[0]));
}
return out;
}
Image3F PadImageMirror(const Image3F& in, const size_t xborder, Image3F PadImageMirror(const Image3F& in, const size_t xborder,
const size_t yborder) { const size_t yborder) {
size_t xsize = in.xsize(); size_t xsize = in.xsize();
@ -216,19 +175,6 @@ Image3F PadImageMirror(const Image3F& in, const size_t xborder,
return out; return out;
} }
Image3F PadImageToMultiple(const Image3F& in, const size_t N) {
PROFILER_FUNC;
const size_t xsize_blocks = DivCeil(in.xsize(), N);
const size_t ysize_blocks = DivCeil(in.ysize(), N);
const size_t xsize = N * xsize_blocks;
const size_t ysize = N * ysize_blocks;
ImageF out[3];
for (size_t c = 0; c < 3; ++c) {
out[c] = PadImage(in.Plane(c), xsize, ysize);
}
return Image3F(std::move(out[0]), std::move(out[1]), std::move(out[2]));
}
void PadImageToBlockMultipleInPlace(Image3F* JXL_RESTRICT in) { void PadImageToBlockMultipleInPlace(Image3F* JXL_RESTRICT in) {
PROFILER_FUNC; PROFILER_FUNC;
const size_t xsize_orig = in->xsize(); const size_t xsize_orig = in->xsize();

View File

@ -194,87 +194,94 @@ class Image3;
// shifting the pointer by x0/y0 allows this to apply to multiple images with // shifting the pointer by x0/y0 allows this to apply to multiple images with
// different resolutions (e.g. color transform and quantization field). // different resolutions (e.g. color transform and quantization field).
// Can compare using SameSize(rect1, rect2). // Can compare using SameSize(rect1, rect2).
class Rect { template <typename T>
class RectT {
public: public:
// Most windows are xsize_max * ysize_max, except those on the borders where // Most windows are xsize_max * ysize_max, except those on the borders where
// begin + size_max > end. // begin + size_max > end.
constexpr Rect(size_t xbegin, size_t ybegin, size_t xsize_max, constexpr RectT(T xbegin, T ybegin, size_t xsize_max, size_t ysize_max,
size_t ysize_max, size_t xend, size_t yend) T xend, T yend)
: x0_(xbegin), : x0_(xbegin),
y0_(ybegin), y0_(ybegin),
xsize_(ClampedSize(xbegin, xsize_max, xend)), xsize_(ClampedSize(xbegin, xsize_max, xend)),
ysize_(ClampedSize(ybegin, ysize_max, yend)) {} ysize_(ClampedSize(ybegin, ysize_max, yend)) {}
// Construct with origin and known size (typically from another Rect). // Construct with origin and known size (typically from another Rect).
constexpr Rect(size_t xbegin, size_t ybegin, size_t xsize, size_t ysize) constexpr RectT(T xbegin, T ybegin, size_t xsize, size_t ysize)
: x0_(xbegin), y0_(ybegin), xsize_(xsize), ysize_(ysize) {} : x0_(xbegin), y0_(ybegin), xsize_(xsize), ysize_(ysize) {}
// Construct a rect that covers a whole image/plane/ImageBundle etc. // Construct a rect that covers a whole image/plane/ImageBundle etc.
template <typename Image> template <typename ImageT>
explicit Rect(const Image& image) explicit RectT(const ImageT& image)
: Rect(0, 0, image.xsize(), image.ysize()) {} : RectT(0, 0, image.xsize(), image.ysize()) {}
Rect() : Rect(0, 0, 0, 0) {} RectT() : RectT(0, 0, 0, 0) {}
Rect(const Rect&) = default; RectT(const RectT&) = default;
Rect& operator=(const Rect&) = default; RectT& operator=(const RectT&) = default;
// Construct a subrect that resides in an image/plane/ImageBundle etc. // Construct a subrect that resides in an image/plane/ImageBundle etc.
template <typename Image> template <typename ImageT>
Rect Crop(const Image& image) const { RectT Crop(const ImageT& image) const {
return Rect(x0_, y0_, xsize_, ysize_, image.xsize(), image.ysize()); return Intersection(RectT(image));
} }
// Construct a subrect that resides in the [0, ysize) x [0, xsize) region of // Construct a subrect that resides in the [0, ysize) x [0, xsize) region of
// the current rect. // the current rect.
Rect Crop(size_t area_xsize, size_t area_ysize) const { RectT Crop(size_t area_xsize, size_t area_ysize) const {
return Rect(x0_, y0_, xsize_, ysize_, area_xsize, area_ysize); return Intersection(RectT(0, 0, area_xsize, area_ysize));
} }
// Returns a rect that only contains `num` lines with offset `y` from `y0()`. // Returns a rect that only contains `num` lines with offset `y` from `y0()`.
Rect Lines(size_t y, size_t num) const { RectT Lines(size_t y, size_t num) const {
JXL_DASSERT(y + num <= ysize_); JXL_DASSERT(y + num <= ysize_);
return Rect(x0_, y0_ + y, xsize_, num); return RectT(x0_, y0_ + y, xsize_, num);
} }
Rect Line(size_t y) const { return Lines(y, 1); } RectT Line(size_t y) const { return Lines(y, 1); }
JXL_MUST_USE_RESULT Rect Intersection(const Rect& other) const { JXL_MUST_USE_RESULT RectT Intersection(const RectT& other) const {
return Rect(std::max(x0_, other.x0_), std::max(y0_, other.y0_), xsize_, return RectT(std::max(x0_, other.x0_), std::max(y0_, other.y0_), xsize_,
ysize_, std::min(x0_ + xsize_, other.x0_ + other.xsize_), ysize_, std::min(x0_ + xsize_, other.x0_ + other.xsize_),
std::min(y0_ + ysize_, other.y0_ + other.ysize_)); std::min(y0_ + ysize_, other.y0_ + other.ysize_));
} }
JXL_MUST_USE_RESULT Rect Translate(int64_t x_offset, int64_t y_offset) const { JXL_MUST_USE_RESULT RectT Translate(int64_t x_offset,
return Rect(x0_ + x_offset, y0_ + y_offset, xsize_, ysize_); int64_t y_offset) const {
return RectT(x0_ + x_offset, y0_ + y_offset, xsize_, ysize_);
} }
template <typename T> template <typename V>
T* Row(Plane<T>* image, size_t y) const { V* Row(Plane<V>* image, size_t y) const {
JXL_DASSERT(y + y0_ >= 0);
return image->Row(y + y0_) + x0_; return image->Row(y + y0_) + x0_;
} }
template <typename T> template <typename V>
const T* Row(const Plane<T>* image, size_t y) const { const V* Row(const Plane<V>* image, size_t y) const {
JXL_DASSERT(y + y0_ >= 0);
return image->Row(y + y0_) + x0_; return image->Row(y + y0_) + x0_;
} }
template <typename T> template <typename V>
T* PlaneRow(Image3<T>* image, const size_t c, size_t y) const { V* PlaneRow(Image3<V>* image, const size_t c, size_t y) const {
JXL_DASSERT(y + y0_ >= 0);
return image->PlaneRow(c, y + y0_) + x0_; return image->PlaneRow(c, y + y0_) + x0_;
} }
template <typename T> template <typename V>
const T* ConstRow(const Plane<T>& image, size_t y) const { const V* ConstRow(const Plane<V>& image, size_t y) const {
JXL_DASSERT(y + y0_ >= 0);
return image.ConstRow(y + y0_) + x0_; return image.ConstRow(y + y0_) + x0_;
} }
template <typename T> template <typename V>
const T* ConstPlaneRow(const Image3<T>& image, size_t c, size_t y) const { const V* ConstPlaneRow(const Image3<V>& image, size_t c, size_t y) const {
JXL_DASSERT(y + y0_ >= 0);
return image.ConstPlaneRow(c, y + y0_) + x0_; return image.ConstPlaneRow(c, y + y0_) + x0_;
} }
bool IsInside(const Rect& other) const { bool IsInside(const RectT& other) const {
return x0_ >= other.x0() && x0_ + xsize_ <= other.x0() + other.xsize_ && return x0_ >= other.x0() && x0_ + xsize_ <= other.x0() + other.xsize_ &&
y0_ >= other.y0() && y0_ + ysize_ <= other.y0() + other.ysize(); y0_ >= other.y0() && y0_ + ysize_ <= other.y0() + other.ysize();
} }
@ -283,29 +290,33 @@ class Rect {
// Plane<T> or Image3<T>; however if ImageT is Rect, results are nonsensical. // Plane<T> or Image3<T>; however if ImageT is Rect, results are nonsensical.
template <class ImageT> template <class ImageT>
bool IsInside(const ImageT& image) const { bool IsInside(const ImageT& image) const {
return (x0_ + xsize_ <= image.xsize()) && (y0_ + ysize_ <= image.ysize()); return IsInside(RectT(image));
} }
size_t x0() const { return x0_; } T x0() const { return x0_; }
size_t y0() const { return y0_; } T y0() const { return y0_; }
size_t xsize() const { return xsize_; } size_t xsize() const { return xsize_; }
size_t ysize() const { return ysize_; } size_t ysize() const { return ysize_; }
T x1() const { return x0_ + xsize_; }
T y1() const { return y0_ + ysize_; }
private: private:
// Returns size_max, or whatever is left in [begin, end). // Returns size_max, or whatever is left in [begin, end).
static constexpr size_t ClampedSize(size_t begin, size_t size_max, static constexpr size_t ClampedSize(T begin, size_t size_max, T end) {
size_t end) { return (static_cast<T>(begin + size_max) <= end)
return (begin + size_max <= end) ? size_max ? size_max
: (end > begin ? end - begin : 0); : (end > begin ? end - begin : 0);
} }
size_t x0_; T x0_;
size_t y0_; T y0_;
size_t xsize_; size_t xsize_;
size_t ysize_; size_t ysize_;
}; };
using Rect = RectT<size_t>;
// Currently, we abuse Image to either refer to an image that owns its storage // Currently, we abuse Image to either refer to an image that owns its storage
// or one that doesn't. In similar vein, we abuse Image* function parameters to // or one that doesn't. In similar vein, we abuse Image* function parameters to
// either mean "assign to me" or "fill the provided image with data". // either mean "assign to me" or "fill the provided image with data".

View File

@ -789,19 +789,10 @@ void ZeroFillPlane(Plane<T>* image, Rect rect) {
} }
} }
// First, image is padded horizontally, with the rightmost value.
// Next, image is padded vertically, by repeating the last line.
ImageF PadImage(const ImageF& in, size_t xsize, size_t ysize);
// Pad an image with xborder columns on each vertical side and yboder rows // Pad an image with xborder columns on each vertical side and yboder rows
// above and below, mirroring the image. // above and below, mirroring the image.
Image3F PadImageMirror(const Image3F& in, size_t xborder, size_t yborder); Image3F PadImageMirror(const Image3F& in, size_t xborder, size_t yborder);
// First, image is padded horizontally, with the rightmost value.
// Next, image is padded vertically, by repeating the last line.
// Prefer PadImageToBlockMultipleInPlace if padding to kBlockDim.
Image3F PadImageToMultiple(const Image3F& in, size_t N);
// Same as above, but operates in-place. Assumes that the `in` image was // Same as above, but operates in-place. Assumes that the `in` image was
// allocated large enough. // allocated large enough.
void PadImageToBlockMultipleInPlace(Image3F* JXL_RESTRICT in); void PadImageToBlockMultipleInPlace(Image3F* JXL_RESTRICT in);

View File

@ -230,7 +230,8 @@ Status SetColorEncodingFromJpegData(const jpeg::JPEGData& jpg,
return color_encoding->SetICC(std::move(icc_profile)); return color_encoding->SetICC(std::move(icc_profile));
} }
Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes) { Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes,
const CompressParams& cparams) {
jpeg_data.app_marker_type.resize(jpeg_data.app_data.size(), jpeg_data.app_marker_type.resize(jpeg_data.app_data.size(),
AppMarkerType::kUnknown); AppMarkerType::kUnknown);
JXL_RETURN_IF_ERROR(DetectIccProfile(jpeg_data)); JXL_RETURN_IF_ERROR(DetectIccProfile(jpeg_data));
@ -241,7 +242,9 @@ Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes) {
*bytes = std::move(writer).TakeBytes(); *bytes = std::move(writer).TakeBytes();
BrotliEncoderState* brotli_enc = BrotliEncoderState* brotli_enc =
BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
BrotliEncoderSetParameter(brotli_enc, BROTLI_PARAM_QUALITY, 11); int effort = cparams.brotli_effort;
if (effort < 0) effort = 11 - static_cast<int>(cparams.speed_tier);
BrotliEncoderSetParameter(brotli_enc, BROTLI_PARAM_QUALITY, effort);
size_t total_data = 0; size_t total_data = 0;
for (size_t i = 0; i < jpeg_data.app_data.size(); i++) { for (size_t i = 0; i < jpeg_data.app_data.size(); i++) {
if (jpeg_data.app_marker_type[i] != AppMarkerType::kUnknown) { if (jpeg_data.app_marker_type[i] != AppMarkerType::kUnknown) {

View File

@ -8,11 +8,13 @@
#include "lib/jxl/base/padded_bytes.h" #include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/codec_in_out.h" #include "lib/jxl/codec_in_out.h"
#include "lib/jxl/enc_params.h"
#include "lib/jxl/jpeg/jpeg_data.h" #include "lib/jxl/jpeg/jpeg_data.h"
namespace jxl { namespace jxl {
namespace jpeg { namespace jpeg {
Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes); Status EncodeJPEGData(JPEGData& jpeg_data, PaddedBytes* bytes,
const CompressParams& cparams);
Status SetColorEncodingFromJpegData(const jpeg::JPEGData& jpg, Status SetColorEncodingFromJpegData(const jpeg::JPEGData& jpg,
ColorEncoding* color_encoding); ColorEncoding* color_encoding);

View File

@ -95,6 +95,7 @@ bool ProcessSOF(const uint8_t* data, const size_t len, JpegReadMode mode,
int height = ReadUint16(data, pos); int height = ReadUint16(data, pos);
int width = ReadUint16(data, pos); int width = ReadUint16(data, pos);
int num_components = ReadUint8(data, pos); int num_components = ReadUint8(data, pos);
// 'jbrd' is hardcoded for 8bits:
JXL_JPEG_VERIFY_INPUT(precision, 8, 8, PRECISION); JXL_JPEG_VERIFY_INPUT(precision, 8, 8, PRECISION);
JXL_JPEG_VERIFY_INPUT(height, 1, kMaxDimPixels, HEIGHT); JXL_JPEG_VERIFY_INPUT(height, 1, kMaxDimPixels, HEIGHT);
JXL_JPEG_VERIFY_INPUT(width, 1, kMaxDimPixels, WIDTH); JXL_JPEG_VERIFY_INPUT(width, 1, kMaxDimPixels, WIDTH);

View File

@ -124,7 +124,7 @@ TEST(JxlTest, RoundtripMarker) {
TEST(JxlTest, RoundtripTinyFast) { TEST(JxlTest, RoundtripTinyFast) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(32, 32); io.ShrinkTo(32, 32);
@ -142,7 +142,7 @@ TEST(JxlTest, RoundtripTinyFast) {
TEST(JxlTest, RoundtripSmallD1) { TEST(JxlTest, RoundtripSmallD1) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CompressParams cparams; CompressParams cparams;
cparams.butteraugli_distance = 1.0; cparams.butteraugli_distance = 1.0;
DecompressParams dparams; DecompressParams dparams;
@ -183,7 +183,7 @@ TEST(JxlTest, RoundtripSmallD1) {
TEST(JxlTest, RoundtripOtherTransforms) { TEST(JxlTest, RoundtripOtherTransforms) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/64px/a2d1un_nkitzmiller_srgb8.png"); ReadTestData("third_party/wesaturate/64px/a2d1un_nkitzmiller_srgb8.png");
std::unique_ptr<CodecInOut> io = jxl::make_unique<CodecInOut>(); std::unique_ptr<CodecInOut> io = jxl::make_unique<CodecInOut>();
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), io.get(), pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), io.get(), pool));
@ -215,7 +215,7 @@ TEST(JxlTest, RoundtripOtherTransforms) {
TEST(JxlTest, RoundtripResample2) { TEST(JxlTest, RoundtripResample2) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize(), io.ysize()); io.ShrinkTo(io.xsize(), io.ysize());
@ -232,7 +232,7 @@ TEST(JxlTest, RoundtripResample2) {
TEST(JxlTest, RoundtripResample2MT) { TEST(JxlTest, RoundtripResample2MT) {
ThreadPoolInternal pool(4); ThreadPoolInternal pool(4);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
// image has to be large enough to have multiple groups after downsampling // image has to be large enough to have multiple groups after downsampling
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -254,7 +254,7 @@ TEST(JxlTest, RoundtripOutOfOrderProcessing) {
FakeParallelRunner fake_pool(/*order_seed=*/123, /*num_threads=*/8); FakeParallelRunner fake_pool(/*order_seed=*/123, /*num_threads=*/8);
ThreadPool pool(&JxlFakeParallelRunner, &fake_pool); ThreadPool pool(&JxlFakeParallelRunner, &fake_pool);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
// Image size is selected so that the block border needed is larger than the // Image size is selected so that the block border needed is larger than the
@ -277,7 +277,7 @@ TEST(JxlTest, RoundtripOutOfOrderProcessingBorder) {
FakeParallelRunner fake_pool(/*order_seed=*/47, /*num_threads=*/8); FakeParallelRunner fake_pool(/*order_seed=*/47, /*num_threads=*/8);
ThreadPool pool(&JxlFakeParallelRunner, &fake_pool); ThreadPool pool(&JxlFakeParallelRunner, &fake_pool);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
// Image size is selected so that the block border needed is larger than the // Image size is selected so that the block border needed is larger than the
@ -300,7 +300,7 @@ TEST(JxlTest, RoundtripOutOfOrderProcessingBorder) {
TEST(JxlTest, RoundtripResample4) { TEST(JxlTest, RoundtripResample4) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize(), io.ysize()); io.ShrinkTo(io.xsize(), io.ysize());
@ -317,7 +317,7 @@ TEST(JxlTest, RoundtripResample4) {
TEST(JxlTest, RoundtripResample8) { TEST(JxlTest, RoundtripResample8) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize(), io.ysize()); io.ShrinkTo(io.xsize(), io.ysize());
@ -334,7 +334,7 @@ TEST(JxlTest, RoundtripResample8) {
TEST(JxlTest, RoundtripUnalignedD2) { TEST(JxlTest, RoundtripUnalignedD2) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 12, io.ysize() / 7); io.ShrinkTo(io.xsize() / 12, io.ysize() / 7);
@ -355,7 +355,7 @@ TEST(JxlTest, RoundtripUnalignedD2) {
TEST(JxlTest, RoundtripMultiGroupNL) { TEST(JxlTest, RoundtripMultiGroupNL) {
ThreadPoolInternal pool(4); ThreadPoolInternal pool(4);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
io.ShrinkTo(600, 1024); // partial X, full Y group io.ShrinkTo(600, 1024); // partial X, full Y group
@ -384,7 +384,7 @@ TEST(JxlTest, RoundtripMultiGroupNL) {
TEST(JxlTest, RoundtripMultiGroup) { TEST(JxlTest, RoundtripMultiGroup) {
ThreadPoolInternal pool(4); ThreadPoolInternal pool(4);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
io.ShrinkTo(600, 1024); io.ShrinkTo(600, 1024);
@ -410,7 +410,7 @@ TEST(JxlTest, RoundtripMultiGroup) {
TEST(JxlTest, RoundtripLargeFast) { TEST(JxlTest, RoundtripLargeFast) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -425,7 +425,7 @@ TEST(JxlTest, RoundtripLargeFast) {
TEST(JxlTest, RoundtripDotsForceEpf) { TEST(JxlTest, RoundtripDotsForceEpf) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); ReadTestData("third_party/wesaturate/500px/cvo9xd_keong_macan_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -445,7 +445,7 @@ TEST(JxlTest, RoundtripDotsForceEpf) {
TEST(JxlTest, RoundtripD2Consistent) { TEST(JxlTest, RoundtripD2Consistent) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -478,7 +478,7 @@ TEST(JxlTest, RoundtripD2Consistent) {
TEST(JxlTest, RoundtripLargeConsistent) { TEST(JxlTest, RoundtripLargeConsistent) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -508,7 +508,7 @@ TEST(JxlTest, RoundtripLargeConsistent) {
TEST(JxlTest, RoundtripSmallNL) { TEST(JxlTest, RoundtripSmallNL) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 8, io.ysize() / 8); io.ShrinkTo(io.xsize() / 8, io.ysize() / 8);
@ -529,7 +529,7 @@ TEST(JxlTest, RoundtripSmallNL) {
TEST(JxlTest, RoundtripNoGaborishNoAR) { TEST(JxlTest, RoundtripNoGaborishNoAR) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@ -549,7 +549,7 @@ TEST(JxlTest, RoundtripNoGaborishNoAR) {
TEST(JxlTest, RoundtripSmallNoGaborish) { TEST(JxlTest, RoundtripSmallNoGaborish) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 8, io.ysize() / 8); io.ShrinkTo(io.xsize() / 8, io.ysize() / 8);
@ -595,7 +595,7 @@ TEST(JxlTest, RoundtripSmallPatchesAlpha) {
EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 2000u); EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 2000u);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool), /*distmap=*/nullptr, pool),
IsSlightlyBelow(0.2f)); IsSlightlyBelow(0.04f));
} }
TEST(JxlTest, RoundtripSmallPatches) { TEST(JxlTest, RoundtripSmallPatches) {
@ -623,7 +623,7 @@ TEST(JxlTest, RoundtripSmallPatches) {
EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 2000u); EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2), 2000u);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool), /*distmap=*/nullptr, pool),
IsSlightlyBelow(0.2f)); IsSlightlyBelow(0.04f));
} }
// Test header encoding of original bits per sample // Test header encoding of original bits per sample
@ -700,8 +700,8 @@ TEST(JxlTest, RoundtripImageBundleOriginalBits) {
TEST(JxlTest, RoundtripGrayscale) { TEST(JxlTest, RoundtripGrayscale) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); "third_party/wesaturate/500px/cvo9xd_keong_macan_grayscale.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
ASSERT_NE(io.xsize(), 0u); ASSERT_NE(io.xsize(), 0u);
@ -756,8 +756,8 @@ TEST(JxlTest, RoundtripGrayscale) {
TEST(JxlTest, RoundtripAlpha) { TEST(JxlTest, RoundtripAlpha) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/tmshre_riaphotographs_alpha.png"); "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@ -786,13 +786,13 @@ TEST(JxlTest, RoundtripAlpha) {
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool), /*distmap=*/nullptr, pool),
IsSlightlyBelow(1.4)); IsSlightlyBelow(1.2));
} }
TEST(JxlTest, RoundtripAlphaPremultiplied) { TEST(JxlTest, RoundtripAlphaPremultiplied) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/tmshre_riaphotographs_alpha.png"); "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png");
CodecInOut io, io_nopremul; CodecInOut io, io_nopremul;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io_nopremul, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io_nopremul, pool));
@ -821,18 +821,18 @@ TEST(JxlTest, RoundtripAlphaPremultiplied) {
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool), /*distmap=*/nullptr, pool),
IsSlightlyBelow(1.4)); IsSlightlyBelow(1.2));
io2.Main().UnpremultiplyAlpha(); io2.Main().UnpremultiplyAlpha();
EXPECT_THAT( EXPECT_THAT(
ButteraugliDistance(io_nopremul, io2, cparams.ba_params, GetJxlCms(), ButteraugliDistance(io_nopremul, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool), /*distmap=*/nullptr, pool),
IsSlightlyBelow(1.8)); IsSlightlyBelow(1.35));
} }
TEST(JxlTest, RoundtripAlphaResampling) { TEST(JxlTest, RoundtripAlphaResampling) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/tmshre_riaphotographs_alpha.png"); "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@ -864,8 +864,8 @@ TEST(JxlTest, RoundtripAlphaResampling) {
TEST(JxlTest, RoundtripAlphaResamplingOnlyAlpha) { TEST(JxlTest, RoundtripAlphaResamplingOnlyAlpha) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/tmshre_riaphotographs_alpha.png"); "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@ -896,8 +896,8 @@ TEST(JxlTest, RoundtripAlphaResamplingOnlyAlpha) {
TEST(JxlTest, RoundtripAlphaNonMultipleOf8) { TEST(JxlTest, RoundtripAlphaNonMultipleOf8) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/tmshre_riaphotographs_alpha.png"); "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@ -922,7 +922,7 @@ TEST(JxlTest, RoundtripAlphaNonMultipleOf8) {
CodecInOut io2; CodecInOut io2;
EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, pool)); EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, pool));
EXPECT_LE(compressed.size(), 200u); EXPECT_LE(compressed.size(), 180u);
// TODO(robryk): Fix the following line in presence of different alpha_bits in // TODO(robryk): Fix the following line in presence of different alpha_bits in
// the two contexts. // the two contexts.
@ -930,7 +930,7 @@ TEST(JxlTest, RoundtripAlphaNonMultipleOf8) {
// TODO(robryk): Fix the distance estimate used in the encoder. // TODO(robryk): Fix the distance estimate used in the encoder.
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool), /*distmap=*/nullptr, pool),
IsSlightlyBelow(0.8)); IsSlightlyBelow(0.9));
} }
TEST(JxlTest, RoundtripAlpha16) { TEST(JxlTest, RoundtripAlpha16) {
@ -965,8 +965,7 @@ TEST(JxlTest, RoundtripAlpha16) {
CompressParams cparams; CompressParams cparams;
cparams.butteraugli_distance = 0.5; cparams.butteraugli_distance = 0.5;
// Prevent the test to be too slow, does not affect alpha cparams.speed_tier = SpeedTier::kWombat;
cparams.speed_tier = SpeedTier::kSquirrel;
DecompressParams dparams; DecompressParams dparams;
io.metadata.m.SetUintSamples(16); io.metadata.m.SetUintSamples(16);
@ -978,8 +977,9 @@ TEST(JxlTest, RoundtripAlpha16) {
aux_out, &pool)); aux_out, &pool));
CodecInOut io2; CodecInOut io2;
EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, &pool)); EXPECT_TRUE(DecodeFile(dparams, compressed, &io2, &pool));
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
EXPECT_TRUE(SamePixels(*io.Main().alpha(), *io2.Main().alpha())); /*distmap=*/nullptr, &pool),
IsSlightlyBelow(0.8));
} }
namespace { namespace {
@ -987,7 +987,7 @@ CompressParams CParamsForLossless() {
CompressParams cparams; CompressParams cparams;
cparams.modular_mode = true; cparams.modular_mode = true;
cparams.color_transform = jxl::ColorTransform::kNone; cparams.color_transform = jxl::ColorTransform::kNone;
cparams.quality_pair = {100, 100}; cparams.butteraugli_distance = 0.f;
cparams.options.predictor = {Predictor::Weighted}; cparams.options.predictor = {Predictor::Weighted};
return cparams; return cparams;
} }
@ -995,8 +995,8 @@ CompressParams CParamsForLossless() {
TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8)) { TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/tmshre_riaphotographs_srgb8.png"); "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -1023,8 +1023,8 @@ TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8)) {
TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderFastPathWP)) { TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderFastPathWP)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/tmshre_riaphotographs_srgb8.png"); "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -1040,8 +1040,8 @@ TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderFastPathWP)) {
TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderFastPathGradient)) { TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderFastPathGradient)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/tmshre_riaphotographs_srgb8.png"); "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -1058,8 +1058,8 @@ TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderFastPathGradient)) {
TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderVeryFastPathGradient)) { TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderVeryFastPathGradient)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/tmshre_riaphotographs_srgb8.png"); "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -1079,8 +1079,8 @@ TEST(JxlTest, JXL_SLOW_TEST(RoundtripLosslessNoEncoderVeryFastPathGradient)) {
TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8Falcon)) { TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8Falcon)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/tmshre_riaphotographs_srgb8.png"); "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -1096,8 +1096,8 @@ TEST(JxlTest, JXL_SLOW_TEST(RoundtripLossless8Falcon)) {
TEST(JxlTest, RoundtripLossless8Alpha) { TEST(JxlTest, RoundtripLossless8Alpha) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/tmshre_riaphotographs_alpha.png"); "third_party/wesaturate/500px/tmshre_riaphotographs_alpha.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
EXPECT_EQ(8u, io.metadata.m.GetAlphaBits()); EXPECT_EQ(8u, io.metadata.m.GetAlphaBits());
@ -1221,11 +1221,11 @@ TEST(JxlTest, RoundtripLossless16AlphaNotMisdetectedAs8Bit) {
TEST(JxlTest, RoundtripYCbCr420) { TEST(JxlTest, RoundtripYCbCr420) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
const PaddedBytes yuv420 = const PaddedBytes yuv420 = ReadTestData(
ReadTestData("imagecompression.info/flower_foveon.png.ffmpeg.y4m"); "third_party/imagecompression.info/flower_foveon.png.ffmpeg.y4m");
CodecInOut io2; CodecInOut io2;
ASSERT_TRUE(test::DecodeImageY4M(Span<const uint8_t>(yuv420), &io2)); ASSERT_TRUE(test::DecodeImageY4M(Span<const uint8_t>(yuv420), &io2));
@ -1251,7 +1251,7 @@ TEST(JxlTest, RoundtripYCbCr420) {
TEST(JxlTest, RoundtripDots) { TEST(JxlTest, RoundtripDots) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); ReadTestData("third_party/wesaturate/500px/cvo9xd_keong_macan_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@ -1284,7 +1284,7 @@ TEST(JxlTest, RoundtripDots) {
TEST(JxlTest, RoundtripNoise) { TEST(JxlTest, RoundtripNoise) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/cvo9xd_keong_macan_srgb8.png"); ReadTestData("third_party/wesaturate/500px/cvo9xd_keong_macan_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@ -1315,8 +1315,8 @@ TEST(JxlTest, RoundtripNoise) {
TEST(JxlTest, RoundtripLossless8Gray) { TEST(JxlTest, RoundtripLossless8Gray) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); "third_party/wesaturate/500px/cvo9xd_keong_macan_grayscale.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
@ -1448,7 +1448,7 @@ size_t RoundtripJpeg(const PaddedBytes& jpeg_in, ThreadPool* pool) {
enc_container.codestream = std::move(codestream); enc_container.codestream = std::move(codestream);
jpeg::JPEGData data_in = *io.Main().jpeg_data; jpeg::JPEGData data_in = *io.Main().jpeg_data;
jxl::PaddedBytes jpeg_data; jxl::PaddedBytes jpeg_data;
EXPECT_TRUE(EncodeJPEGData(data_in, &jpeg_data)); EXPECT_TRUE(EncodeJPEGData(data_in, &jpeg_data, cparams));
enc_container.jpeg_reconstruction = jpeg_data.data(); enc_container.jpeg_reconstruction = jpeg_data.data();
enc_container.jpeg_reconstruction_size = jpeg_data.size(); enc_container.jpeg_reconstruction_size = jpeg_data.size();
EXPECT_TRUE(EncodeJpegXlContainerOneShot(enc_container, &compressed)); EXPECT_TRUE(EncodeJpegXlContainerOneShot(enc_container, &compressed));
@ -1474,8 +1474,8 @@ size_t RoundtripJpeg(const PaddedBytes& jpeg_in, ThreadPool* pool) {
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression444)) { TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression444)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("imagecompression.info/flower_foveon.png.im_q85_444.jpg"); "third_party/imagecompression.info/flower_foveon.png.im_q85_444.jpg");
// JPEG size is 326'916 bytes. // JPEG size is 326'916 bytes.
EXPECT_LE(RoundtripJpeg(orig, &pool), 256000u); EXPECT_LE(RoundtripJpeg(orig, &pool), 256000u);
} }
@ -1484,8 +1484,8 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression444)) {
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels)) { TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("imagecompression.info/flower_foveon.png.im_q85_444.jpg"); "third_party/imagecompression.info/flower_foveon.png.im_q85_444.jpg");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io)); ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io));
@ -1508,8 +1508,8 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels)) {
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420)) { TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("imagecompression.info/flower_foveon.png.im_q85_420.jpg"); "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io)); ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io));
@ -1531,8 +1531,8 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420)) {
TEST(JxlTest, TEST(JxlTest,
JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420EarlyFlush)) { JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420EarlyFlush)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("imagecompression.info/flower_foveon.png.im_q85_420.jpg"); "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io)); ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io));
@ -1555,8 +1555,8 @@ TEST(JxlTest,
TEST(JxlTest, TEST(JxlTest,
JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420Mul16)) { JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels420Mul16)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("imagecompression.info/flower_foveon_cropped.jpg"); "third_party/imagecompression.info/flower_foveon_cropped.jpg");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io)); ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io));
@ -1579,7 +1579,8 @@ TEST(JxlTest,
JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels_asymmetric)) { JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionToPixels_asymmetric)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = ReadTestData( const PaddedBytes orig = ReadTestData(
"imagecompression.info/flower_foveon.png.im_q85_asymmetric.jpg"); "third_party/imagecompression.info/"
"flower_foveon.png.im_q85_asymmetric.jpg");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io)); ASSERT_TRUE(jpeg::DecodeImageJPG(Span<const uint8_t>(orig), &io));
@ -1602,16 +1603,16 @@ TEST(JxlTest,
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionGray)) { TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompressionGray)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("imagecompression.info/flower_foveon.png.im_q85_gray.jpg"); "third_party/imagecompression.info/flower_foveon.png.im_q85_gray.jpg");
// JPEG size is 167'025 bytes. // JPEG size is 167'025 bytes.
EXPECT_LE(RoundtripJpeg(orig, &pool), 140000u); EXPECT_LE(RoundtripJpeg(orig, &pool), 140000u);
} }
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression420)) { TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression420)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("imagecompression.info/flower_foveon.png.im_q85_420.jpg"); "third_party/imagecompression.info/flower_foveon.png.im_q85_420.jpg");
// JPEG size is 226'018 bytes. // JPEG size is 226'018 bytes.
EXPECT_LE(RoundtripJpeg(orig, &pool), 181050u); EXPECT_LE(RoundtripJpeg(orig, &pool), 181050u);
} }
@ -1620,7 +1621,8 @@ TEST(JxlTest,
JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression_luma_subsample)) { JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression_luma_subsample)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = ReadTestData( const PaddedBytes orig = ReadTestData(
"imagecompression.info/flower_foveon.png.im_q85_luma_subsample.jpg"); "third_party/imagecompression.info/"
"flower_foveon.png.im_q85_luma_subsample.jpg");
// JPEG size is 216'069 bytes. // JPEG size is 216'069 bytes.
EXPECT_LE(RoundtripJpeg(orig, &pool), 181000u); EXPECT_LE(RoundtripJpeg(orig, &pool), 181000u);
} }
@ -1629,23 +1631,23 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression444_12)) {
// 444 JPEG that has an interesting sampling-factor (1x2, 1x2, 1x2). // 444 JPEG that has an interesting sampling-factor (1x2, 1x2, 1x2).
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = ReadTestData( const PaddedBytes orig = ReadTestData(
"imagecompression.info/flower_foveon.png.im_q85_444_1x2.jpg"); "third_party/imagecompression.info/flower_foveon.png.im_q85_444_1x2.jpg");
// JPEG size is 329'942 bytes. // JPEG size is 329'942 bytes.
EXPECT_LE(RoundtripJpeg(orig, &pool), 256000u); EXPECT_LE(RoundtripJpeg(orig, &pool), 256000u);
} }
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression422)) { TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression422)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("imagecompression.info/flower_foveon.png.im_q85_422.jpg"); "third_party/imagecompression.info/flower_foveon.png.im_q85_422.jpg");
// JPEG size is 265'590 bytes. // JPEG size is 265'590 bytes.
EXPECT_LE(RoundtripJpeg(orig, &pool), 209000u); EXPECT_LE(RoundtripJpeg(orig, &pool), 209000u);
} }
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression440)) { TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression440)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("imagecompression.info/flower_foveon.png.im_q85_440.jpg"); "third_party/imagecompression.info/flower_foveon.png.im_q85_440.jpg");
// JPEG size is 262'249 bytes. // JPEG size is 262'249 bytes.
EXPECT_LE(RoundtripJpeg(orig, &pool), 209000u); EXPECT_LE(RoundtripJpeg(orig, &pool), 209000u);
} }
@ -1655,7 +1657,8 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression_asymmetric)) {
// the other. // the other.
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = ReadTestData( const PaddedBytes orig = ReadTestData(
"imagecompression.info/flower_foveon.png.im_q85_asymmetric.jpg"); "third_party/imagecompression.info/"
"flower_foveon.png.im_q85_asymmetric.jpg");
// JPEG size is 262'249 bytes. // JPEG size is 262'249 bytes.
EXPECT_LE(RoundtripJpeg(orig, &pool), 209000u); EXPECT_LE(RoundtripJpeg(orig, &pool), 209000u);
} }
@ -1663,14 +1666,15 @@ TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression_asymmetric)) {
TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression420Progr)) { TEST(JxlTest, JXL_TRANSCODE_JPEG_TEST(RoundtripJpegRecompression420Progr)) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = ReadTestData( const PaddedBytes orig = ReadTestData(
"imagecompression.info/flower_foveon.png.im_q85_420_progr.jpg"); "third_party/imagecompression.info/"
"flower_foveon.png.im_q85_420_progr.jpg");
EXPECT_LE(RoundtripJpeg(orig, &pool), 181000u); EXPECT_LE(RoundtripJpeg(orig, &pool), 181000u);
} }
TEST(JxlTest, RoundtripProgressive) { TEST(JxlTest, RoundtripProgressive) {
ThreadPoolInternal pool(4); ThreadPoolInternal pool(4);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
io.ShrinkTo(600, 1024); io.ShrinkTo(600, 1024);
@ -1686,7 +1690,7 @@ TEST(JxlTest, RoundtripProgressive) {
EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 40000u); EXPECT_LE(Roundtrip(&io, cparams, dparams, &pool, &io2), 40000u);
EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(), EXPECT_THAT(ButteraugliDistance(io, io2, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, &pool), /*distmap=*/nullptr, &pool),
IsSlightlyBelow(2.5f)); IsSlightlyBelow(1.1f));
} }
} // namespace } // namespace

View File

@ -304,7 +304,7 @@ class MATreeLookup {
int64_t offset; int64_t offset;
int32_t multiplier; int32_t multiplier;
}; };
LookupResult Lookup(const Properties &properties) const { JXL_INLINE LookupResult Lookup(const Properties &properties) const {
uint32_t pos = 0; uint32_t pos = 0;
while (true) { while (true) {
const FlatDecisionNode &node = nodes_[pos]; const FlatDecisionNode &node = nodes_[pos];
@ -416,6 +416,7 @@ enum PredictorMode {
kUseWP = 2, kUseWP = 2,
kForceComputeProperties = 4, kForceComputeProperties = 4,
kAllPredictions = 8, kAllPredictions = 8,
kNoEdgeCases = 16
}; };
JXL_INLINE pixel_type_w PredictOne(Predictor p, pixel_type_w left, JXL_INLINE pixel_type_w PredictOne(Predictor p, pixel_type_w left,
@ -461,7 +462,7 @@ JXL_INLINE pixel_type_w PredictOne(Predictor p, pixel_type_w left,
} }
template <int mode> template <int mode>
inline PredictionResult Predict( JXL_INLINE PredictionResult Predict(
Properties *p, size_t w, const pixel_type *JXL_RESTRICT pp, Properties *p, size_t w, const pixel_type *JXL_RESTRICT pp,
const intptr_t onerow, const size_t x, const size_t y, Predictor predictor, const intptr_t onerow, const size_t x, const size_t y, Predictor predictor,
const MATreeLookup *lookup, const Channel *references, const MATreeLookup *lookup, const Channel *references,
@ -470,13 +471,15 @@ inline PredictionResult Predict(
size_t offset = 3; size_t offset = 3;
constexpr bool compute_properties = constexpr bool compute_properties =
mode & kUseTree || mode & kForceComputeProperties; mode & kUseTree || mode & kForceComputeProperties;
pixel_type_w left = (x ? pp[-1] : (y ? pp[-onerow] : 0)); constexpr bool nec = mode & kNoEdgeCases;
pixel_type_w top = (y ? pp[-onerow] : left); pixel_type_w left = (nec || x ? pp[-1] : (y ? pp[-onerow] : 0));
pixel_type_w topleft = (x && y ? pp[-1 - onerow] : left); pixel_type_w top = (nec || y ? pp[-onerow] : left);
pixel_type_w topright = (x + 1 < w && y ? pp[1 - onerow] : top); pixel_type_w topleft = (nec || (x && y) ? pp[-1 - onerow] : left);
pixel_type_w leftleft = (x > 1 ? pp[-2] : left); pixel_type_w topright = (nec || (x + 1 < w && y) ? pp[1 - onerow] : top);
pixel_type_w toptop = (y > 1 ? pp[-onerow - onerow] : top); pixel_type_w leftleft = (nec || x > 1 ? pp[-2] : left);
pixel_type_w toprightright = (x + 2 < w && y ? pp[2 - onerow] : topright); pixel_type_w toptop = (nec || y > 1 ? pp[-onerow - onerow] : top);
pixel_type_w toprightright =
(nec || (x + 2 < w && y) ? pp[2 - onerow] : topright);
if (compute_properties) { if (compute_properties) {
// location // location
@ -506,7 +509,7 @@ inline PredictionResult Predict(
wp_pred = wp_state->Predict<compute_properties>( wp_pred = wp_state->Predict<compute_properties>(
x, y, w, top, left, topright, topleft, toptop, p, offset); x, y, w, top, left, topright, topleft, toptop, p, offset);
} }
if (compute_properties) { if (!nec && compute_properties) {
offset += weighted::kNumProperties; offset += weighted::kNumProperties;
// Extra properties. // Extra properties.
const pixel_type *JXL_RESTRICT rp = references->Row(x); const pixel_type *JXL_RESTRICT rp = references->Row(x);
@ -565,6 +568,15 @@ inline PredictionResult PredictTreeNoWP(Properties *p, size_t w,
p, w, pp, onerow, x, y, Predictor::Zero, &tree_lookup, &references, p, w, pp, onerow, x, y, Predictor::Zero, &tree_lookup, &references,
/*wp_state=*/nullptr, /*predictions=*/nullptr); /*wp_state=*/nullptr, /*predictions=*/nullptr);
} }
// Only use for y > 1, x > 1, x < w-2, and empty references
JXL_INLINE PredictionResult
PredictTreeNoWPNEC(Properties *p, size_t w, const pixel_type *JXL_RESTRICT pp,
const intptr_t onerow, const int x, const int y,
const MATreeLookup &tree_lookup, const Channel &references) {
return detail::Predict<detail::kUseTree | detail::kNoEdgeCases>(
p, w, pp, onerow, x, y, Predictor::Zero, &tree_lookup, &references,
/*wp_state=*/nullptr, /*predictions=*/nullptr);
}
inline PredictionResult PredictTreeWP(Properties *p, size_t w, inline PredictionResult PredictTreeWP(Properties *p, size_t w,
const pixel_type *JXL_RESTRICT pp, const pixel_type *JXL_RESTRICT pp,

View File

@ -186,14 +186,23 @@ Status DecodeModularChannelMAANS(BitReader *br, ANSSymbolReader *reader,
pixel_type *JXL_RESTRICT r = channel.Row(y); pixel_type *JXL_RESTRICT r = channel.Row(y);
std::fill(r, r + channel.w, v); std::fill(r, r + channel.w, v);
} }
} else { } else {
JXL_DEBUG_V(8, "Fast track."); JXL_DEBUG_V(8, "Fast track.");
for (size_t y = 0; y < channel.h; y++) { if (multiplier == 1 && offset == 0) {
pixel_type *JXL_RESTRICT r = channel.Row(y); for (size_t y = 0; y < channel.h; y++) {
for (size_t x = 0; x < channel.w; x++) { pixel_type *JXL_RESTRICT r = channel.Row(y);
uint32_t v = reader->ReadHybridUintClustered(ctx_id, br); for (size_t x = 0; x < channel.w; x++) {
r[x] = make_pixel(v, multiplier, offset); uint32_t v = reader->ReadHybridUintClustered(ctx_id, br);
r[x] = UnpackSigned(v);
}
}
} else {
for (size_t y = 0; y < channel.h; y++) {
pixel_type *JXL_RESTRICT r = channel.Row(y);
for (size_t x = 0; x < channel.w; x++) {
uint32_t v = reader->ReadHybridUintClustered(ctx_id, br);
r[x] = make_pixel(v, multiplier, offset);
}
} }
} }
} }
@ -355,12 +364,36 @@ Status DecodeModularChannelMAANS(BitReader *br, ANSSymbolReader *reader,
pixel_type *JXL_RESTRICT p = channel.Row(y); pixel_type *JXL_RESTRICT p = channel.Row(y);
PrecomputeReferences(channel, y, *image, chan, &references); PrecomputeReferences(channel, y, *image, chan, &references);
InitPropsRow(&properties, static_props, y); InitPropsRow(&properties, static_props, y);
for (size_t x = 0; x < channel.w; x++) { if (y > 1 && channel.w > 8 && references.w == 0) {
PredictionResult res = for (size_t x = 0; x < 2; x++) {
PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y, PredictionResult res =
tree_lookup, references); PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
uint64_t v = reader->ReadHybridUintClustered(res.context, br); tree_lookup, references);
p[x] = make_pixel(v, res.multiplier, res.guess); uint64_t v = reader->ReadHybridUintClustered(res.context, br);
p[x] = make_pixel(v, res.multiplier, res.guess);
}
for (size_t x = 2; x < channel.w - 2; x++) {
PredictionResult res =
PredictTreeNoWPNEC(&properties, channel.w, p + x, onerow, x, y,
tree_lookup, references);
uint64_t v = reader->ReadHybridUintClustered(res.context, br);
p[x] = make_pixel(v, res.multiplier, res.guess);
}
for (size_t x = channel.w - 2; x < channel.w; x++) {
PredictionResult res =
PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
tree_lookup, references);
uint64_t v = reader->ReadHybridUintClustered(res.context, br);
p[x] = make_pixel(v, res.multiplier, res.guess);
}
} else {
for (size_t x = 0; x < channel.w; x++) {
PredictionResult res =
PredictTreeNoWP(&properties, channel.w, p + x, onerow, x, y,
tree_lookup, references);
uint64_t v = reader->ReadHybridUintClustered(res.context, br);
p[x] = make_pixel(v, res.multiplier, res.guess);
}
} }
} }
} else { } else {

View File

@ -550,18 +550,15 @@ Status FwdPalette(Image &input, uint32_t begin_c, uint32_t end_c,
bool lossy, Predictor &predictor, bool lossy, Predictor &predictor,
const weighted::Header &wp_header) { const weighted::Header &wp_header) {
PaletteIterationData palette_iteration_data; PaletteIterationData palette_iteration_data;
uint32_t nb = end_c - begin_c + 1;
uint32_t nb_colors_orig = nb_colors; uint32_t nb_colors_orig = nb_colors;
uint32_t nb_deltas_orig = nb_deltas; uint32_t nb_deltas_orig = nb_deltas;
// if no channel palette special case // preprocessing pass in case of lossy palette
if ((lossy || nb != 1) && input.bitdepth >= 8) { if (lossy && input.bitdepth >= 8) {
JXL_RETURN_IF_ERROR(FwdPaletteIteration( JXL_RETURN_IF_ERROR(FwdPaletteIteration(
input, begin_c, end_c, nb_colors, nb_deltas, ordered, lossy, predictor, input, begin_c, end_c, nb_colors_orig, nb_deltas_orig, ordered, lossy,
wp_header, palette_iteration_data)); predictor, wp_header, palette_iteration_data));
} }
palette_iteration_data.final_run = true; palette_iteration_data.final_run = true;
nb_colors = nb_colors_orig;
nb_deltas = nb_deltas_orig;
return FwdPaletteIteration(input, begin_c, end_c, nb_colors, nb_deltas, return FwdPaletteIteration(input, begin_c, end_c, nb_colors, nb_deltas,
ordered, lossy, predictor, wp_header, ordered, lossy, predictor, wp_header,
palette_iteration_data); palette_iteration_data);

View File

@ -12,13 +12,80 @@
#include "lib/jxl/common.h" #include "lib/jxl/common.h"
#include "lib/jxl/modular/modular_image.h" #include "lib/jxl/modular/modular_image.h"
#include "lib/jxl/modular/transform/transform.h" #include "lib/jxl/modular/transform/transform.h"
#undef HWY_TARGET_INCLUDE
#define HWY_TARGET_INCLUDE "lib/jxl/modular/transform/squeeze.cc"
#include <hwy/foreach_target.h>
#include <hwy/highway.h>
#include "lib/jxl/simd_util-inl.h"
HWY_BEFORE_NAMESPACE();
namespace jxl { namespace jxl {
namespace HWY_NAMESPACE {
using hwy::HWY_NAMESPACE::RebindToUnsigned;
using hwy::HWY_NAMESPACE::ShiftLeft;
using hwy::HWY_NAMESPACE::ShiftRight;
#if HWY_TARGET != HWY_SCALAR
JXL_INLINE void FastUnsqueeze(const pixel_type *JXL_RESTRICT p_residual,
const pixel_type *JXL_RESTRICT p_avg,
const pixel_type *JXL_RESTRICT p_navg,
const pixel_type *p_pout,
pixel_type *JXL_RESTRICT p_out,
pixel_type *p_nout) {
const HWY_CAPPED(pixel_type, 8) d;
const RebindToUnsigned<decltype(d)> du;
const size_t N = Lanes(d);
auto onethird = Set(d, 0x55555556);
for (size_t x = 0; x < 8; x += N) {
auto avg = Load(d, p_avg + x);
auto next_avg = Load(d, p_navg + x);
auto top = Load(d, p_pout + x);
// Equivalent to SmoothTendency(top,avg,next_avg), but without branches
auto Ba = top - avg;
auto an = avg - next_avg;
auto nonmono = Ba ^ an;
auto absBa = Abs(Ba);
auto absan = Abs(an);
auto absBn = Abs(top - next_avg);
// Compute a3 = absBa / 3
auto a3e = BitCast(d, ShiftRight<32>(MulEven(absBa, onethird)));
auto a3oi = MulEven(Reverse(d, absBa), onethird);
auto a3o = BitCast(
d, Reverse(hwy::HWY_NAMESPACE::Repartition<pixel_type_w, decltype(d)>(),
a3oi));
auto a3 = OddEven(a3o, a3e);
a3 += absBn + Set(d, 2);
auto absdiff = ShiftRight<2>(a3);
auto skipdiff = Ba != Zero(d);
skipdiff = And(skipdiff, an != Zero(d));
skipdiff = And(skipdiff, nonmono < Zero(d));
auto absBa2 = ShiftLeft<1>(absBa) + (absdiff & Set(d, 1));
absdiff =
IfThenElse(absdiff > absBa2, ShiftLeft<1>(absBa) + Set(d, 1), absdiff);
auto absan2 = ShiftLeft<1>(absan);
absdiff =
IfThenElse(absdiff + (absdiff & Set(d, 1)) > absan2, absan2, absdiff);
auto diff1 = IfThenElse(top < next_avg, Neg(absdiff), absdiff);
auto tendency = IfThenZeroElse(skipdiff, diff1);
auto diff_minus_tendency = Load(d, p_residual + x);
auto diff = diff_minus_tendency + tendency;
auto out = avg + ShiftRight<1>(
diff + BitCast(d, ShiftRight<31>(BitCast(du, diff))));
Store(out, d, p_out + x);
Store(out - diff, d, p_nout + x);
}
}
#endif
Status InvHSqueeze(Image &input, uint32_t c, uint32_t rc, ThreadPool *pool) { Status InvHSqueeze(Image &input, uint32_t c, uint32_t rc, ThreadPool *pool) {
JXL_ASSERT(c < input.channel.size()); JXL_ASSERT(c < input.channel.size());
JXL_ASSERT(rc < input.channel.size()); JXL_ASSERT(rc < input.channel.size());
const Channel &chin = input.channel[c]; Channel &chin = input.channel[c];
const Channel &chin_residual = input.channel[rc]; const Channel &chin_residual = input.channel[rc];
// These must be valid since we ran MetaApply already. // These must be valid since we ran MetaApply already.
JXL_ASSERT(chin.w == DivCeil(chin.w + chin_residual.w, 2)); JXL_ASSERT(chin.w == DivCeil(chin.w + chin_residual.w, 2));
@ -42,42 +109,84 @@ Status InvHSqueeze(Image &input, uint32_t c, uint32_t rc, ThreadPool *pool) {
input.channel[c] = std::move(chout); input.channel[c] = std::move(chout);
return true; return true;
} }
auto unsqueeze_row = [&](size_t y, size_t x0) {
const pixel_type *JXL_RESTRICT p_residual = chin_residual.Row(y);
const pixel_type *JXL_RESTRICT p_avg = chin.Row(y);
pixel_type *JXL_RESTRICT p_out = chout.Row(y);
for (size_t x = x0; x < chin_residual.w; x++) {
pixel_type diff_minus_tendency = p_residual[x];
pixel_type avg = p_avg[x];
pixel_type next_avg = (x + 1 < chin.w ? p_avg[x + 1] : avg);
pixel_type left = (x ? p_out[(x << 1) - 1] : avg);
pixel_type tendency = SmoothTendency(left, avg, next_avg);
pixel_type diff = diff_minus_tendency + tendency;
pixel_type A = avg + (diff / 2);
p_out[(x << 1)] = A;
pixel_type B = A - diff;
p_out[(x << 1) + 1] = B;
}
if (chout.w & 1) p_out[chout.w - 1] = p_avg[chin.w - 1];
};
JXL_RETURN_IF_ERROR(RunOnPool( // somewhat complicated trickery just to be able to SIMD this.
pool, 0, chin.h, ThreadPool::NoInit, // Horizontal unsqueeze has horizontal data dependencies, so we do
[&](const uint32_t task, size_t /* thread */) { // 8 rows at a time and treat it as a vertical unsqueeze of a
const size_t y = task; // transposed 8x8 block (or 9x8 for one input).
const pixel_type *JXL_RESTRICT p_residual = chin_residual.Row(y); static constexpr const size_t kRowsPerThread = 8;
const pixel_type *JXL_RESTRICT p_avg = chin.Row(y); const auto unsqueeze_span = [&](const uint32_t task, size_t /* thread */) {
pixel_type *JXL_RESTRICT p_out = chout.Row(y); const size_t y0 = task * kRowsPerThread;
const size_t rows = std::min(kRowsPerThread, chin.h - y0);
size_t x = 0;
// special case for x=0 so we don't have to check x>0 #if HWY_TARGET != HWY_SCALAR
pixel_type_w avg = p_avg[0]; intptr_t onerow_in = chin.plane.PixelsPerRow();
pixel_type_w next_avg = (1 < chin.w ? p_avg[1] : avg); intptr_t onerow_inr = chin_residual.plane.PixelsPerRow();
pixel_type_w tendency = SmoothTendency(avg, avg, next_avg); intptr_t onerow_out = chout.plane.PixelsPerRow();
pixel_type_w diff = p_residual[0] + tendency; const pixel_type *JXL_RESTRICT p_residual = chin_residual.Row(y0);
pixel_type_w A = const pixel_type *JXL_RESTRICT p_avg = chin.Row(y0);
((avg * 2) + diff + (diff > 0 ? -(diff & 1) : (diff & 1))) >> 1; pixel_type *JXL_RESTRICT p_out = chout.Row(y0);
pixel_type_w B = A - diff; HWY_ALIGN pixel_type b_p_avg[9 * kRowsPerThread];
p_out[0] = A; HWY_ALIGN pixel_type b_p_residual[8 * kRowsPerThread];
p_out[1] = B; HWY_ALIGN pixel_type b_p_out_even[8 * kRowsPerThread];
HWY_ALIGN pixel_type b_p_out_odd[8 * kRowsPerThread];
for (size_t x = 1; x < chin_residual.w; x++) { HWY_ALIGN pixel_type b_p_out_evenT[8 * kRowsPerThread];
pixel_type_w diff_minus_tendency = p_residual[x]; HWY_ALIGN pixel_type b_p_out_oddT[8 * kRowsPerThread];
pixel_type_w avg = p_avg[x]; const HWY_CAPPED(pixel_type, 8) d;
pixel_type_w next_avg = (x + 1 < chin.w ? p_avg[x + 1] : avg); const size_t N = Lanes(d);
pixel_type_w left = p_out[(x << 1) - 1]; if (chin_residual.w > 16 && rows == kRowsPerThread) {
pixel_type_w tendency = SmoothTendency(left, avg, next_avg); for (; x < chin_residual.w - 9; x += 8) {
pixel_type_w diff = diff_minus_tendency + tendency; Transpose8x8Block(p_residual + x, b_p_residual, onerow_inr);
pixel_type_w A = Transpose8x8Block(p_avg + x, b_p_avg, onerow_in);
((avg * 2) + diff + (diff > 0 ? -(diff & 1) : (diff & 1))) >> 1; for (size_t y = 0; y < kRowsPerThread; y++) {
p_out[x << 1] = A; b_p_avg[8 * 8 + y] = p_avg[x + 8 + onerow_in * y];
pixel_type_w B = A - diff;
p_out[(x << 1) + 1] = B;
} }
if (chout.w & 1) p_out[chout.w - 1] = p_avg[chin.w - 1]; for (size_t i = 0; i < 8; i++) {
}, FastUnsqueeze(
"InvHorizontalSqueeze")); b_p_residual + 8 * i, b_p_avg + 8 * i, b_p_avg + 8 * (i + 1),
(x + i ? b_p_out_odd + 8 * ((x + i - 1) & 7) : b_p_avg + 8 * i),
b_p_out_even + 8 * i, b_p_out_odd + 8 * i);
}
Transpose8x8Block(b_p_out_even, b_p_out_evenT, 8);
Transpose8x8Block(b_p_out_odd, b_p_out_oddT, 8);
for (size_t y = 0; y < kRowsPerThread; y++) {
for (size_t i = 0; i < kRowsPerThread; i += N) {
auto even = Load(d, b_p_out_evenT + 8 * y + i);
auto odd = Load(d, b_p_out_oddT + 8 * y + i);
StoreInterleaved(d, even, odd,
p_out + ((x + i) << 1) + onerow_out * y);
}
}
}
}
#endif
for (size_t y = 0; y < rows; y++) {
unsqueeze_row(y0 + y, x);
}
};
JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, DivCeil(chin.h, kRowsPerThread),
ThreadPool::NoInit, unsqueeze_span,
"InvHorizontalSqueeze"));
input.channel[c] = std::move(chout); input.channel[c] = std::move(chout);
return true; return true;
} }
@ -111,42 +220,47 @@ Status InvVSqueeze(Image &input, uint32_t c, uint32_t rc, ThreadPool *pool) {
return true; return true;
} }
intptr_t onerow_in = chin.plane.PixelsPerRow(); static constexpr const int kColsPerThread = 64;
intptr_t onerow_out = chout.plane.PixelsPerRow(); const auto unsqueeze_slice = [&](const uint32_t task, size_t /* thread */) {
constexpr int kColsPerThread = 64; const size_t x0 = task * kColsPerThread;
JXL_RETURN_IF_ERROR(RunOnPool( const size_t x1 = std::min((size_t)(task + 1) * kColsPerThread, chin.w);
pool, 0, DivCeil(chin.w, kColsPerThread), ThreadPool::NoInit, const size_t w = x1 - x0;
[&](const uint32_t task, size_t /* thread */) { // We only iterate up to std::min(chin_residual.h, chin.h) which is
const size_t x0 = task * kColsPerThread; // always chin_residual.h.
const size_t x1 = std::min((size_t)(task + 1) * kColsPerThread, chin.w); for (size_t y = 0; y < chin_residual.h; y++) {
// We only iterate up to std::min(chin_residual.h, chin.h) which is const pixel_type *JXL_RESTRICT p_residual = chin_residual.Row(y) + x0;
// always chin_residual.h. const pixel_type *JXL_RESTRICT p_avg = chin.Row(y) + x0;
for (size_t y = 0; y < chin_residual.h; y++) { const pixel_type *JXL_RESTRICT p_navg =
const pixel_type *JXL_RESTRICT p_residual = chin_residual.Row(y); chin.Row(y + 1 < chin.h ? y + 1 : y) + x0;
const pixel_type *JXL_RESTRICT p_avg = chin.Row(y); pixel_type *JXL_RESTRICT p_out = chout.Row(y << 1) + x0;
pixel_type *JXL_RESTRICT p_out = chout.Row(y << 1); pixel_type *JXL_RESTRICT p_nout = chout.Row((y << 1) + 1) + x0;
for (size_t x = x0; x < x1; x++) { const pixel_type *p_pout = y > 0 ? chout.Row((y << 1) - 1) + x0 : p_avg;
pixel_type_w diff_minus_tendency = p_residual[x]; size_t x = 0;
pixel_type_w avg = p_avg[x]; #if HWY_TARGET != HWY_SCALAR
for (; x + 7 < w; x += 8) {
pixel_type_w next_avg = avg; FastUnsqueeze(p_residual + x, p_avg + x, p_navg + x, p_pout + x,
if (y + 1 < chin.h) next_avg = p_avg[x + onerow_in]; p_out + x, p_nout + x);
pixel_type_w top = }
(y > 0 ? p_out[static_cast<ssize_t>(x) - onerow_out] : avg); #endif
pixel_type_w tendency = SmoothTendency(top, avg, next_avg); for (; x < w; x++) {
pixel_type_w diff = diff_minus_tendency + tendency; pixel_type avg = p_avg[x];
pixel_type_w out = pixel_type next_avg = p_navg[x];
((avg * 2) + diff + (diff > 0 ? -(diff & 1) : (diff & 1))) >> 1; pixel_type top = p_pout[x];
pixel_type tendency = SmoothTendency(top, avg, next_avg);
p_out[x] = out; pixel_type diff_minus_tendency = p_residual[x];
// If the chin_residual.h == chin.h, the output has an even number pixel_type diff = diff_minus_tendency + tendency;
// of rows so the next line is fine. Otherwise, this loop won't pixel_type out = avg + (diff / 2);
// write to the last output row which is handled separately. p_out[x] = out;
p_out[x + onerow_out] = p_out[x] - diff; // If the chin_residual.h == chin.h, the output has an even number
} // of rows so the next line is fine. Otherwise, this loop won't
} // write to the last output row which is handled separately.
}, p_nout[x] = out - diff;
"InvVertSqueeze")); }
}
};
JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, DivCeil(chin.w, kColsPerThread),
ThreadPool::NoInit, unsqueeze_slice,
"InvVertSqueeze"));
if (chout.h & 1) { if (chout.h & 1) {
size_t y = chin.h - 1; size_t y = chin.h - 1;
@ -160,6 +274,62 @@ Status InvVSqueeze(Image &input, uint32_t c, uint32_t rc, ThreadPool *pool) {
return true; return true;
} }
Status InvSqueeze(Image &input, std::vector<SqueezeParams> parameters,
ThreadPool *pool) {
for (int i = parameters.size() - 1; i >= 0; i--) {
JXL_RETURN_IF_ERROR(
CheckMetaSqueezeParams(parameters[i], input.channel.size()));
bool horizontal = parameters[i].horizontal;
bool in_place = parameters[i].in_place;
uint32_t beginc = parameters[i].begin_c;
uint32_t endc = parameters[i].begin_c + parameters[i].num_c - 1;
uint32_t offset;
if (in_place) {
offset = endc + 1;
} else {
offset = input.channel.size() + beginc - endc - 1;
}
if (beginc < input.nb_meta_channels) {
// This is checked in MetaSqueeze.
JXL_ASSERT(input.nb_meta_channels > parameters[i].num_c);
input.nb_meta_channels -= parameters[i].num_c;
}
for (uint32_t c = beginc; c <= endc; c++) {
uint32_t rc = offset + c - beginc;
// MetaApply should imply that `rc` is within range, otherwise there's a
// programming bug.
JXL_ASSERT(rc < input.channel.size());
if ((input.channel[c].w < input.channel[rc].w) ||
(input.channel[c].h < input.channel[rc].h)) {
return JXL_FAILURE("Corrupted squeeze transform");
}
if (horizontal) {
JXL_RETURN_IF_ERROR(InvHSqueeze(input, c, rc, pool));
} else {
JXL_RETURN_IF_ERROR(InvVSqueeze(input, c, rc, pool));
}
}
input.channel.erase(input.channel.begin() + offset,
input.channel.begin() + offset + (endc - beginc + 1));
}
return true;
}
} // namespace HWY_NAMESPACE
} // namespace jxl
HWY_AFTER_NAMESPACE();
#if HWY_ONCE
namespace jxl {
HWY_EXPORT(InvSqueeze);
Status InvSqueeze(Image &input, std::vector<SqueezeParams> parameters,
ThreadPool *pool) {
return HWY_DYNAMIC_DISPATCH(InvSqueeze)(input, parameters, pool);
}
void DefaultSqueezeParameters(std::vector<SqueezeParams> *parameters, void DefaultSqueezeParameters(std::vector<SqueezeParams> *parameters,
const Image &image) { const Image &image) {
int nb_channels = image.channel.size() - image.nb_meta_channels; int nb_channels = image.channel.size() - image.nb_meta_channels;
@ -285,46 +455,6 @@ Status MetaSqueeze(Image &image, std::vector<SqueezeParams> *parameters) {
return true; return true;
} }
Status InvSqueeze(Image &input, std::vector<SqueezeParams> parameters,
ThreadPool *pool) {
for (int i = parameters.size() - 1; i >= 0; i--) {
JXL_RETURN_IF_ERROR(
CheckMetaSqueezeParams(parameters[i], input.channel.size()));
bool horizontal = parameters[i].horizontal;
bool in_place = parameters[i].in_place;
uint32_t beginc = parameters[i].begin_c;
uint32_t endc = parameters[i].begin_c + parameters[i].num_c - 1;
uint32_t offset;
if (in_place) {
offset = endc + 1;
} else {
offset = input.channel.size() + beginc - endc - 1;
}
if (beginc < input.nb_meta_channels) {
// This is checked in MetaSqueeze.
JXL_ASSERT(input.nb_meta_channels > parameters[i].num_c);
input.nb_meta_channels -= parameters[i].num_c;
}
for (uint32_t c = beginc; c <= endc; c++) {
uint32_t rc = offset + c - beginc;
// MetaApply should imply that `rc` is within range, otherwise there's a
// programming bug.
JXL_ASSERT(rc < input.channel.size());
if ((input.channel[c].w < input.channel[rc].w) ||
(input.channel[c].h < input.channel[rc].h)) {
return JXL_FAILURE("Corrupted squeeze transform");
}
if (horizontal) {
JXL_RETURN_IF_ERROR(InvHSqueeze(input, c, rc, pool));
} else {
JXL_RETURN_IF_ERROR(InvVSqueeze(input, c, rc, pool));
}
}
input.channel.erase(input.channel.begin() + offset,
input.channel.begin() + offset + (endc - beginc + 1));
}
return true;
}
} // namespace jxl } // namespace jxl
#endif

View File

@ -75,10 +75,6 @@ inline pixel_type_w SmoothTendency(pixel_type_w B, pixel_type_w a,
return diff; return diff;
} }
void InvHSqueeze(Image &input, int c, int rc, ThreadPool *pool);
void InvVSqueeze(Image &input, int c, int rc, ThreadPool *pool);
void DefaultSqueezeParameters(std::vector<SqueezeParams> *parameters, void DefaultSqueezeParameters(std::vector<SqueezeParams> *parameters,
const Image &image); const Image &image);

View File

@ -25,6 +25,7 @@
#include "lib/jxl/dec_file.h" #include "lib/jxl/dec_file.h"
#include "lib/jxl/dec_params.h" #include "lib/jxl/dec_params.h"
#include "lib/jxl/enc_butteraugli_comparator.h" #include "lib/jxl/enc_butteraugli_comparator.h"
#include "lib/jxl/enc_butteraugli_pnorm.h"
#include "lib/jxl/enc_cache.h" #include "lib/jxl/enc_cache.h"
#include "lib/jxl/enc_color_management.h" #include "lib/jxl/enc_color_management.h"
#include "lib/jxl/enc_file.h" #include "lib/jxl/enc_file.h"
@ -45,11 +46,10 @@ using test::Roundtrip;
void TestLosslessGroups(size_t group_size_shift) { void TestLosslessGroups(size_t group_size_shift) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CompressParams cparams; CompressParams cparams;
cparams.modular_mode = true; cparams.SetLossless();
cparams.modular_group_size_shift = group_size_shift; cparams.modular_group_size_shift = group_size_shift;
cparams.color_transform = jxl::ColorTransform::kNone;
DecompressParams dparams; DecompressParams dparams;
CodecInOut io_out; CodecInOut io_out;
@ -79,15 +79,14 @@ TEST(ModularTest, JXL_TSAN_SLOW_TEST(RoundtripLosslessGroups1024)) {
TEST(ModularTest, RoundtripLosslessCustomWP_PermuteRCT) { TEST(ModularTest, RoundtripLosslessCustomWP_PermuteRCT) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CompressParams cparams; CompressParams cparams;
cparams.modular_mode = true; cparams.SetLossless();
// 9 = permute to GBR, to test the special case of permutation-only // 9 = permute to GBR, to test the special case of permutation-only
cparams.colorspace = 9; cparams.colorspace = 9;
// slowest speed so different WP modes are tried // slowest speed so different WP modes are tried
cparams.speed_tier = SpeedTier::kTortoise; cparams.speed_tier = SpeedTier::kTortoise;
cparams.options.predictor = {Predictor::Weighted}; cparams.options.predictor = {Predictor::Weighted};
cparams.color_transform = jxl::ColorTransform::kNone;
DecompressParams dparams; DecompressParams dparams;
CodecInOut io_out; CodecInOut io_out;
@ -107,7 +106,7 @@ TEST(ModularTest, RoundtripLosslessCustomWP_PermuteRCT) {
TEST(ModularTest, RoundtripLossyDeltaPalette) { TEST(ModularTest, RoundtripLossyDeltaPalette) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CompressParams cparams; CompressParams cparams;
cparams.modular_mode = true; cparams.modular_mode = true;
cparams.color_transform = jxl::ColorTransform::kNone; cparams.color_transform = jxl::ColorTransform::kNone;
@ -133,10 +132,9 @@ TEST(ModularTest, RoundtripLossyDeltaPalette) {
TEST(ModularTest, RoundtripLossyDeltaPaletteWP) { TEST(ModularTest, RoundtripLossyDeltaPaletteWP) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CompressParams cparams; CompressParams cparams;
cparams.modular_mode = true; cparams.SetLossless();
cparams.color_transform = jxl::ColorTransform::kNone;
cparams.lossy_palette = true; cparams.lossy_palette = true;
cparams.palette_colors = 0; cparams.palette_colors = 0;
cparams.options.predictor = jxl::Predictor::Weighted; cparams.options.predictor = jxl::Predictor::Weighted;
@ -161,10 +159,10 @@ TEST(ModularTest, RoundtripLossyDeltaPaletteWP) {
TEST(ModularTest, RoundtripLossy) { TEST(ModularTest, RoundtripLossy) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CompressParams cparams; CompressParams cparams;
cparams.modular_mode = true; cparams.modular_mode = true;
cparams.quality_pair = {80.0f, 80.0f}; cparams.butteraugli_distance = 2.f;
DecompressParams dparams; DecompressParams dparams;
CodecInOut io_out; CodecInOut io_out;
@ -174,20 +172,20 @@ TEST(ModularTest, RoundtripLossy) {
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
compressed_size = Roundtrip(&io, cparams, dparams, pool, &io_out); compressed_size = Roundtrip(&io, cparams, dparams, pool, &io_out);
EXPECT_LE(compressed_size, 40000u); EXPECT_LE(compressed_size, 30000u);
cparams.ba_params.intensity_target = 80.0f; cparams.ba_params.intensity_target = 80.0f;
EXPECT_THAT(ButteraugliDistance(io, io_out, cparams.ba_params, GetJxlCms(), EXPECT_THAT(ButteraugliDistance(io, io_out, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool), /*distmap=*/nullptr, pool),
IsSlightlyBelow(2.0)); IsSlightlyBelow(2.3));
} }
TEST(ModularTest, RoundtripLossy16) { TEST(ModularTest, RoundtripLossy16) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("raw.pixls/DJI-FC6310-16bit_709_v4_krita.png"); ReadTestData("third_party/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png");
CompressParams cparams; CompressParams cparams;
cparams.modular_mode = true; cparams.modular_mode = true;
cparams.quality_pair = {80.0f, 80.0f}; cparams.butteraugli_distance = 2.f;
DecompressParams dparams; DecompressParams dparams;
CodecInOut io_out; CodecInOut io_out;
@ -199,11 +197,11 @@ TEST(ModularTest, RoundtripLossy16) {
io.metadata.m.color_encoding = ColorEncoding::SRGB(); io.metadata.m.color_encoding = ColorEncoding::SRGB();
compressed_size = Roundtrip(&io, cparams, dparams, pool, &io_out); compressed_size = Roundtrip(&io, cparams, dparams, pool, &io_out);
EXPECT_LE(compressed_size, 400u); EXPECT_LE(compressed_size, 300u);
cparams.ba_params.intensity_target = 80.0f; cparams.ba_params.intensity_target = 80.0f;
EXPECT_THAT(ButteraugliDistance(io, io_out, cparams.ba_params, GetJxlCms(), EXPECT_THAT(ButteraugliDistance(io, io_out, cparams.ba_params, GetJxlCms(),
/*distmap=*/nullptr, pool), /*distmap=*/nullptr, pool),
IsSlightlyBelow(1.5)); IsSlightlyBelow(1.6));
} }
TEST(ModularTest, RoundtripExtraProperties) { TEST(ModularTest, RoundtripExtraProperties) {
@ -250,15 +248,15 @@ TEST(ModularTest, RoundtripExtraProperties) {
TEST(ModularTest, RoundtripLosslessCustomSqueeze) { TEST(ModularTest, RoundtripLosslessCustomSqueeze) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/tmshre_riaphotographs_srgb8.png"); "third_party/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
CompressParams cparams; CompressParams cparams;
cparams.modular_mode = true; cparams.modular_mode = true;
cparams.color_transform = jxl::ColorTransform::kNone; cparams.color_transform = jxl::ColorTransform::kNone;
cparams.quality_pair = {100, 100}; cparams.butteraugli_distance = 0.f;
cparams.options.predictor = {Predictor::Zero}; cparams.options.predictor = {Predictor::Zero};
cparams.speed_tier = SpeedTier::kThunder; cparams.speed_tier = SpeedTier::kThunder;
cparams.responsive = 1; cparams.responsive = 1;
@ -281,6 +279,105 @@ TEST(ModularTest, RoundtripLosslessCustomSqueeze) {
/*distmap=*/nullptr, pool)); /*distmap=*/nullptr, pool));
} }
struct RoundtripLosslessConfig {
int bitdepth;
int responsive;
};
class ModularTestParam
: public ::testing::TestWithParam<RoundtripLosslessConfig> {};
std::vector<RoundtripLosslessConfig> GenerateLosslessTests() {
std::vector<RoundtripLosslessConfig> all;
for (int responsive = 0; responsive <= 1; responsive++) {
for (int bitdepth = 1; bitdepth < 32; bitdepth++) {
if (responsive && bitdepth > 30) continue;
all.push_back({bitdepth, responsive});
}
}
return all;
}
std::string LosslessTestDescription(
const testing::TestParamInfo<ModularTestParam::ParamType>& info) {
std::stringstream name;
name << info.param.bitdepth << "bit";
if (info.param.responsive) name << "Squeeze";
return name.str();
}
JXL_GTEST_INSTANTIATE_TEST_SUITE_P(RoundtripLossless, ModularTestParam,
testing::ValuesIn(GenerateLosslessTests()),
LosslessTestDescription);
TEST_P(ModularTestParam, RoundtripLossless) {
RoundtripLosslessConfig config = GetParam();
int bitdepth = config.bitdepth;
int responsive = config.responsive;
ThreadPool* pool = nullptr;
Rng generator(123);
const PaddedBytes orig =
ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io1;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io1, pool));
// vary the dimensions a bit, in case of bugs related to
// even vs odd width or height.
size_t xsize = 423 + bitdepth;
size_t ysize = 467 + bitdepth;
CodecInOut io;
io.SetSize(xsize, ysize);
io.metadata.m.color_encoding = jxl::ColorEncoding::SRGB(false);
io.metadata.m.SetUintSamples(bitdepth);
double factor = ((1lu << bitdepth) - 1lu);
double ifactor = 1.0 / factor;
Image3F noise_added(xsize, ysize);
for (size_t c = 0; c < 3; c++) {
for (size_t y = 0; y < ysize; y++) {
const float* in = io1.Main().color()->PlaneRow(c, y);
float* out = noise_added.PlaneRow(c, y);
for (size_t x = 0; x < xsize; x++) {
// make the least significant bits random
float f = in[x] + generator.UniformF(0.0f, 1.f / 255.f);
if (f > 1.f) f = 1.f;
// quantize to the bitdepth we're testing
unsigned int u = f * factor + 0.5;
out[x] = u * ifactor;
}
}
}
io.SetFromImage(std::move(noise_added), jxl::ColorEncoding::SRGB(false));
CompressParams cparams;
cparams.modular_mode = true;
cparams.color_transform = jxl::ColorTransform::kNone;
cparams.butteraugli_distance = 0.f;
cparams.options.predictor = {Predictor::Zero};
cparams.speed_tier = SpeedTier::kThunder;
cparams.responsive = responsive;
DecompressParams dparams;
CodecInOut io2;
EXPECT_LE(Roundtrip(&io, cparams, dparams, pool, &io2),
bitdepth * xsize * ysize / 3);
EXPECT_LE(0, ComputeDistance2(io.Main(), io2.Main(), GetJxlCms()));
size_t different = 0;
for (size_t c = 0; c < 3; c++) {
for (size_t y = 0; y < ysize; y++) {
const float* in = io.Main().color()->PlaneRow(c, y);
const float* out = io2.Main().color()->PlaneRow(c, y);
for (size_t x = 0; x < xsize; x++) {
uint32_t uin = in[x] * factor + 0.5;
uint32_t uout = out[x] * factor + 0.5;
// check that the integer values are identical
if (uin != uout) different++;
}
}
}
EXPECT_EQ(different, 0);
}
TEST(ModularTest, RoundtripLosslessCustomFloat) { TEST(ModularTest, RoundtripLosslessCustomFloat) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
CodecInOut io; CodecInOut io;
@ -310,7 +407,7 @@ TEST(ModularTest, RoundtripLosslessCustomFloat) {
CompressParams cparams; CompressParams cparams;
cparams.modular_mode = true; cparams.modular_mode = true;
cparams.color_transform = jxl::ColorTransform::kNone; cparams.color_transform = jxl::ColorTransform::kNone;
cparams.quality_pair = {100, 100}; cparams.butteraugli_distance = 0.f;
cparams.options.predictor = {Predictor::Zero}; cparams.options.predictor = {Predictor::Zero};
cparams.speed_tier = SpeedTier::kThunder; cparams.speed_tier = SpeedTier::kThunder;
cparams.decoding_speed_tier = 2; cparams.decoding_speed_tier = 2;

View File

@ -37,7 +37,7 @@ using test::Roundtrip;
TEST(PassesTest, RoundtripSmallPasses) { TEST(PassesTest, RoundtripSmallPasses) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 8, io.ysize() / 8); io.ShrinkTo(io.xsize() / 8, io.ysize() / 8);
@ -57,7 +57,7 @@ TEST(PassesTest, RoundtripSmallPasses) {
TEST(PassesTest, RoundtripUnalignedPasses) { TEST(PassesTest, RoundtripUnalignedPasses) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 12, io.ysize() / 7); io.ShrinkTo(io.xsize() / 12, io.ysize() / 7);
@ -77,7 +77,7 @@ TEST(PassesTest, RoundtripUnalignedPasses) {
TEST(PassesTest, RoundtripMultiGroupPasses) { TEST(PassesTest, RoundtripMultiGroupPasses) {
ThreadPoolInternal pool(4); ThreadPoolInternal pool(4);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
io.ShrinkTo(600, 1024); // partial X, full Y group io.ShrinkTo(600, 1024); // partial X, full Y group
@ -104,7 +104,7 @@ TEST(PassesTest, RoundtripMultiGroupPasses) {
TEST(PassesTest, RoundtripLargeFastPasses) { TEST(PassesTest, RoundtripLargeFastPasses) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -123,7 +123,7 @@ TEST(PassesTest, RoundtripLargeFastPasses) {
TEST(PassesTest, RoundtripProgressiveConsistent) { TEST(PassesTest, RoundtripProgressiveConsistent) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -160,7 +160,7 @@ TEST(PassesTest, RoundtripProgressiveConsistent) {
TEST(PassesTest, AllDownsampleFeasible) { TEST(PassesTest, AllDownsampleFeasible) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -207,7 +207,7 @@ TEST(PassesTest, AllDownsampleFeasible) {
TEST(PassesTest, AllDownsampleFeasibleQProgressive) { TEST(PassesTest, AllDownsampleFeasibleQProgressive) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -253,8 +253,8 @@ TEST(PassesTest, AllDownsampleFeasibleQProgressive) {
TEST(PassesTest, ProgressiveDownsample2DegradesCorrectlyGrayscale) { TEST(PassesTest, ProgressiveDownsample2DegradesCorrectlyGrayscale) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig = ReadTestData(
ReadTestData("wesaturate/500px/cvo9xd_keong_macan_grayscale.png"); "third_party/wesaturate/500px/cvo9xd_keong_macan_grayscale.png");
CodecInOut io_orig; CodecInOut io_orig;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io_orig, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io_orig, &pool));
Rect rect(0, 0, io_orig.xsize(), 128); Rect rect(0, 0, io_orig.xsize(), 128);
@ -302,7 +302,7 @@ TEST(PassesTest, ProgressiveDownsample2DegradesCorrectlyGrayscale) {
TEST(PassesTest, ProgressiveDownsample2DegradesCorrectly) { TEST(PassesTest, ProgressiveDownsample2DegradesCorrectly) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CodecInOut io_orig; CodecInOut io_orig;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io_orig, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io_orig, &pool));
Rect rect(0, 0, io_orig.xsize(), 128); Rect rect(0, 0, io_orig.xsize(), 128);
@ -349,7 +349,7 @@ TEST(PassesTest, ProgressiveDownsample2DegradesCorrectly) {
TEST(PassesTest, NonProgressiveDCImage) { TEST(PassesTest, NonProgressiveDCImage) {
ThreadPoolInternal pool(8); ThreadPoolInternal pool(8);
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("imagecompression.info/flower_foveon.png"); ReadTestData("third_party/imagecompression.info/flower_foveon.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, &pool));
@ -377,7 +377,7 @@ TEST(PassesTest, NonProgressiveDCImage) {
TEST(PassesTest, RoundtripSmallNoGaborishPasses) { TEST(PassesTest, RoundtripSmallNoGaborishPasses) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 8, io.ysize() / 8); io.ShrinkTo(io.xsize() / 8, io.ysize() / 8);

View File

@ -24,8 +24,7 @@ TEST(PatchDictionaryTest, GrayscaleModular) {
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
CompressParams cparams; CompressParams cparams;
cparams.color_transform = jxl::ColorTransform::kNone; cparams.SetLossless();
cparams.modular_mode = true;
cparams.patches = jxl::Override::kOn; cparams.patches = jxl::Override::kOn;
DecompressParams dparams; DecompressParams dparams;

View File

@ -34,7 +34,7 @@ using test::Roundtrip;
TEST(PreviewTest, RoundtripGivenPreview) { TEST(PreviewTest, RoundtripGivenPreview) {
ThreadPool* pool = nullptr; ThreadPool* pool = nullptr;
const PaddedBytes orig = const PaddedBytes orig =
ReadTestData("wesaturate/500px/u76c0g_bliznaca_srgb8.png"); ReadTestData("third_party/wesaturate/500px/u76c0g_bliznaca_srgb8.png");
CodecInOut io; CodecInOut io;
ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool)); ASSERT_TRUE(SetFromBytes(Span<const uint8_t>(orig), &io, pool));
io.ShrinkTo(io.xsize() / 8, io.ysize() / 8); io.ShrinkTo(io.xsize() / 8, io.ysize() / 8);

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