зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1749780 - Update libjxl to 9a74bd70b7932750deb78a8aebd6e041ce7f8b01 r=tnikkel
Differential Revision: https://phabricator.services.mozilla.com/D135769
This commit is contained in:
Родитель
cbb0d57034
Коммит
edcdc5567a
|
@ -48,6 +48,7 @@ SOURCES += [
|
|||
"/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/epf.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/fast_dct.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/fields.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/filters.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/frame_header.cc",
|
||||
|
@ -75,6 +76,19 @@ SOURCES += [
|
|||
"/third_party/jpeg-xl/lib/jxl/passes_state.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/quant_weights.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/quantizer.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/low_memory_render_pipeline.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/render_pipeline.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/simple_render_pipeline.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_chroma_upsampling.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_epf.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_gaborish.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_noise.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_patches.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_splines.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_upsampling.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_write_to_ib.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_xyb.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/render_pipeline/stage_ycbcr.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/splines.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/toc.cc",
|
||||
]
|
||||
|
@ -93,6 +107,7 @@ EXPORTS.jxl += [
|
|||
"./include/jxl/jxl_threads_export.h",
|
||||
"/third_party/jpeg-xl/lib/include/jxl/butteraugli.h",
|
||||
"/third_party/jpeg-xl/lib/include/jxl/butteraugli_cxx.h",
|
||||
"/third_party/jpeg-xl/lib/include/jxl/cms_interface.h",
|
||||
"/third_party/jpeg-xl/lib/include/jxl/codestream_header.h",
|
||||
"/third_party/jpeg-xl/lib/include/jxl/color_encoding.h",
|
||||
"/third_party/jpeg-xl/lib/include/jxl/decode.h",
|
||||
|
|
|
@ -20,12 +20,12 @@ origin:
|
|||
|
||||
# Human-readable identifier for this version/release
|
||||
# Generally "version NNN", "tag SSS", "bookmark SSS"
|
||||
release: commit a9100da7143e4f367d2e26f4d11e457e85343543 (2021-11-25T17:57:37Z).
|
||||
release: commit 9a74bd70b7932750deb78a8aebd6e041ce7f8b01 (2021-12-28T23:30:10Z).
|
||||
|
||||
# Revision to pull in
|
||||
# Must be a long or short commit SHA (long preferred)
|
||||
# NOTE(krosylight): Update highway together when updating this!
|
||||
revision: a9100da7143e4f367d2e26f4d11e457e85343543
|
||||
revision: 9a74bd70b7932750deb78a8aebd6e041ce7f8b01
|
||||
|
||||
# The package's license, where possible using the mnemonic from
|
||||
# https://spdx.org/licenses/
|
||||
|
@ -54,3 +54,5 @@ vendoring:
|
|||
- tools/
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ Kleis Auke Wolthuizen <github@kleisauke.nl>
|
|||
Leo Izen <leo.izen@gmail.com>
|
||||
Lovell Fuller
|
||||
Marcin Konicki <ahwayakchih@gmail.com>
|
||||
Mathieu Malaterre <mathieu.malaterre@gmail.com>
|
||||
Misaki Kasumi <misakikasumi@outlook.com>
|
||||
Petr Diblík
|
||||
Pieter Wuille
|
||||
|
|
|
@ -23,6 +23,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
the non-coalesced case.
|
||||
- decoder API: new function `JxlDecoderGetExtraChannelBlendInfo` to get
|
||||
the blending information for extra channels in the non-coalesced case.
|
||||
- encoder API: added ability to set several encoder options to frames using
|
||||
`JxlEncoderFrameSettingsSetOption`
|
||||
- encoder API: new functions `JxlEncoderSetFrameHeader` and
|
||||
`JxlEncoderSetExtraChannelBlendInfo` to set animation
|
||||
and blending parameters of the frame, and `JxlEncoderInitFrameHeader` and
|
||||
`JxlEncoderInitBlendInfo` to initialize the structs to set.
|
||||
- decoder/encoder API: add two fields to `JXLBasicInfo`: `intrinsic_xsize`
|
||||
and `intrinsic_ysize` to signal the intrinsic size.
|
||||
- encoder API: ability to encode arbitrary extra channels:
|
||||
`JxlEncoderInitExtraChannelInfo`, `JxlEncoderSetExtraChannelInfo`,
|
||||
`JxlEncoderSetExtraChannelName` and `JxlEncoderSetExtraChannelBuffer`.
|
||||
|
||||
### Changed
|
||||
- decoder API: using `JxlDecoderCloseInput` at the end of all input is required
|
||||
|
@ -30,6 +41,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
required in those other cases for backwards compatiblity.
|
||||
- encoder API: `JxlEncoderCloseInput` now closes both frames and boxes input.
|
||||
|
||||
### Deprecated
|
||||
- encoder API: `JxlEncoderOptions`: use `JxlEncoderFrameSettings` instead
|
||||
- encoder API: `JxlEncoderOptionsCreate`: use `JxlEncoderFrameSettingsCreate`
|
||||
instead
|
||||
- encoder API: `JxlEncoderOptionsSetDistance`: use `JxlEncoderSetFrameDistance`
|
||||
instead
|
||||
- encoder API: `JxlEncoderOptionsSetLossless`: use `JxlEncoderSetFrameLossless`
|
||||
instead
|
||||
- encoder API: `JxlEncoderOptionsSetEffort`: use `JxlEncoderFrameSettingsSetOption(
|
||||
frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, effort)` instead.
|
||||
- encoder API: `JxlEncoderOptionsSetDecodingSpeed`: use
|
||||
`JxlEncoderFrameSettingsSetOption(frame_settings,
|
||||
JXL_ENC_FRAME_SETTING_DECODING_SPEED, tier)` instead.
|
||||
- encoder API: deprecated `JXL_ENC_NOT_SUPPORTED`, the encoder returns
|
||||
`JXL_ENC_ERROR` instead and there is no need to handle
|
||||
`JXL_ENC_NOT_SUPPORTED`.
|
||||
|
||||
## [0.6.1] - 2021-10-29
|
||||
### Changed
|
||||
- Security: Fix OOB read in splines rendering (#735 -
|
||||
|
|
|
@ -39,6 +39,7 @@ include(CheckCXXCompilerFlag)
|
|||
check_cxx_compiler_flag("-fsanitize=fuzzer-no-link" CXX_FUZZERS_SUPPORTED)
|
||||
check_cxx_compiler_flag("-Xclang -mconstructor-aliases" CXX_CONSTRUCTOR_ALIASES_SUPPORTED)
|
||||
check_cxx_compiler_flag("-fmacro-prefix-map=OLD=NEW" CXX_MACRO_PREFIX_MAP)
|
||||
check_cxx_compiler_flag("-fno-rtti" CXX_NO_RTTI_SUPPORTED)
|
||||
|
||||
# Enabled PIE binaries by default if supported.
|
||||
include(CheckPIESupported OPTIONAL RESULT_VARIABLE CHECK_PIE_SUPPORTED)
|
||||
|
@ -73,6 +74,12 @@ endif()
|
|||
|
||||
set(WARNINGS_AS_ERRORS_DEFAULT false)
|
||||
|
||||
if((SANITIZER STREQUAL "msan") OR JPEGXL_EMSCRIPTEN)
|
||||
set(BUNDLE_LIBPNG_DEFAULT YES)
|
||||
else()
|
||||
set(BUNDLE_LIBPNG_DEFAULT NO)
|
||||
endif()
|
||||
|
||||
# Standard cmake naming for building shared libraries.
|
||||
option(BUILD_SHARED_LIBS "Build shared libraries instead of static ones" ON)
|
||||
|
||||
|
@ -88,6 +95,8 @@ set(JPEGXL_ENABLE_BENCHMARK true CACHE BOOL
|
|||
"Build JPEGXL benchmark tools.")
|
||||
set(JPEGXL_ENABLE_EXAMPLES true CACHE BOOL
|
||||
"Build JPEGXL library usage examples.")
|
||||
set(JPEGXL_BUNDLE_LIBPNG ${BUNDLE_LIBPNG_DEFAULT} CACHE BOOL
|
||||
"Build libpng from source and link it statically.")
|
||||
set(JPEGXL_ENABLE_JNI true CACHE BOOL
|
||||
"Build JPEGXL JNI Java wrapper, if Java dependencies are installed.")
|
||||
set(JPEGXL_ENABLE_SJPEG true CACHE BOOL
|
||||
|
@ -214,6 +223,10 @@ if ("${CXX_MACRO_PREFIX_MAP}")
|
|||
add_compile_options(-fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.)
|
||||
endif()
|
||||
|
||||
if (CXX_NO_RTTI_SUPPORTED)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
# TODO(janwas): add flags
|
||||
else ()
|
||||
|
@ -231,12 +244,6 @@ if(MSVC)
|
|||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
|
||||
if("${JPEGXL_ENABLE_FUZZERS}" OR "${JPEGXL_ENABLE_COVERAGE}")
|
||||
add_definitions(
|
||||
-DJXL_ENABLE_FUZZERS
|
||||
)
|
||||
endif() # JPEGXL_ENABLE_FUZZERS
|
||||
|
||||
# In CMake before 3.12 it is problematic to pass repeated flags like -Xclang.
|
||||
# For this reason we place them in CMAKE_CXX_FLAGS instead.
|
||||
# See https://gitlab.kitware.com/cmake/cmake/issues/15826
|
||||
|
|
|
@ -600,8 +600,8 @@ cmd_gbench() {
|
|||
}
|
||||
|
||||
cmd_asanfuzz() {
|
||||
CMAKE_CXX_FLAGS+=" -fsanitize=fuzzer-no-link"
|
||||
CMAKE_C_FLAGS+=" -fsanitize=fuzzer-no-link"
|
||||
CMAKE_CXX_FLAGS+=" -fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1"
|
||||
CMAKE_C_FLAGS+=" -fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1"
|
||||
cmd_asan -DJPEGXL_ENABLE_FUZZERS=ON "$@"
|
||||
}
|
||||
|
||||
|
@ -615,8 +615,8 @@ cmd_msanfuzz() {
|
|||
cmd_msan_install
|
||||
fi
|
||||
|
||||
CMAKE_CXX_FLAGS+=" -fsanitize=fuzzer-no-link"
|
||||
CMAKE_C_FLAGS+=" -fsanitize=fuzzer-no-link"
|
||||
CMAKE_CXX_FLAGS+=" -fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1"
|
||||
CMAKE_C_FLAGS+=" -fsanitize=fuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1"
|
||||
cmd_msan -DJPEGXL_ENABLE_FUZZERS=ON "$@"
|
||||
}
|
||||
|
||||
|
@ -697,7 +697,7 @@ cmd_msan() {
|
|||
strip_dead_code
|
||||
cmake_configure "$@" \
|
||||
-DCMAKE_CROSSCOMPILING=1 -DRUN_HAVE_STD_REGEX=0 -DRUN_HAVE_POSIX_REGEX=0 \
|
||||
-DJPEGXL_ENABLE_TCMALLOC=OFF
|
||||
-DJPEGXL_ENABLE_TCMALLOC=OFF -DJPEGXL_WARNINGS_AS_ERRORS=OFF
|
||||
cmake_build_and_test
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
set(brlibs brotlicommon brotlienc brotlidec)
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
if (PkgConfig_FOUND)
|
||||
foreach(brlib IN ITEMS ${brlibs})
|
||||
string(TOUPPER "${brlib}" BRPREFIX)
|
||||
pkg_check_modules("PC_${BRPREFIX}" lib${brlib})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
find_path(BROTLI_INCLUDE_DIR
|
||||
NAMES brotli/decode.h
|
||||
HINTS ${PC_BROTLICOMMON_INCLUDEDIR} ${PC_BROTLICOMMON_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
foreach(brlib IN ITEMS ${brlibs})
|
||||
string(TOUPPER "${brlib}" BRPREFIX)
|
||||
find_library(${BRPREFIX}_LIBRARY
|
||||
NAMES ${${BRPREFIX}_NAMES} ${brlib}
|
||||
HINTS ${PC_${BRPREFIX}_LIBDIR} ${PC_${BRPREFIX}_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if (${BRPREFIX}_LIBRARY AND NOT TARGET ${brlib})
|
||||
if(${CMAKE_VERSION} VERSION_LESS "3.13.5")
|
||||
add_library(${brlib} INTERFACE IMPORTED GLOBAL)
|
||||
set_property(TARGET ${brlib} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${BROTLI_INCLUDE_DIR})
|
||||
target_link_libraries(${brlib} INTERFACE ${${BRPREFIX}_LIBRARY})
|
||||
set_property(TARGET ${brlib} PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_${BRPREFIX}_CFLAGS_OTHER})
|
||||
|
||||
add_library(${brlib}-static INTERFACE IMPORTED GLOBAL)
|
||||
set_property(TARGET ${brlib}-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${BROTLI_INCLUDE_DIR})
|
||||
target_link_libraries(${brlib}-static INTERFACE ${${BRPREFIX}_LIBRARY})
|
||||
set_property(TARGET ${brlib}-static PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_${BRPREFIX}_CFLAGS_OTHER})
|
||||
else()
|
||||
add_library(${brlib} INTERFACE IMPORTED GLOBAL)
|
||||
target_include_directories(${brlib}
|
||||
INTERFACE ${BROTLI_INCLUDE_DIR})
|
||||
target_link_libraries(${brlib}
|
||||
INTERFACE ${${BRPREFIX}_LIBRARY})
|
||||
target_link_options(${brlib}
|
||||
INTERFACE ${PC_${BRPREFIX}_LDFLAGS_OTHER})
|
||||
target_compile_options(${brlib}
|
||||
INTERFACE ${PC_${BRPREFIX}_CFLAGS_OTHER})
|
||||
|
||||
# TODO(deymo): Remove the -static library versions, this target is
|
||||
# currently needed by brunsli.cmake. When importing it this way, the
|
||||
# brotli*-static target is just an alias.
|
||||
add_library(${brlib}-static ALIAS ${brlib})
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if (BROTLICOMMON_FOUND AND BROTLIENC_FOUND AND BROTLIDEC_FOUND)
|
||||
set(Brotli_FOUND ON)
|
||||
else ()
|
||||
set(Brotli_FOUND OFF)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Brotli
|
||||
FOUND_VAR Brotli_FOUND
|
||||
REQUIRED_VARS
|
||||
BROTLI_INCLUDE_DIR
|
||||
BROTLICOMMON_LIBRARY
|
||||
BROTLIENC_LIBRARY
|
||||
BROTLIDEC_LIBRARY
|
||||
VERSION_VAR Brotli_VERSION
|
||||
)
|
||||
|
||||
mark_as_advanced(
|
||||
BROTLI_INCLUDE_DIR
|
||||
BROTLICOMMON_LIBRARY
|
||||
BROTLIENC_LIBRARY
|
||||
BROTLIDEC_LIBRARY
|
||||
)
|
||||
|
||||
if (Brotli_FOUND)
|
||||
set(Brotli_LIBRARIES ${BROTLICOMMON_LIBRARY} ${BROTLIENC_LIBRARY} ${BROTLIDEC_LIBRARY})
|
||||
set(Brotli_INCLUDE_DIRS ${BROTLI_INCLUDE_DIR})
|
||||
endif()
|
|
@ -9,28 +9,6 @@ Files: third_party/sjpeg/*
|
|||
Copyright: 2017 Google, Inc
|
||||
License: Apache-2.0
|
||||
|
||||
Files: third_party/lodepng/*
|
||||
Copyright: 2005-2018 Lode Vandevenne
|
||||
License: Zlib License
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
.
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
.
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
|
||||
Files: third_party/skcms/*
|
||||
Copyright: 2018 Google Inc.
|
||||
License: BSD-3-clause
|
||||
|
|
|
@ -14,9 +14,10 @@ MYDIR=$(dirname $(realpath "$0"))
|
|||
# Git revisions we use for the given submodules. Update these whenever you
|
||||
# update a git submodule.
|
||||
THIRD_PARTY_HIGHWAY="e69083a12a05caf037cabecdf1b248b7579705a5"
|
||||
THIRD_PARTY_LODEPNG="8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a"
|
||||
THIRD_PARTY_SKCMS="64374756e03700d649f897dbd98c95e78c30c7da"
|
||||
THIRD_PARTY_SJPEG="868ab558fad70fcbe8863ba4e85179eeb81cc840"
|
||||
THIRD_PARTY_ZLIB="cacf7f1d4e3d44d871b605da3b647f07d718623f"
|
||||
THIRD_PARTY_LIBPNG="a40189cf881e9f0db80511c382292a5604c3c3d1"
|
||||
|
||||
# Download the target revision from GitHub.
|
||||
download_github() {
|
||||
|
@ -70,10 +71,11 @@ EOF
|
|||
|
||||
# Sources downloaded from a tarball.
|
||||
download_github third_party/highway google/highway
|
||||
download_github third_party/lodepng lvandeve/lodepng
|
||||
download_github third_party/sjpeg webmproject/sjpeg
|
||||
download_github third_party/skcms \
|
||||
"https://skia.googlesource.com/skcms/+archive/"
|
||||
download_github third_party/zlib madler/zlib
|
||||
download_github third_party/libpng glennrp/libpng
|
||||
echo "Done."
|
||||
}
|
||||
|
||||
|
|
|
@ -183,13 +183,17 @@ bool EncodeJxlOneshot(const std::vector<float>& pixels, const uint32_t xsize,
|
|||
return false;
|
||||
}
|
||||
|
||||
JxlEncoderFrameSettings* frame_settings =
|
||||
JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
|
||||
|
||||
if (JXL_ENC_SUCCESS !=
|
||||
JxlEncoderAddImageFrame(JxlEncoderOptionsCreate(enc.get(), nullptr),
|
||||
&pixel_format, (void*)pixels.data(),
|
||||
JxlEncoderAddImageFrame(frame_settings, &pixel_format,
|
||||
(void*)pixels.data(),
|
||||
sizeof(float) * pixels.size())) {
|
||||
fprintf(stderr, "JxlEncoderAddImageFrame failed\n");
|
||||
return false;
|
||||
}
|
||||
JxlEncoderCloseInput(enc.get());
|
||||
|
||||
compressed->resize(64);
|
||||
uint8_t* next_out = compressed->data();
|
||||
|
|
|
@ -106,6 +106,8 @@ int PrintBasicInfo(FILE* file) {
|
|||
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",
|
||||
|
@ -153,8 +155,8 @@ int PrintBasicInfo(FILE* file) {
|
|||
free(name);
|
||||
break;
|
||||
}
|
||||
free(name);
|
||||
printf(" name: %s\n", name);
|
||||
free(name);
|
||||
}
|
||||
if (extra.type == JXL_CHANNEL_ALPHA)
|
||||
printf(" alpha_premultiplied: %d (%s)\n", extra.alpha_premultiplied,
|
||||
|
@ -268,8 +270,8 @@ int PrintBasicInfo(FILE* file) {
|
|||
free(name);
|
||||
break;
|
||||
}
|
||||
free(name);
|
||||
printf(" name: %s\n", name);
|
||||
free(name);
|
||||
}
|
||||
float ms = frame_header.duration * 1000.f *
|
||||
info.animation.tps_denominator / info.animation.tps_numerator;
|
||||
|
|
|
@ -5,22 +5,22 @@
|
|||
|
||||
#include "lib/extras/codec.h"
|
||||
|
||||
#include "lib/jxl/base/file_io.h"
|
||||
#if JPEGXL_ENABLE_APNG
|
||||
#include "lib/extras/codec_apng.h"
|
||||
#include "lib/extras/enc/apng.h"
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
#include "lib/extras/enc/jpg.h"
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_EXR
|
||||
#include "lib/extras/codec_exr.h"
|
||||
#include "lib/extras/enc/exr.h"
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_GIF
|
||||
#include "lib/extras/codec_gif.h"
|
||||
#endif
|
||||
#include "lib/extras/codec_jpg.h"
|
||||
#include "lib/extras/codec_pgx.h"
|
||||
#include "lib/extras/codec_png.h"
|
||||
#include "lib/extras/codec_pnm.h"
|
||||
|
||||
#include "lib/extras/codec_psd.h"
|
||||
#include "lib/extras/dec/decode.h"
|
||||
#include "lib/extras/enc/pgx.h"
|
||||
#include "lib/extras/enc/pnm.h"
|
||||
#include "lib/extras/packed_image_convert.h"
|
||||
#include "lib/jxl/base/file_io.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
|
||||
namespace jxl {
|
||||
|
@ -31,127 +31,26 @@ constexpr size_t kMinBytes = 9;
|
|||
|
||||
} // 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(const std::string& extension,
|
||||
size_t* JXL_RESTRICT bits_per_sample) {
|
||||
if (extension == ".png") return Codec::kPNG;
|
||||
|
||||
if (extension == ".jpg") return Codec::kJPG;
|
||||
if (extension == ".jpeg") return Codec::kJPG;
|
||||
|
||||
if (extension == ".pgx") return Codec::kPGX;
|
||||
|
||||
if (extension == ".pbm") {
|
||||
*bits_per_sample = 1;
|
||||
return Codec::kPNM;
|
||||
}
|
||||
if (extension == ".pgm") return Codec::kPNM;
|
||||
if (extension == ".ppm") return Codec::kPNM;
|
||||
if (extension == ".pfm") {
|
||||
*bits_per_sample = 32;
|
||||
return Codec::kPNM;
|
||||
}
|
||||
|
||||
if (extension == ".gif") return Codec::kGIF;
|
||||
|
||||
if (extension == ".exr") return Codec::kEXR;
|
||||
|
||||
if (extension == ".psd") return Codec::kPSD;
|
||||
|
||||
return Codec::kUnknown;
|
||||
}
|
||||
|
||||
Status SetFromBytes(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints, CodecInOut* io,
|
||||
ThreadPool* pool, Codec* orig_codec) {
|
||||
const extras::ColorHints& color_hints, CodecInOut* io,
|
||||
ThreadPool* pool, extras::Codec* orig_codec) {
|
||||
if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes");
|
||||
|
||||
extras::PackedPixelFile ppf;
|
||||
// Default values when not set by decoders.
|
||||
ppf.info.uses_original_profile = true;
|
||||
ppf.info.orientation = JXL_ORIENT_IDENTITY;
|
||||
|
||||
Codec codec;
|
||||
bool skip_ppf_conversion = false;
|
||||
if (extras::DecodeImagePNG(bytes, color_hints, io->constraints, &ppf)) {
|
||||
codec = Codec::kPNG;
|
||||
}
|
||||
#if JPEGXL_ENABLE_APNG
|
||||
else if (extras::DecodeImageAPNG(bytes, color_hints, io->constraints, &ppf)) {
|
||||
codec = Codec::kPNG;
|
||||
}
|
||||
#endif
|
||||
else if (extras::DecodeImagePGX(bytes, color_hints, io->constraints, &ppf)) {
|
||||
codec = Codec::kPGX;
|
||||
} else if (extras::DecodeImagePNM(bytes, color_hints, io->constraints,
|
||||
&ppf)) {
|
||||
codec = Codec::kPNM;
|
||||
}
|
||||
#if JPEGXL_ENABLE_GIF
|
||||
else if (extras::DecodeImageGIF(bytes, color_hints, io->constraints, &ppf)) {
|
||||
codec = Codec::kGIF;
|
||||
}
|
||||
#endif
|
||||
else if (io->dec_target == DecodeTarget::kQuantizedCoeffs &&
|
||||
extras::DecodeImageJPGCoefficients(bytes, io)) {
|
||||
// TODO(deymo): In this case the tools should use a different API to
|
||||
// transcode the input JPEG to JXL instead of expressing it as a
|
||||
// PackedPixelFile.
|
||||
codec = Codec::kJPG;
|
||||
skip_ppf_conversion = true;
|
||||
} else if (io->dec_target == DecodeTarget::kPixels &&
|
||||
extras::DecodeImageJPG(bytes, color_hints, io->constraints,
|
||||
&ppf)) {
|
||||
codec = Codec::kJPG;
|
||||
if (extras::DecodeBytes(bytes, color_hints, io->constraints, &ppf, pool,
|
||||
orig_codec)) {
|
||||
return ConvertPackedPixelFileToCodecInOut(ppf, pool, io);
|
||||
} else if (extras::DecodeImagePSD(bytes, color_hints, pool, io)) {
|
||||
// TODO(deymo): Migrate PSD codec too.
|
||||
codec = Codec::kPSD;
|
||||
skip_ppf_conversion = true;
|
||||
if (orig_codec) *orig_codec = extras::Codec::kPSD;
|
||||
return true;
|
||||
}
|
||||
#if JPEGXL_ENABLE_EXR
|
||||
else if (extras::DecodeImageEXR(bytes, color_hints, io->constraints,
|
||||
io->target_nits, pool, &ppf)) {
|
||||
codec = Codec::kEXR;
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
return JXL_FAILURE("Codecs failed to decode");
|
||||
}
|
||||
if (orig_codec) *orig_codec = codec;
|
||||
|
||||
if (!skip_ppf_conversion) {
|
||||
JXL_RETURN_IF_ERROR(ConvertPackedPixelFileToCodecInOut(ppf, pool, io));
|
||||
}
|
||||
|
||||
return true;
|
||||
return JXL_FAILURE("Codecs failed to decode");
|
||||
}
|
||||
|
||||
Status SetFromFile(const std::string& pathname, const ColorHints& color_hints,
|
||||
CodecInOut* io, ThreadPool* pool, Codec* orig_codec) {
|
||||
Status SetFromFile(const std::string& pathname,
|
||||
const extras::ColorHints& color_hints, CodecInOut* io,
|
||||
ThreadPool* pool, extras::Codec* orig_codec) {
|
||||
PaddedBytes encoded;
|
||||
JXL_RETURN_IF_ERROR(ReadFile(pathname, &encoded));
|
||||
JXL_RETURN_IF_ERROR(SetFromBytes(Span<const uint8_t>(encoded), color_hints,
|
||||
|
@ -159,54 +58,52 @@ Status SetFromFile(const std::string& pathname, const ColorHints& color_hints,
|
|||
return true;
|
||||
}
|
||||
|
||||
Status Encode(const CodecInOut& io, const Codec codec,
|
||||
Status Encode(const CodecInOut& io, const extras::Codec codec,
|
||||
const ColorEncoding& c_desired, size_t bits_per_sample,
|
||||
PaddedBytes* bytes, ThreadPool* pool) {
|
||||
JXL_CHECK(!io.Main().c_current().ICC().empty());
|
||||
JXL_CHECK(!c_desired.ICC().empty());
|
||||
io.CheckMetadata();
|
||||
if (io.Main().IsJPEG() && codec != Codec::kJPG) {
|
||||
return JXL_FAILURE(
|
||||
"Output format has to be JPEG for losslessly recompressed JPEG "
|
||||
"reconstruction");
|
||||
if (io.Main().IsJPEG()) {
|
||||
JXL_WARNING("Writing JPEG data as pixels");
|
||||
}
|
||||
|
||||
switch (codec) {
|
||||
case Codec::kPNG:
|
||||
return extras::EncodeImagePNG(&io, c_desired, bits_per_sample, pool,
|
||||
bytes);
|
||||
case Codec::kJPG:
|
||||
if (io.Main().IsJPEG()) {
|
||||
return extras::EncodeImageJPGCoefficients(&io, bytes);
|
||||
} else {
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
return EncodeImageJPG(&io,
|
||||
io.use_sjpeg ? extras::JpegEncoder::kSJpeg
|
||||
: extras::JpegEncoder::kLibJpeg,
|
||||
io.jpeg_quality, YCbCrChromaSubsampling(), pool,
|
||||
bytes);
|
||||
case extras::Codec::kPNG:
|
||||
#if JPEGXL_ENABLE_APNG
|
||||
return extras::EncodeImageAPNG(&io, c_desired, bits_per_sample, pool,
|
||||
bytes);
|
||||
#else
|
||||
return JXL_FAILURE("JPEG XL was built without JPEG support");
|
||||
return JXL_FAILURE("JPEG XL was built without (A)PNG support");
|
||||
#endif
|
||||
}
|
||||
case Codec::kPNM:
|
||||
case extras::Codec::kJPG:
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
return EncodeImageJPG(&io,
|
||||
io.use_sjpeg ? extras::JpegEncoder::kSJpeg
|
||||
: extras::JpegEncoder::kLibJpeg,
|
||||
io.jpeg_quality, YCbCrChromaSubsampling(), pool,
|
||||
bytes);
|
||||
#else
|
||||
return JXL_FAILURE("JPEG XL was built without JPEG support");
|
||||
#endif
|
||||
case extras::Codec::kPNM:
|
||||
return extras::EncodeImagePNM(&io, c_desired, bits_per_sample, pool,
|
||||
bytes);
|
||||
case Codec::kPGX:
|
||||
case extras::Codec::kPGX:
|
||||
return extras::EncodeImagePGX(&io, c_desired, bits_per_sample, pool,
|
||||
bytes);
|
||||
case Codec::kGIF:
|
||||
case extras::Codec::kGIF:
|
||||
return JXL_FAILURE("Encoding to GIF is not implemented");
|
||||
case Codec::kPSD:
|
||||
case extras::Codec::kPSD:
|
||||
return extras::EncodeImagePSD(&io, c_desired, bits_per_sample, pool,
|
||||
bytes);
|
||||
case Codec::kEXR:
|
||||
case extras::Codec::kEXR:
|
||||
#if JPEGXL_ENABLE_EXR
|
||||
return extras::EncodeImageEXR(&io, c_desired, pool, bytes);
|
||||
#else
|
||||
return JXL_FAILURE("JPEG XL was built without OpenEXR support");
|
||||
#endif
|
||||
case Codec::kUnknown:
|
||||
case extras::Codec::kUnknown:
|
||||
return JXL_FAILURE("Cannot encode using Codec::kUnknown");
|
||||
}
|
||||
|
||||
|
@ -217,11 +114,12 @@ Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired,
|
|||
size_t bits_per_sample, const std::string& pathname,
|
||||
ThreadPool* pool) {
|
||||
const std::string extension = Extension(pathname);
|
||||
const Codec codec = CodecFromExtension(extension, &bits_per_sample);
|
||||
const extras::Codec codec =
|
||||
extras::CodecFromExtension(extension, &bits_per_sample);
|
||||
|
||||
// Warn about incorrect usage of PBM/PGM/PGX/PPM - only the latter supports
|
||||
// color, but CodecFromExtension lumps them all together.
|
||||
if (codec == Codec::kPNM && extension != ".pfm") {
|
||||
if (codec == extras::Codec::kPNM && extension != ".pfm") {
|
||||
if (!io.Main().IsGray() && extension != ".ppm") {
|
||||
JXL_WARNING("For color images, the filename should end with .ppm.\n");
|
||||
} else if (io.Main().IsGray() && extension == ".ppm") {
|
||||
|
@ -232,10 +130,10 @@ Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired,
|
|||
JXL_WARNING("PPM only supports up to 16 bits per sample");
|
||||
bits_per_sample = 16;
|
||||
}
|
||||
} else if (codec == Codec::kPGX && !io.Main().IsGray()) {
|
||||
} else if (codec == extras::Codec::kPGX && !io.Main().IsGray()) {
|
||||
JXL_WARNING("Storing color image to PGX - use .ppm extension instead.\n");
|
||||
}
|
||||
if (bits_per_sample > 16 && codec == Codec::kPNG) {
|
||||
if (bits_per_sample > 16 && codec == extras::Codec::kPNG) {
|
||||
JXL_WARNING("PNG only supports up to 16 bits per sample");
|
||||
bits_per_sample = 16;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
#include "lib/extras/color_hints.h"
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/extras/dec/decode.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
|
@ -25,61 +26,30 @@
|
|||
|
||||
namespace jxl {
|
||||
|
||||
// Codecs supported by CodecInOut::Encode.
|
||||
enum class Codec : uint32_t {
|
||||
kUnknown, // for CodecFromExtension
|
||||
kPNG,
|
||||
kPNM,
|
||||
kPGX,
|
||||
kJPG,
|
||||
kGIF,
|
||||
kEXR,
|
||||
kPSD
|
||||
};
|
||||
|
||||
static inline constexpr uint64_t EnumBits(Codec /*unused*/) {
|
||||
// Return only fully-supported codecs (kGIF is decode-only).
|
||||
return MakeBit(Codec::kPNM) | MakeBit(Codec::kPNG)
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
| MakeBit(Codec::kJPG)
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_EXR
|
||||
| MakeBit(Codec::kEXR)
|
||||
#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
|
||||
// that Encode() would encode to PFM instead of PPM.
|
||||
Codec CodecFromExtension(const std::string& extension,
|
||||
size_t* JXL_RESTRICT bits_per_sample);
|
||||
|
||||
// Decodes "bytes" and sets io->metadata.m.
|
||||
// color_space_hint may specify the color space, otherwise, defaults to sRGB.
|
||||
Status SetFromBytes(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints, CodecInOut* io,
|
||||
ThreadPool* pool, Codec* orig_codec);
|
||||
Status SetFromBytes(Span<const uint8_t> bytes,
|
||||
const extras::ColorHints& color_hints, CodecInOut* io,
|
||||
ThreadPool* pool = nullptr,
|
||||
extras::Codec* orig_codec = nullptr);
|
||||
// Helper function to use no color_space_hint.
|
||||
JXL_INLINE Status SetFromBytes(const Span<const uint8_t> bytes, CodecInOut* io,
|
||||
ThreadPool* pool = nullptr,
|
||||
Codec* orig_codec = nullptr) {
|
||||
return SetFromBytes(bytes, ColorHints(), io, pool, orig_codec);
|
||||
extras::Codec* orig_codec = nullptr) {
|
||||
return SetFromBytes(bytes, extras::ColorHints(), io, pool, orig_codec);
|
||||
}
|
||||
|
||||
// Reads from file and calls SetFromBytes.
|
||||
Status SetFromFile(const std::string& pathname, const ColorHints& color_hints,
|
||||
CodecInOut* io, ThreadPool* pool = nullptr,
|
||||
Codec* orig_codec = nullptr);
|
||||
Status SetFromFile(const std::string& pathname,
|
||||
const extras::ColorHints& color_hints, CodecInOut* io,
|
||||
ThreadPool* pool = nullptr,
|
||||
extras::Codec* orig_codec = nullptr);
|
||||
|
||||
// Replaces "bytes" with an encoding of pixels transformed from c_current
|
||||
// color space to c_desired.
|
||||
Status Encode(const CodecInOut& io, Codec codec, const ColorEncoding& c_desired,
|
||||
size_t bits_per_sample, PaddedBytes* bytes,
|
||||
ThreadPool* pool = nullptr);
|
||||
Status Encode(const CodecInOut& io, extras::Codec codec,
|
||||
const ColorEncoding& c_desired, size_t bits_per_sample,
|
||||
PaddedBytes* bytes, ThreadPool* pool = nullptr);
|
||||
|
||||
// Deduces codec, calls Encode and writes to file.
|
||||
Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired,
|
||||
|
|
|
@ -1,412 +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_apng.h"
|
||||
|
||||
// Parts of this code are taken from apngdis, which has the following license:
|
||||
/* APNG Disassembler 2.8
|
||||
*
|
||||
* Deconstructs APNG files into individual frames.
|
||||
*
|
||||
* http://apngdis.sourceforge.net
|
||||
*
|
||||
* Copyright (c) 2010-2015 Max Stepin
|
||||
* maxst at users.sourceforge.net
|
||||
*
|
||||
* zlib license
|
||||
* ------------
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/encode.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/color_management.h"
|
||||
#include "lib/jxl/frame_header.h"
|
||||
#include "lib/jxl/headers.h"
|
||||
#include "lib/jxl/image.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
#include "lib/jxl/luminance.h"
|
||||
#include "png.h" /* original (unpatched) libpng is ok */
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr bool isAbc(char c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
||||
}
|
||||
#define notabc(c) ((c) < 65 || (c) > 122 || ((c) > 90 && (c) < 97))
|
||||
|
||||
constexpr uint32_t kId_IHDR = 0x52444849;
|
||||
constexpr uint32_t kId_acTL = 0x4C546361;
|
||||
constexpr uint32_t kId_fcTL = 0x4C546366;
|
||||
constexpr uint32_t kId_IDAT = 0x54414449;
|
||||
constexpr uint32_t kId_fdAT = 0x54416466;
|
||||
constexpr uint32_t kId_IEND = 0x444E4549;
|
||||
|
||||
struct CHUNK {
|
||||
unsigned char* p;
|
||||
unsigned int size;
|
||||
};
|
||||
|
||||
struct APNGFrame {
|
||||
unsigned char *p, **rows;
|
||||
unsigned int w, h, delay_num, delay_den;
|
||||
};
|
||||
|
||||
struct Reader {
|
||||
const uint8_t* next;
|
||||
const uint8_t* last;
|
||||
bool Read(void* data, size_t len) {
|
||||
size_t cap = last - next;
|
||||
size_t to_copy = std::min(cap, len);
|
||||
memcpy(data, next, to_copy);
|
||||
next += to_copy;
|
||||
return (len == to_copy);
|
||||
}
|
||||
bool Eof() { return next == last; }
|
||||
};
|
||||
|
||||
const unsigned long cMaxPNGSize = 1000000UL;
|
||||
const size_t kMaxPNGChunkSize = 100000000; // 100 MB
|
||||
|
||||
void info_fn(png_structp png_ptr, png_infop info_ptr) {
|
||||
png_set_expand(png_ptr);
|
||||
png_set_strip_16(png_ptr);
|
||||
png_set_gray_to_rgb(png_ptr);
|
||||
png_set_palette_to_rgb(png_ptr);
|
||||
png_set_add_alpha(png_ptr, 0xff, PNG_FILLER_AFTER);
|
||||
(void)png_set_interlace_handling(png_ptr);
|
||||
png_read_update_info(png_ptr, info_ptr);
|
||||
}
|
||||
|
||||
void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num,
|
||||
int pass) {
|
||||
APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr);
|
||||
png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row);
|
||||
}
|
||||
|
||||
inline unsigned int read_chunk(Reader* r, CHUNK* pChunk) {
|
||||
unsigned char len[4];
|
||||
pChunk->size = 0;
|
||||
pChunk->p = 0;
|
||||
if (r->Read(&len, 4)) {
|
||||
const auto size = png_get_uint_32(len);
|
||||
// Check first, to avoid overflow.
|
||||
if (size > kMaxPNGChunkSize) {
|
||||
JXL_WARNING("APNG chunk size is too big");
|
||||
return 0;
|
||||
}
|
||||
pChunk->size = size + 12;
|
||||
pChunk->p = new unsigned char[pChunk->size];
|
||||
memcpy(pChunk->p, len, 4);
|
||||
if (r->Read(pChunk->p + 4, pChunk->size - 4)) {
|
||||
return *(unsigned int*)(pChunk->p + 4);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr,
|
||||
bool hasInfo, CHUNK& chunkIHDR,
|
||||
std::vector<CHUNK>& chunksInfo) {
|
||||
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);
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!png_ptr || !info_ptr) return 1;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
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_process_data(png_ptr, info_ptr, header, 8);
|
||||
png_process_data(png_ptr, info_ptr, chunkIHDR.p, chunkIHDR.size);
|
||||
|
||||
if (hasInfo) {
|
||||
for (unsigned int i = 0; i < chunksInfo.size(); i++) {
|
||||
png_process_data(png_ptr, info_ptr, chunksInfo[i].p, chunksInfo[i].size);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int processing_data(png_structp png_ptr, png_infop info_ptr, unsigned char* p,
|
||||
unsigned int size) {
|
||||
if (!png_ptr || !info_ptr) return 1;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
png_process_data(png_ptr, info_ptr, p, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int processing_finish(png_structp png_ptr, png_infop info_ptr) {
|
||||
unsigned char footer[12] = {0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130};
|
||||
|
||||
if (!png_ptr || !info_ptr) return 1;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
png_process_data(png_ptr, info_ptr, footer, 12);
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
Reader r;
|
||||
unsigned int id, i, j, w, h, w0, h0, x0, y0;
|
||||
unsigned int delay_num, delay_den, dop, bop, rowbytes, imagesize;
|
||||
unsigned char sig[8];
|
||||
png_structp png_ptr;
|
||||
png_infop info_ptr;
|
||||
CHUNK chunk;
|
||||
CHUNK chunkIHDR;
|
||||
std::vector<CHUNK> chunksInfo;
|
||||
bool isAnimated = false;
|
||||
bool skipFirst = false;
|
||||
bool hasInfo = false;
|
||||
bool all_dispose_bg = true;
|
||||
APNGFrame frameRaw = {};
|
||||
|
||||
r = {bytes.data(), bytes.data() + bytes.size()};
|
||||
// Not an aPNG => not an error
|
||||
unsigned char png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10};
|
||||
if (!r.Read(sig, 8) || memcmp(sig, png_signature, 8) != 0) {
|
||||
return false;
|
||||
}
|
||||
id = read_chunk(&r, &chunkIHDR);
|
||||
|
||||
// todo: get data from png metadata
|
||||
JxlColorEncodingSetToSRGB(&ppf->color_encoding, /*is_gray=*/false);
|
||||
JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, /*color_already_set=*/true,
|
||||
/*is_gray=*/false, ppf));
|
||||
|
||||
// Only 8-bit supported.
|
||||
ppf->info.bits_per_sample = 8;
|
||||
ppf->info.exponent_bits_per_sample = 0;
|
||||
ppf->info.alpha_bits = 8;
|
||||
ppf->info.alpha_exponent_bits = 0;
|
||||
|
||||
ppf->info.num_color_channels = 3; // RGBA
|
||||
ppf->info.orientation = JXL_ORIENT_IDENTITY;
|
||||
|
||||
const JxlPixelFormat format{
|
||||
/*num_channels=*/4,
|
||||
/*data_type=*/JXL_TYPE_UINT8,
|
||||
/*endianness=*/JXL_BIG_ENDIAN,
|
||||
/*align=*/0,
|
||||
};
|
||||
ppf->frames.clear();
|
||||
|
||||
bool errorstate = true;
|
||||
if (id == kId_IHDR && chunkIHDR.size == 25) {
|
||||
w0 = w = png_get_uint_32(chunkIHDR.p + 8);
|
||||
h0 = h = png_get_uint_32(chunkIHDR.p + 12);
|
||||
|
||||
if (w > cMaxPNGSize || h > cMaxPNGSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ppf->info.xsize = w;
|
||||
ppf->info.ysize = h;
|
||||
JXL_RETURN_IF_ERROR(VerifyDimensions(&constraints, w, h));
|
||||
|
||||
x0 = 0;
|
||||
y0 = 0;
|
||||
delay_num = 1;
|
||||
delay_den = 10;
|
||||
dop = 0;
|
||||
bop = 0;
|
||||
rowbytes = w * 4;
|
||||
imagesize = h * rowbytes;
|
||||
|
||||
frameRaw.p = new unsigned char[imagesize];
|
||||
frameRaw.rows = new png_bytep[h * sizeof(png_bytep)];
|
||||
for (j = 0; j < h; j++) frameRaw.rows[j] = frameRaw.p + j * rowbytes;
|
||||
|
||||
if (!processing_start(png_ptr, info_ptr, (void*)&frameRaw, hasInfo,
|
||||
chunkIHDR, chunksInfo)) {
|
||||
bool last_base_was_none = true;
|
||||
while (!r.Eof()) {
|
||||
id = read_chunk(&r, &chunk);
|
||||
if (!id) break;
|
||||
JXL_ASSERT(chunk.p != nullptr);
|
||||
|
||||
if (id == kId_acTL && !hasInfo && !isAnimated) {
|
||||
isAnimated = true;
|
||||
skipFirst = true;
|
||||
ppf->info.have_animation = true;
|
||||
ppf->info.animation.tps_numerator = 1000;
|
||||
ppf->info.animation.tps_denominator = 1;
|
||||
} else if (id == kId_IEND ||
|
||||
(id == kId_fcTL && (!hasInfo || isAnimated))) {
|
||||
if (hasInfo) {
|
||||
if (!processing_finish(png_ptr, info_ptr)) {
|
||||
// Allocates the frame buffer.
|
||||
ppf->frames.emplace_back(w0, h0, format);
|
||||
auto* frame = &ppf->frames.back();
|
||||
|
||||
frame->frame_info.duration = delay_num * 1000 / delay_den;
|
||||
frame->x0 = x0;
|
||||
frame->y0 = y0;
|
||||
// TODO(veluca): this could in principle be implemented.
|
||||
if (last_base_was_none && !all_dispose_bg &&
|
||||
(x0 != 0 || y0 != 0 || w0 != w || h0 != h || bop != 0)) {
|
||||
delete[] frameRaw.rows;
|
||||
delete[] frameRaw.p;
|
||||
return JXL_FAILURE(
|
||||
"APNG with dispose-to-0 is not supported for non-full or "
|
||||
"blended frames");
|
||||
}
|
||||
switch (dop) {
|
||||
case 0:
|
||||
frame->use_for_next_frame = true;
|
||||
last_base_was_none = false;
|
||||
all_dispose_bg = false;
|
||||
break;
|
||||
case 2:
|
||||
frame->use_for_next_frame = false;
|
||||
all_dispose_bg = false;
|
||||
break;
|
||||
default:
|
||||
frame->use_for_next_frame = false;
|
||||
last_base_was_none = true;
|
||||
}
|
||||
frame->blend = bop != 0;
|
||||
|
||||
for (size_t y = 0; y < h0; ++y) {
|
||||
memcpy(static_cast<uint8_t*>(frame->color.pixels()) +
|
||||
frame->color.stride * y,
|
||||
frameRaw.rows[y], 4 * w0);
|
||||
}
|
||||
} else {
|
||||
delete[] chunk.p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (id == kId_IEND) {
|
||||
errorstate = false;
|
||||
break;
|
||||
}
|
||||
// At this point the old frame is done. Let's start a new one.
|
||||
w0 = png_get_uint_32(chunk.p + 12);
|
||||
h0 = png_get_uint_32(chunk.p + 16);
|
||||
x0 = png_get_uint_32(chunk.p + 20);
|
||||
y0 = png_get_uint_32(chunk.p + 24);
|
||||
delay_num = png_get_uint_16(chunk.p + 28);
|
||||
delay_den = png_get_uint_16(chunk.p + 30);
|
||||
dop = chunk.p[32];
|
||||
bop = chunk.p[33];
|
||||
|
||||
if (!delay_den) delay_den = 100;
|
||||
|
||||
if (w0 > cMaxPNGSize || h0 > cMaxPNGSize || x0 > cMaxPNGSize ||
|
||||
y0 > cMaxPNGSize || x0 + w0 > w || y0 + h0 > h || dop > 2 ||
|
||||
bop > 1) {
|
||||
delete[] chunk.p;
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasInfo) {
|
||||
memcpy(chunkIHDR.p + 8, chunk.p + 12, 8);
|
||||
if (processing_start(png_ptr, info_ptr, (void*)&frameRaw, hasInfo,
|
||||
chunkIHDR, chunksInfo)) {
|
||||
delete[] chunk.p;
|
||||
break;
|
||||
}
|
||||
} else
|
||||
skipFirst = false;
|
||||
|
||||
if (ppf->frames.size() == (skipFirst ? 1 : 0)) {
|
||||
bop = 0;
|
||||
if (dop == 2) dop = 1;
|
||||
}
|
||||
} else if (id == kId_IDAT) {
|
||||
hasInfo = true;
|
||||
if (processing_data(png_ptr, info_ptr, chunk.p, chunk.size)) {
|
||||
delete[] chunk.p;
|
||||
break;
|
||||
}
|
||||
} else if (id == kId_fdAT && isAnimated) {
|
||||
png_save_uint_32(chunk.p + 4, chunk.size - 16);
|
||||
memcpy(chunk.p + 8, "IDAT", 4);
|
||||
if (processing_data(png_ptr, info_ptr, chunk.p + 4, chunk.size - 4)) {
|
||||
delete[] chunk.p;
|
||||
break;
|
||||
}
|
||||
} else if (!isAbc(chunk.p[4]) || !isAbc(chunk.p[5]) ||
|
||||
!isAbc(chunk.p[6]) || !isAbc(chunk.p[7])) {
|
||||
delete[] chunk.p;
|
||||
break;
|
||||
} else if (!hasInfo) {
|
||||
if (processing_data(png_ptr, info_ptr, chunk.p, chunk.size)) {
|
||||
delete[] chunk.p;
|
||||
break;
|
||||
}
|
||||
chunksInfo.push_back(chunk);
|
||||
continue;
|
||||
}
|
||||
delete[] chunk.p;
|
||||
}
|
||||
}
|
||||
delete[] frameRaw.rows;
|
||||
delete[] frameRaw.p;
|
||||
}
|
||||
|
||||
for (i = 0; i < chunksInfo.size(); i++) delete[] chunksInfo[i].p;
|
||||
|
||||
chunksInfo.clear();
|
||||
delete[] chunkIHDR.p;
|
||||
|
||||
if (errorstate) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
|
@ -1,59 +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_JPG_H_
|
||||
#define LIB_EXTRAS_CODEC_JPG_H_
|
||||
|
||||
// Encodes JPG pixels and metadata in memory.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/extras/codec.h"
|
||||
#include "lib/extras/color_hints.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
enum class JpegEncoder {
|
||||
kLibJpeg,
|
||||
kSJpeg,
|
||||
};
|
||||
|
||||
static inline bool IsJPG(const Span<const uint8_t> bytes) {
|
||||
if (bytes.size() < 2) return false;
|
||||
if (bytes[0] != 0xFF || bytes[1] != 0xD8) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Decodes `bytes` into `io`. color_hints are ignored.
|
||||
// `elapsed_deinterleave`, if non-null, will be set to the time (in seconds)
|
||||
// that it took to deinterleave the raw JSAMPLEs to planar floats.
|
||||
Status DecodeImageJPG(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, PackedPixelFile* ppf);
|
||||
|
||||
// Encodes into `bytes`.
|
||||
Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
|
||||
YCbCrChromaSubsampling chroma_subsampling,
|
||||
ThreadPool* pool, PaddedBytes* bytes);
|
||||
|
||||
// Temporary wrappers to load the JPEG coefficients to a CodecInOut. This should
|
||||
// be replaced by calling the corresponding JPEG input and output functions on
|
||||
// the API.
|
||||
|
||||
// Decodes the JPEG image coefficients to a CodecIO for lossless recompression.
|
||||
Status DecodeImageJPGCoefficients(Span<const uint8_t> bytes, CodecInOut* io);
|
||||
|
||||
// Reconstructs the JPEG from the coefficients and metadata in CodecInOut.
|
||||
Status EncodeImageJPGCoefficients(const CodecInOut* io, PaddedBytes* bytes);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_CODEC_JPG_H_
|
|
@ -1,852 +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_png.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// Lodepng library:
|
||||
#include <lodepng.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#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/dec_external_image.h"
|
||||
#include "lib/jxl/enc_external_image.h"
|
||||
#include "lib/jxl/enc_image_bundle.h"
|
||||
#include "lib/jxl/image.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
#include "lib/jxl/luminance.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
namespace {
|
||||
|
||||
#define JXL_PNG_VERBOSE 0
|
||||
|
||||
// Retrieves XMP and EXIF/IPTC from itext and text.
|
||||
class BlobsReaderPNG {
|
||||
public:
|
||||
static Status Decode(const LodePNGInfo& info, PackedMetadata* metadata) {
|
||||
for (unsigned idx_itext = 0; idx_itext < info.itext_num; ++idx_itext) {
|
||||
// We trust these are properly null-terminated by LodePNG.
|
||||
const char* key = info.itext_keys[idx_itext];
|
||||
const char* value = info.itext_strings[idx_itext];
|
||||
if (strstr(key, "XML:com.adobe.xmp")) {
|
||||
metadata->xmp.resize(strlen(value)); // safe, see above
|
||||
memcpy(metadata->xmp.data(), value, metadata->xmp.size());
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned idx_text = 0; idx_text < info.text_num; ++idx_text) {
|
||||
// We trust these are properly null-terminated by LodePNG.
|
||||
const char* key = info.text_keys[idx_text];
|
||||
const char* value = info.text_strings[idx_text];
|
||||
std::string type;
|
||||
std::vector<uint8_t> bytes;
|
||||
|
||||
// Handle text chunks annotated with key "Raw profile type ####", with
|
||||
// #### a type, which may contain metadata.
|
||||
const char* kKey = "Raw profile type ";
|
||||
if (strncmp(key, kKey, strlen(kKey)) != 0) continue;
|
||||
|
||||
if (!MaybeDecodeBase16(key, value, &type, &bytes)) {
|
||||
JXL_WARNING("Couldn't parse 'Raw format type' text chunk");
|
||||
continue;
|
||||
}
|
||||
if (type == "exif") {
|
||||
if (!metadata->exif.empty()) {
|
||||
JXL_WARNING("overwriting EXIF (%" PRIuS " bytes) with base16 (%" PRIuS
|
||||
" bytes)",
|
||||
metadata->exif.size(), bytes.size());
|
||||
}
|
||||
metadata->exif = std::move(bytes);
|
||||
} else if (type == "iptc") {
|
||||
// TODO (jon): Deal with IPTC in some way
|
||||
} else if (type == "8bim") {
|
||||
// TODO (jon): Deal with 8bim in some way
|
||||
} else if (type == "xmp") {
|
||||
if (!metadata->xmp.empty()) {
|
||||
JXL_WARNING("overwriting XMP (%" PRIuS " bytes) with base16 (%" PRIuS
|
||||
" bytes)",
|
||||
metadata->xmp.size(), bytes.size());
|
||||
}
|
||||
metadata->xmp = std::move(bytes);
|
||||
} else {
|
||||
JXL_WARNING("Unknown type in 'Raw format type' text chunk: %s: %" PRIuS
|
||||
" bytes",
|
||||
type.c_str(), bytes.size());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Returns false if invalid.
|
||||
static JXL_INLINE Status DecodeNibble(const char c,
|
||||
uint32_t* JXL_RESTRICT nibble) {
|
||||
if ('a' <= c && c <= 'f') {
|
||||
*nibble = 10 + c - 'a';
|
||||
} else if ('0' <= c && c <= '9') {
|
||||
*nibble = c - '0';
|
||||
} else {
|
||||
*nibble = 0;
|
||||
return JXL_FAILURE("Invalid metadata nibble");
|
||||
}
|
||||
JXL_ASSERT(*nibble < 16);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parses a PNG text chunk with key of the form "Raw profile type ####", with
|
||||
// #### a type.
|
||||
// Returns whether it could successfully parse the content.
|
||||
// We trust key and encoded are null-terminated because they come from
|
||||
// LodePNG.
|
||||
static Status MaybeDecodeBase16(const char* key, const char* encoded,
|
||||
std::string* type,
|
||||
std::vector<uint8_t>* bytes) {
|
||||
const char* encoded_end = encoded + strlen(encoded);
|
||||
|
||||
const char* kKey = "Raw profile type ";
|
||||
if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
|
||||
*type = key + strlen(kKey);
|
||||
const size_t kMaxTypeLen = 20;
|
||||
if (type->length() > kMaxTypeLen) return false; // Type too long
|
||||
|
||||
// Header: freeform string and number of bytes
|
||||
// Expected format is:
|
||||
// \n
|
||||
// profile name/description\n
|
||||
// 40\n (the number of bytes after hex-decoding)
|
||||
// 01234566789abcdef....\n (72 bytes per line max).
|
||||
// 012345667\n (last line)
|
||||
const char* pos = encoded;
|
||||
|
||||
if (*(pos++) != '\n') return false;
|
||||
while (pos < encoded_end && *pos != '\n') {
|
||||
pos++;
|
||||
}
|
||||
if (pos == encoded_end) return false;
|
||||
// We parsed so far a \n, some number of non \n characters and are now
|
||||
// pointing at a \n.
|
||||
if (*(pos++) != '\n') return false;
|
||||
unsigned long bytes_to_decode;
|
||||
const int fields = sscanf(pos, "%8lu", &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.
|
||||
const unsigned long needed_bytes =
|
||||
bytes_to_decode * 2 + 1 + DivCeil(bytes_to_decode, 36);
|
||||
if (needed_bytes != static_cast<size_t>(encoded_end - pos)) {
|
||||
return JXL_FAILURE("Not enough bytes to parse %lu bytes in hex",
|
||||
bytes_to_decode);
|
||||
}
|
||||
JXL_ASSERT(bytes->empty());
|
||||
bytes->reserve(bytes_to_decode);
|
||||
|
||||
// Encoding: base16 with newline after 72 chars.
|
||||
// pos points to the \n before the first line of hex values.
|
||||
for (size_t i = 0; i < bytes_to_decode; ++i) {
|
||||
if (i % 36 == 0) {
|
||||
if (pos + 1 >= encoded_end) return false; // Truncated base16 1
|
||||
if (*pos != '\n') return false; // Expected newline
|
||||
++pos;
|
||||
}
|
||||
|
||||
if (pos + 2 >= encoded_end) return false; // Truncated base16 2;
|
||||
uint32_t nibble0, nibble1;
|
||||
JXL_RETURN_IF_ERROR(DecodeNibble(pos[0], &nibble0));
|
||||
JXL_RETURN_IF_ERROR(DecodeNibble(pos[1], &nibble1));
|
||||
bytes->push_back(static_cast<uint8_t>((nibble0 << 4) + nibble1));
|
||||
pos += 2;
|
||||
}
|
||||
if (pos + 1 != encoded_end) return false; // Too many encoded bytes
|
||||
if (pos[0] != '\n') return false; // Incorrect metadata terminator
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Stores XMP and EXIF/IPTC into itext and text.
|
||||
class BlobsWriterPNG {
|
||||
public:
|
||||
static Status Encode(const Blobs& blobs, LodePNGInfo* JXL_RESTRICT info) {
|
||||
if (!blobs.exif.empty()) {
|
||||
JXL_RETURN_IF_ERROR(EncodeBase16("exif", blobs.exif, info));
|
||||
}
|
||||
if (!blobs.iptc.empty()) {
|
||||
JXL_RETURN_IF_ERROR(EncodeBase16("iptc", blobs.iptc, info));
|
||||
}
|
||||
|
||||
if (!blobs.xmp.empty()) {
|
||||
JXL_RETURN_IF_ERROR(EncodeBase16("xmp", blobs.xmp, info));
|
||||
|
||||
// Below is the official way, but it does not seem to work in ImageMagick.
|
||||
// Exiv2 and exiftool are OK with either way of encoding XMP.
|
||||
if (/* DISABLES CODE */ (0)) {
|
||||
const char* key = "XML:com.adobe.xmp";
|
||||
const std::string text(reinterpret_cast<const char*>(blobs.xmp.data()),
|
||||
blobs.xmp.size());
|
||||
if (lodepng_add_itext(info, key, "", "", text.c_str()) != 0) {
|
||||
return JXL_FAILURE("Failed to add itext");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
LodePNGInfo* JXL_RESTRICT info) {
|
||||
// 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());
|
||||
|
||||
const std::string& encoded = std::string(header) + base16;
|
||||
if (lodepng_add_text(info, key, encoded.c_str()) != 0) {
|
||||
return JXL_FAILURE("Failed to add text");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Retrieves ColorEncoding from PNG chunks.
|
||||
class ColorEncodingReaderPNG {
|
||||
public:
|
||||
// Fills original->color_encoding or returns false.
|
||||
Status operator()(const Span<const uint8_t> bytes, const bool is_gray,
|
||||
PackedPixelFile* ppf) {
|
||||
JXL_RETURN_IF_ERROR(Decode(bytes, &ppf->metadata, &ppf->color_encoding));
|
||||
|
||||
const JxlColorSpace color_space =
|
||||
is_gray ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
|
||||
ppf->color_encoding.color_space = color_space;
|
||||
|
||||
if (have_pq_) {
|
||||
// Synthesize the ICC with these parameters instead.
|
||||
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
|
||||
ppf->color_encoding.primaries = JXL_PRIMARIES_2100;
|
||||
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_PQ;
|
||||
ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ICC overrides anything else if present.
|
||||
ppf->icc = std::move(icc_);
|
||||
|
||||
// PNG requires that sRGB override gAMA/cHRM.
|
||||
if (have_srgb_) {
|
||||
ppf->icc.clear();
|
||||
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
|
||||
ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
|
||||
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
|
||||
ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try to create a custom profile:
|
||||
|
||||
// Attempt to set whitepoint and primaries if there is a cHRM chunk, or else
|
||||
// use default sRGB (the PNG then is device-dependent).
|
||||
// In case of grayscale, do not attempt to set the primaries and ignore the
|
||||
// ones the PNG image has (but still set the white point).
|
||||
if (!have_chrm_) {
|
||||
#if JXL_PNG_VERBOSE >= 1
|
||||
JXL_WARNING("No cHRM, assuming sRGB");
|
||||
#endif
|
||||
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
|
||||
ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
|
||||
}
|
||||
|
||||
if (!have_gama_) {
|
||||
#if JXL_PNG_VERBOSE >= 1
|
||||
JXL_WARNING("No gAMA nor sRGB, assuming sRGB");
|
||||
#endif
|
||||
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
|
||||
}
|
||||
|
||||
ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Whether the image has any color profile information (ICC chunk, sRGB
|
||||
// chunk, cHRM chunk, and so on), or has no color information chunks at all.
|
||||
bool HaveColorProfile() const {
|
||||
return have_pq_ || have_srgb_ || have_gama_ || have_chrm_ || have_icc_;
|
||||
}
|
||||
|
||||
private:
|
||||
Status DecodeICC(const unsigned char* const payload,
|
||||
const size_t payload_size) {
|
||||
if (payload_size == 0) return JXL_FAILURE("Empty ICC payload");
|
||||
const unsigned char* pos = payload;
|
||||
const unsigned char* end = payload + payload_size;
|
||||
|
||||
// Profile name
|
||||
if (*pos == '\0') return JXL_FAILURE("Expected ICC name");
|
||||
for (size_t i = 0;; ++i) {
|
||||
if (i == 80) return JXL_FAILURE("ICC profile name too long");
|
||||
if (pos == end) return JXL_FAILURE("Not enough bytes for ICC name");
|
||||
if (*pos++ == '\0') break;
|
||||
}
|
||||
|
||||
// Special case for BT.2100 PQ (https://w3c.github.io/png-hdr-pq/) - try to
|
||||
// synthesize the profile because table-based curves are less accurate.
|
||||
// strcmp is safe because we already verified the string is 0-terminated.
|
||||
if (!strcmp(reinterpret_cast<const char*>(payload), "ITUR_2100_PQ_FULL")) {
|
||||
have_pq_ = true;
|
||||
}
|
||||
|
||||
// Skip over compression method (only one is allowed)
|
||||
if (pos == end) return JXL_FAILURE("Not enough bytes for ICC method");
|
||||
if (*pos++ != 0) return JXL_FAILURE("Unsupported ICC method");
|
||||
|
||||
// Decompress
|
||||
unsigned char* icc_buf = nullptr;
|
||||
size_t icc_size = 0;
|
||||
LodePNGDecompressSettings settings;
|
||||
lodepng_decompress_settings_init(&settings);
|
||||
const unsigned err = lodepng_zlib_decompress(
|
||||
&icc_buf, &icc_size, pos, payload_size - (pos - payload), &settings);
|
||||
if (err == 0) {
|
||||
icc_.resize(icc_size);
|
||||
memcpy(icc_.data(), icc_buf, icc_size);
|
||||
}
|
||||
free(icc_buf);
|
||||
have_icc_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns floating-point value from the PNG encoding (times 10^5).
|
||||
static double F64FromU32(const uint32_t x) {
|
||||
return static_cast<int32_t>(x) * 1E-5;
|
||||
}
|
||||
|
||||
Status DecodeSRGB(const unsigned char* payload, const size_t payload_size,
|
||||
JxlColorEncoding* color_encoding) {
|
||||
if (payload_size != 1) return JXL_FAILURE("Wrong sRGB size");
|
||||
// (PNG uses the same values as ICC.)
|
||||
if (payload[0] >= 4) return JXL_FAILURE("Invalid Rendering Intent");
|
||||
color_encoding->rendering_intent =
|
||||
static_cast<JxlRenderingIntent>(payload[0]);
|
||||
have_srgb_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Status DecodeGAMA(const unsigned char* payload, const size_t payload_size,
|
||||
JxlColorEncoding* color_encoding) {
|
||||
if (payload_size != 4) return JXL_FAILURE("Wrong gAMA size");
|
||||
color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
|
||||
color_encoding->gamma = F64FromU32(LoadBE32(payload));
|
||||
have_gama_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Status DecodeCHRM(const unsigned char* payload, const size_t payload_size,
|
||||
JxlColorEncoding* color_encoding) {
|
||||
if (payload_size != 32) return JXL_FAILURE("Wrong cHRM size");
|
||||
|
||||
color_encoding->white_point = JXL_WHITE_POINT_CUSTOM;
|
||||
color_encoding->white_point_xy[0] = F64FromU32(LoadBE32(payload + 0));
|
||||
color_encoding->white_point_xy[1] = F64FromU32(LoadBE32(payload + 4));
|
||||
|
||||
color_encoding->primaries = JXL_PRIMARIES_CUSTOM;
|
||||
color_encoding->primaries_red_xy[0] = F64FromU32(LoadBE32(payload + 8));
|
||||
color_encoding->primaries_red_xy[1] = F64FromU32(LoadBE32(payload + 12));
|
||||
color_encoding->primaries_green_xy[0] = F64FromU32(LoadBE32(payload + 16));
|
||||
color_encoding->primaries_green_xy[1] = F64FromU32(LoadBE32(payload + 20));
|
||||
color_encoding->primaries_blue_xy[0] = F64FromU32(LoadBE32(payload + 24));
|
||||
color_encoding->primaries_blue_xy[1] = F64FromU32(LoadBE32(payload + 28));
|
||||
|
||||
have_chrm_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Status DecodeEXIF(const unsigned char* payload, const size_t payload_size,
|
||||
PackedMetadata* metadata) {
|
||||
// If we already have EXIF, keep the larger one.
|
||||
if (metadata->exif.size() > payload_size) return true;
|
||||
metadata->exif.resize(payload_size);
|
||||
memcpy(metadata->exif.data(), payload, payload_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
Status Decode(const Span<const uint8_t> bytes, PackedMetadata* metadata,
|
||||
JxlColorEncoding* color_encoding) {
|
||||
// Look for colorimetry and text chunks in the PNG image. The PNG chunks
|
||||
// begin after the PNG magic header of 8 bytes.
|
||||
const unsigned char* chunk = bytes.data() + 8;
|
||||
const unsigned char* end = bytes.data() + bytes.size();
|
||||
for (;;) {
|
||||
// chunk points to the first field of a PNG chunk. The chunk has
|
||||
// respectively 4 bytes of length, 4 bytes type, length bytes of data,
|
||||
// 4 bytes CRC.
|
||||
if (chunk + 4 >= end) {
|
||||
break; // Regular end reached.
|
||||
}
|
||||
|
||||
char type_char[5];
|
||||
if (chunk + 8 >= end) {
|
||||
JXL_NOTIFY_ERROR("PNG: malformed chunk");
|
||||
break;
|
||||
}
|
||||
lodepng_chunk_type(type_char, chunk);
|
||||
std::string type = type_char;
|
||||
|
||||
if (type == "acTL" || type == "fcTL" || type == "fdAT") {
|
||||
// this is an APNG file, without proper handling we would just return
|
||||
// the first frame, so for now codec_apng handles animation until the
|
||||
// animation chunk handling is added here
|
||||
return false;
|
||||
}
|
||||
if (type == "eXIf" || type == "iCCP" || type == "sRGB" ||
|
||||
type == "gAMA" || type == "cHRM") {
|
||||
const unsigned char* payload = lodepng_chunk_data_const(chunk);
|
||||
const size_t payload_size = lodepng_chunk_length(chunk);
|
||||
// The entire chunk needs also 4 bytes of CRC after the payload.
|
||||
if (payload + payload_size + 4 >= end) {
|
||||
JXL_NOTIFY_ERROR("PNG: truncated chunk");
|
||||
break;
|
||||
}
|
||||
if (lodepng_chunk_check_crc(chunk) != 0) {
|
||||
JXL_NOTIFY_ERROR("CRC mismatch in unknown PNG chunk");
|
||||
chunk = lodepng_chunk_next_const(chunk, end);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type == "eXIf") {
|
||||
JXL_RETURN_IF_ERROR(DecodeEXIF(payload, payload_size, metadata));
|
||||
} else if (type == "iCCP") {
|
||||
JXL_RETURN_IF_ERROR(DecodeICC(payload, payload_size));
|
||||
} else if (type == "sRGB") {
|
||||
JXL_RETURN_IF_ERROR(
|
||||
DecodeSRGB(payload, payload_size, color_encoding));
|
||||
} else if (type == "gAMA") {
|
||||
JXL_RETURN_IF_ERROR(
|
||||
DecodeGAMA(payload, payload_size, color_encoding));
|
||||
} else if (type == "cHRM") {
|
||||
JXL_RETURN_IF_ERROR(
|
||||
DecodeCHRM(payload, payload_size, color_encoding));
|
||||
}
|
||||
}
|
||||
|
||||
chunk = lodepng_chunk_next_const(chunk, end);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> icc_;
|
||||
|
||||
bool have_pq_ = false;
|
||||
bool have_srgb_ = false;
|
||||
bool have_gama_ = false;
|
||||
bool have_chrm_ = false;
|
||||
bool have_icc_ = false;
|
||||
};
|
||||
|
||||
// Stores ColorEncoding into PNG chunks.
|
||||
class ColorEncodingWriterPNG {
|
||||
public:
|
||||
static Status Encode(const ColorEncoding& c, LodePNGInfo* JXL_RESTRICT info) {
|
||||
// Prefer to only write sRGB - smaller.
|
||||
if (c.IsSRGB()) {
|
||||
JXL_RETURN_IF_ERROR(AddSRGB(c, info));
|
||||
// PNG recommends not including both sRGB and iCCP, so skip the latter.
|
||||
} else if (!c.HaveFields() || !c.tf.IsGamma()) {
|
||||
// Having a gamma value means that the source was a PNG with gAMA and
|
||||
// without iCCP.
|
||||
JXL_ASSERT(!c.ICC().empty());
|
||||
JXL_RETURN_IF_ERROR(AddICC(c.ICC(), info));
|
||||
}
|
||||
|
||||
// gAMA and cHRM are always allowed but will be overridden by sRGB/iCCP.
|
||||
JXL_RETURN_IF_ERROR(MaybeAddGAMA(c, info));
|
||||
JXL_RETURN_IF_ERROR(MaybeAddCHRM(c, info));
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
static Status AddChunk(const char* type, const PaddedBytes& payload,
|
||||
LodePNGInfo* JXL_RESTRICT info) {
|
||||
// Ignore original location/order of chunks; place them in the first group.
|
||||
if (lodepng_chunk_create(&info->unknown_chunks_data[0],
|
||||
&info->unknown_chunks_size[0], payload.size(),
|
||||
type, payload.data()) != 0) {
|
||||
return JXL_FAILURE("Failed to add chunk");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static Status AddICC(const PaddedBytes& icc, LodePNGInfo* JXL_RESTRICT info) {
|
||||
LodePNGCompressSettings settings;
|
||||
lodepng_compress_settings_init(&settings);
|
||||
unsigned char* out = nullptr;
|
||||
size_t out_size = 0;
|
||||
if (lodepng_zlib_compress(&out, &out_size, icc.data(), icc.size(),
|
||||
&settings) != 0) {
|
||||
return JXL_FAILURE("Failed to compress ICC");
|
||||
}
|
||||
|
||||
PaddedBytes payload;
|
||||
payload.resize(3 + out_size);
|
||||
// TODO(janwas): use special name if PQ
|
||||
payload[0] = '1'; // profile name
|
||||
payload[1] = '\0';
|
||||
payload[2] = 0; // compression method (zlib)
|
||||
memcpy(&payload[3], out, out_size);
|
||||
free(out);
|
||||
|
||||
return AddChunk("iCCP", payload, info);
|
||||
}
|
||||
|
||||
static Status AddSRGB(const ColorEncoding& c,
|
||||
LodePNGInfo* JXL_RESTRICT info) {
|
||||
PaddedBytes payload;
|
||||
payload.push_back(static_cast<uint8_t>(c.rendering_intent));
|
||||
return AddChunk("sRGB", payload, info);
|
||||
}
|
||||
|
||||
// Returns PNG encoding of floating-point value (times 10^5).
|
||||
static uint32_t U32FromF64(const double x) {
|
||||
return static_cast<int32_t>(roundf(x * 1E5));
|
||||
}
|
||||
|
||||
static Status MaybeAddGAMA(const ColorEncoding& c,
|
||||
LodePNGInfo* JXL_RESTRICT info) {
|
||||
double gamma;
|
||||
if (c.tf.IsGamma()) {
|
||||
gamma = c.tf.GetGamma();
|
||||
} else if (c.tf.IsLinear()) {
|
||||
gamma = 1;
|
||||
} else if (c.tf.IsSRGB()) {
|
||||
gamma = 0.45455;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
PaddedBytes payload(4);
|
||||
StoreBE32(U32FromF64(gamma), payload.data());
|
||||
return AddChunk("gAMA", payload, info);
|
||||
}
|
||||
|
||||
static Status MaybeAddCHRM(const ColorEncoding& c,
|
||||
LodePNGInfo* JXL_RESTRICT info) {
|
||||
CIExy white_point = c.GetWhitePoint();
|
||||
// A PNG image stores both whitepoint and primaries in the cHRM chunk, but
|
||||
// for grayscale images we don't have primaries. It does not matter what
|
||||
// values are stored in the PNG though (all colors are a multiple of the
|
||||
// whitepoint), so choose default ones. See
|
||||
// http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html section 4.2.2.1.
|
||||
PrimariesCIExy primaries =
|
||||
c.IsGray() ? ColorEncoding().GetPrimaries() : c.GetPrimaries();
|
||||
|
||||
if (c.primaries == Primaries::kSRGB && c.white_point == WhitePoint::kD65) {
|
||||
// For sRGB, the cHRM chunk is supposed to have very specific values which
|
||||
// don't quite match the pre-quantized ones we have (red is off by
|
||||
// 0.00010). Technically, this is only required for full sRGB, but for
|
||||
// consistency, we might as well use them whenever the primaries and white
|
||||
// point are sRGB's.
|
||||
white_point.x = 0.31270;
|
||||
white_point.y = 0.32900;
|
||||
primaries.r.x = 0.64000;
|
||||
primaries.r.y = 0.33000;
|
||||
primaries.g.x = 0.30000;
|
||||
primaries.g.y = 0.60000;
|
||||
primaries.b.x = 0.15000;
|
||||
primaries.b.y = 0.06000;
|
||||
}
|
||||
|
||||
PaddedBytes payload(32);
|
||||
StoreBE32(U32FromF64(white_point.x), &payload[0]);
|
||||
StoreBE32(U32FromF64(white_point.y), &payload[4]);
|
||||
StoreBE32(U32FromF64(primaries.r.x), &payload[8]);
|
||||
StoreBE32(U32FromF64(primaries.r.y), &payload[12]);
|
||||
StoreBE32(U32FromF64(primaries.g.x), &payload[16]);
|
||||
StoreBE32(U32FromF64(primaries.g.y), &payload[20]);
|
||||
StoreBE32(U32FromF64(primaries.b.x), &payload[24]);
|
||||
StoreBE32(U32FromF64(primaries.b.y), &payload[28]);
|
||||
return AddChunk("cHRM", payload, info);
|
||||
}
|
||||
};
|
||||
|
||||
// RAII - ensures state is freed even if returning early.
|
||||
struct PNGState {
|
||||
PNGState() { lodepng_state_init(&s); }
|
||||
~PNGState() { lodepng_state_cleanup(&s); }
|
||||
|
||||
LodePNGState s;
|
||||
};
|
||||
|
||||
Status CheckGray(const LodePNGColorMode& mode, bool has_icc, bool* is_gray) {
|
||||
switch (mode.colortype) {
|
||||
case LCT_GREY:
|
||||
case LCT_GREY_ALPHA:
|
||||
*is_gray = true;
|
||||
return true;
|
||||
|
||||
case LCT_RGB:
|
||||
case LCT_RGBA:
|
||||
*is_gray = false;
|
||||
return true;
|
||||
|
||||
case LCT_PALETTE: {
|
||||
if (has_icc) {
|
||||
// If an ICC profile is present, the PNG specification requires
|
||||
// palette to be interpreted as RGB colored, not grayscale, so we must
|
||||
// output color in that case and unfortunately can't optimize it to
|
||||
// gray if the palette only has gray entries.
|
||||
*is_gray = false;
|
||||
return true;
|
||||
} else {
|
||||
*is_gray = true;
|
||||
for (size_t i = 0; i < mode.palettesize; i++) {
|
||||
if (mode.palette[i * 4] != mode.palette[i * 4 + 1] ||
|
||||
mode.palette[i * 4] != mode.palette[i * 4 + 2]) {
|
||||
*is_gray = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
*is_gray = false;
|
||||
return JXL_FAILURE("Unexpected PNG color type");
|
||||
}
|
||||
}
|
||||
|
||||
Status CheckAlpha(const LodePNGColorMode& mode, bool* has_alpha) {
|
||||
if (mode.key_defined) {
|
||||
// Color key marks a single color as transparent.
|
||||
*has_alpha = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (mode.colortype) {
|
||||
case LCT_GREY:
|
||||
case LCT_RGB:
|
||||
*has_alpha = false;
|
||||
return true;
|
||||
|
||||
case LCT_GREY_ALPHA:
|
||||
case LCT_RGBA:
|
||||
*has_alpha = true;
|
||||
return true;
|
||||
|
||||
case LCT_PALETTE: {
|
||||
*has_alpha = false;
|
||||
for (size_t i = 0; i < mode.palettesize; i++) {
|
||||
// PNG palettes are always 8-bit.
|
||||
if (mode.palette[i * 4 + 3] != 255) {
|
||||
*has_alpha = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
*has_alpha = false;
|
||||
return JXL_FAILURE("Unexpected PNG color type");
|
||||
}
|
||||
}
|
||||
|
||||
LodePNGColorType MakeType(const bool is_gray, const bool has_alpha) {
|
||||
if (is_gray) {
|
||||
return has_alpha ? LCT_GREY_ALPHA : LCT_GREY;
|
||||
}
|
||||
return has_alpha ? LCT_RGBA : LCT_RGB;
|
||||
}
|
||||
|
||||
// Inspects first chunk of the given type and updates state with the information
|
||||
// when the chunk is relevant and present in the file.
|
||||
Status InspectChunkType(const Span<const uint8_t> bytes,
|
||||
const std::string& type, LodePNGState* state) {
|
||||
const unsigned char* chunk = lodepng_chunk_find_const(
|
||||
bytes.data(), bytes.data() + bytes.size(), type.c_str());
|
||||
if (chunk && lodepng_inspect_chunk(state, chunk - bytes.data(), bytes.data(),
|
||||
bytes.size()) != 0) {
|
||||
return JXL_FAILURE("Invalid chunk \"%s\" in PNG image", type.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeImagePNG(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
unsigned w, h;
|
||||
PNGState state;
|
||||
if (lodepng_inspect(&w, &h, &state.s, bytes.data(), bytes.size()) != 0) {
|
||||
return false; // not an error - just wrong format
|
||||
}
|
||||
JXL_RETURN_IF_ERROR(VerifyDimensions(&constraints, w, h));
|
||||
|
||||
// Palette RGB values
|
||||
if (!InspectChunkType(bytes, "PLTE", &state.s)) {
|
||||
return false;
|
||||
}
|
||||
// Transparent color key, or palette transparency
|
||||
if (!InspectChunkType(bytes, "tRNS", &state.s)) {
|
||||
return false;
|
||||
}
|
||||
// ICC profile
|
||||
if (!InspectChunkType(bytes, "iCCP", &state.s)) {
|
||||
return false;
|
||||
}
|
||||
const LodePNGColorMode& color_mode = state.s.info_png.color;
|
||||
bool has_icc = state.s.info_png.iccp_defined;
|
||||
|
||||
bool is_gray, has_alpha;
|
||||
JXL_RETURN_IF_ERROR(CheckGray(color_mode, has_icc, &is_gray));
|
||||
JXL_RETURN_IF_ERROR(CheckAlpha(color_mode, &has_alpha));
|
||||
// We want LodePNG to promote 1/2/4 bit pixels to 8.
|
||||
size_t bits_per_sample = std::max(color_mode.bitdepth, 8u);
|
||||
if (bits_per_sample != 8 && bits_per_sample != 16) {
|
||||
return JXL_FAILURE("Unexpected PNG bit depth");
|
||||
}
|
||||
|
||||
// Always decode to 8/16-bit RGB/RGBA, not LCT_PALETTE.
|
||||
state.s.info_raw.bitdepth = static_cast<unsigned>(bits_per_sample);
|
||||
state.s.info_raw.colortype = MakeType(is_gray, has_alpha);
|
||||
unsigned char* out = nullptr;
|
||||
const unsigned err =
|
||||
lodepng_decode(&out, &w, &h, &state.s, bytes.data(), bytes.size());
|
||||
// Automatically call free(out) on return.
|
||||
std::unique_ptr<unsigned char, void (*)(void*)> out_ptr{out, free};
|
||||
if (err != 0) {
|
||||
return JXL_FAILURE("PNG decode failed: %s", lodepng_error_text(err));
|
||||
}
|
||||
|
||||
if (!BlobsReaderPNG::Decode(state.s.info_png, &ppf->metadata)) {
|
||||
JXL_WARNING("PNG metadata may be incomplete");
|
||||
}
|
||||
ColorEncodingReaderPNG reader;
|
||||
JXL_RETURN_IF_ERROR(reader(bytes, is_gray, ppf));
|
||||
|
||||
const uint32_t num_channels = (is_gray ? 1 : 3) + has_alpha;
|
||||
|
||||
ppf->info.xsize = w;
|
||||
ppf->info.ysize = h;
|
||||
// Original data is uint, so exponent_bits_per_sample = 0.
|
||||
ppf->info.bits_per_sample = bits_per_sample;
|
||||
ppf->info.exponent_bits_per_sample = 0;
|
||||
ppf->info.uses_original_profile = true;
|
||||
ppf->info.have_preview = false;
|
||||
ppf->info.have_animation = false;
|
||||
|
||||
ppf->info.alpha_bits = has_alpha ? bits_per_sample : 0;
|
||||
ppf->info.num_color_channels = is_gray ? 1 : 3;
|
||||
|
||||
const JxlPixelFormat format{
|
||||
/*num_channels=*/num_channels,
|
||||
/*data_type=*/bits_per_sample == 16 ? JXL_TYPE_UINT16 : JXL_TYPE_UINT8,
|
||||
/*endianness=*/JXL_BIG_ENDIAN, // PNG requirement
|
||||
/*align=*/0,
|
||||
};
|
||||
const size_t out_size = static_cast<size_t>(w) * h * num_channels *
|
||||
bits_per_sample / kBitsPerByte;
|
||||
ppf->frames.emplace_back(w, h, format, out_ptr.release(), out_size);
|
||||
|
||||
JXL_RETURN_IF_ERROR(
|
||||
ApplyColorHints(color_hints, reader.HaveColorProfile(), is_gray, ppf));
|
||||
return true;
|
||||
}
|
||||
|
||||
Status EncodeImagePNG(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||
size_t bits_per_sample, ThreadPool* pool,
|
||||
PaddedBytes* bytes) {
|
||||
if (bits_per_sample > 8) {
|
||||
bits_per_sample = 16;
|
||||
} else if (bits_per_sample < 8) {
|
||||
// PNG can also do 4, 2, and 1 bits per sample, but it isn't implemented
|
||||
bits_per_sample = 8;
|
||||
}
|
||||
ImageBundle ib = io->Main().Copy();
|
||||
const size_t alpha_bits = ib.HasAlpha() ? bits_per_sample : 0;
|
||||
ImageMetadata metadata = io->metadata.m;
|
||||
ImageBundle store(&metadata);
|
||||
const ImageBundle* transformed;
|
||||
JXL_RETURN_IF_ERROR(
|
||||
TransformIfNeeded(ib, c_desired, pool, &store, &transformed));
|
||||
size_t stride = ib.oriented_xsize() *
|
||||
DivCeil(c_desired.Channels() * bits_per_sample + alpha_bits,
|
||||
kBitsPerByte);
|
||||
PaddedBytes raw_bytes(stride * ib.oriented_ysize());
|
||||
JXL_RETURN_IF_ERROR(ConvertToExternal(
|
||||
*transformed, bits_per_sample, /*float_out=*/false,
|
||||
c_desired.Channels() + (ib.HasAlpha() ? 1 : 0), JXL_BIG_ENDIAN, stride,
|
||||
pool, raw_bytes.data(), raw_bytes.size(), /*out_callback=*/nullptr,
|
||||
/*out_opaque=*/nullptr, metadata.GetOrientation()));
|
||||
|
||||
PNGState state;
|
||||
// For maximum compatibility, still store 8-bit even if pixels are all zero.
|
||||
state.s.encoder.auto_convert = 0;
|
||||
|
||||
LodePNGInfo* info = &state.s.info_png;
|
||||
info->color.bitdepth = bits_per_sample;
|
||||
info->color.colortype = MakeType(ib.IsGray(), ib.HasAlpha());
|
||||
state.s.info_raw = info->color;
|
||||
|
||||
JXL_RETURN_IF_ERROR(ColorEncodingWriterPNG::Encode(c_desired, info));
|
||||
JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(io->blobs, info));
|
||||
|
||||
unsigned char* out = nullptr;
|
||||
size_t out_size = 0;
|
||||
const unsigned err =
|
||||
lodepng_encode(&out, &out_size, raw_bytes.data(), ib.oriented_xsize(),
|
||||
ib.oriented_ysize(), &state.s);
|
||||
// Automatically call free(out) on return.
|
||||
std::unique_ptr<unsigned char, void (*)(void*)> out_ptr{out, free};
|
||||
if (err != 0) {
|
||||
return JXL_FAILURE("Failed to encode PNG: %s", lodepng_error_text(err));
|
||||
}
|
||||
bytes->resize(out_size);
|
||||
memcpy(bytes->data(), out, out_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
|
@ -11,7 +11,7 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/extras/color_hints.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"
|
||||
|
@ -23,7 +23,7 @@ namespace extras {
|
|||
|
||||
// Decodes `bytes` into `io`.
|
||||
Status DecodeImagePSD(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints, ThreadPool* pool,
|
||||
const extras::ColorHints& color_hints, ThreadPool* pool,
|
||||
CodecInOut* io);
|
||||
|
||||
// Not implemented yet
|
||||
|
|
|
@ -13,16 +13,16 @@
|
|||
#include <vector>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "lib/extras/codec_pgx.h"
|
||||
#include "lib/extras/codec_pnm.h"
|
||||
#include "lib/extras/dec/pgx.h"
|
||||
#include "lib/extras/dec/pnm.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
#include "lib/jxl/base/random.h"
|
||||
#include "lib/jxl/base/thread_pool_internal.h"
|
||||
#include "lib/jxl/color_management.h"
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
#include "lib/jxl/image.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
#include "lib/jxl/image_test_utils.h"
|
||||
#include "lib/jxl/luminance.h"
|
||||
#include "lib/jxl/testdata.h"
|
||||
|
||||
namespace jxl {
|
||||
|
@ -106,7 +106,6 @@ void TestRoundTrip(Codec codec, const size_t xsize, const size_t ysize,
|
|||
|
||||
CodecInOut io2;
|
||||
ColorHints color_hints;
|
||||
io2.target_nits = io.metadata.m.IntensityTarget();
|
||||
// Only for PNM because PNG will warn about ignoring them.
|
||||
if (codec == Codec::kPNM) {
|
||||
color_hints.Add("color_space", Description(c_external));
|
||||
|
@ -128,7 +127,7 @@ void TestRoundTrip(Codec codec, const size_t xsize, const size_t ysize,
|
|||
EXPECT_TRUE(SamePixels(ib1.alpha(), *ib2.alpha()));
|
||||
}
|
||||
|
||||
JXL_CHECK(ib2.TransformTo(ib1.c_current(), pool));
|
||||
JXL_CHECK(ib2.TransformTo(ib1.c_current(), GetJxlCms(), pool));
|
||||
|
||||
double max_l1, max_rel;
|
||||
// Round-trip tolerances must be higher than in external_image_test because
|
||||
|
@ -184,8 +183,7 @@ TEST(CodecTest, TestRoundTrip) {
|
|||
}
|
||||
#endif
|
||||
|
||||
CodecInOut DecodeRoundtrip(const std::string& pathname, Codec expected_codec,
|
||||
ThreadPool* pool,
|
||||
CodecInOut DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
|
||||
const ColorHints& color_hints = ColorHints()) {
|
||||
CodecInOut io;
|
||||
const PaddedBytes orig = ReadTestData(pathname);
|
||||
|
@ -195,7 +193,7 @@ CodecInOut DecodeRoundtrip(const std::string& pathname, Codec expected_codec,
|
|||
|
||||
// Encode/Decode again to make sure Encode carries through all metadata.
|
||||
PaddedBytes encoded;
|
||||
JXL_CHECK(Encode(io, expected_codec, io.metadata.m.color_encoding,
|
||||
JXL_CHECK(Encode(io, Codec::kPNG, io.metadata.m.color_encoding,
|
||||
io.metadata.m.bit_depth.bits_per_sample, &encoded, pool));
|
||||
|
||||
CodecInOut io2;
|
||||
|
@ -345,7 +343,7 @@ TEST(CodecTest, TestPNGSuite) {
|
|||
|
||||
void VerifyWideGamutMetadata(const std::string& relative_pathname,
|
||||
const Primaries primaries, ThreadPool* pool) {
|
||||
const CodecInOut io = DecodeRoundtrip(relative_pathname, Codec::kPNG, pool);
|
||||
const CodecInOut io = DecodeRoundtrip(relative_pathname, pool);
|
||||
|
||||
EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample);
|
||||
EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample);
|
||||
|
|
|
@ -0,0 +1,712 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/dec/apng.h"
|
||||
|
||||
// Parts of this code are taken from apngdis, which has the following license:
|
||||
/* APNG Disassembler 2.8
|
||||
*
|
||||
* Deconstructs APNG files into individual frames.
|
||||
*
|
||||
* http://apngdis.sourceforge.net
|
||||
*
|
||||
* Copyright (c) 2010-2015 Max Stepin
|
||||
* maxst at users.sourceforge.net
|
||||
*
|
||||
* zlib license
|
||||
* ------------
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/encode.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
#include "lib/jxl/common.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
#include "png.h" /* original (unpatched) libpng is ok */
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
namespace {
|
||||
|
||||
// Returns floating-point value from the PNG encoding (times 10^5).
|
||||
static double F64FromU32(const uint32_t x) {
|
||||
return static_cast<int32_t>(x) * 1E-5;
|
||||
}
|
||||
|
||||
Status DecodeSRGB(const unsigned char* payload, const size_t payload_size,
|
||||
JxlColorEncoding* color_encoding) {
|
||||
if (payload_size != 1) return JXL_FAILURE("Wrong sRGB size");
|
||||
// (PNG uses the same values as ICC.)
|
||||
if (payload[0] >= 4) return JXL_FAILURE("Invalid Rendering Intent");
|
||||
color_encoding->rendering_intent =
|
||||
static_cast<JxlRenderingIntent>(payload[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
Status DecodeGAMA(const unsigned char* payload, const size_t payload_size,
|
||||
JxlColorEncoding* color_encoding) {
|
||||
if (payload_size != 4) return JXL_FAILURE("Wrong gAMA size");
|
||||
color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
|
||||
color_encoding->gamma = F64FromU32(LoadBE32(payload));
|
||||
return true;
|
||||
}
|
||||
|
||||
Status DecodeCHRM(const unsigned char* payload, const size_t payload_size,
|
||||
JxlColorEncoding* color_encoding) {
|
||||
if (payload_size != 32) return JXL_FAILURE("Wrong cHRM size");
|
||||
|
||||
color_encoding->white_point = JXL_WHITE_POINT_CUSTOM;
|
||||
color_encoding->white_point_xy[0] = F64FromU32(LoadBE32(payload + 0));
|
||||
color_encoding->white_point_xy[1] = F64FromU32(LoadBE32(payload + 4));
|
||||
|
||||
color_encoding->primaries = JXL_PRIMARIES_CUSTOM;
|
||||
color_encoding->primaries_red_xy[0] = F64FromU32(LoadBE32(payload + 8));
|
||||
color_encoding->primaries_red_xy[1] = F64FromU32(LoadBE32(payload + 12));
|
||||
color_encoding->primaries_green_xy[0] = F64FromU32(LoadBE32(payload + 16));
|
||||
color_encoding->primaries_green_xy[1] = F64FromU32(LoadBE32(payload + 20));
|
||||
color_encoding->primaries_blue_xy[0] = F64FromU32(LoadBE32(payload + 24));
|
||||
color_encoding->primaries_blue_xy[1] = F64FromU32(LoadBE32(payload + 28));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Retrieves XMP and EXIF/IPTC from itext and text.
|
||||
class BlobsReaderPNG {
|
||||
public:
|
||||
static Status Decode(const png_text_struct& info, PackedMetadata* metadata) {
|
||||
// We trust these are properly null-terminated by libpng.
|
||||
const char* key = info.key;
|
||||
const char* value = info.text;
|
||||
if (strstr(key, "XML:com.adobe.xmp")) {
|
||||
metadata->xmp.resize(strlen(value)); // safe, see above
|
||||
memcpy(metadata->xmp.data(), value, metadata->xmp.size());
|
||||
}
|
||||
|
||||
std::string type;
|
||||
std::vector<uint8_t> bytes;
|
||||
|
||||
// Handle text chunks annotated with key "Raw profile type ####", with
|
||||
// #### a type, which may contain metadata.
|
||||
const char* kKey = "Raw profile type ";
|
||||
if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
|
||||
|
||||
if (!MaybeDecodeBase16(key, value, &type, &bytes)) {
|
||||
JXL_WARNING("Couldn't parse 'Raw format type' text chunk");
|
||||
return false;
|
||||
}
|
||||
if (type == "exif") {
|
||||
if (!metadata->exif.empty()) {
|
||||
JXL_WARNING("overwriting EXIF (%" PRIuS " bytes) with base16 (%" PRIuS
|
||||
" bytes)",
|
||||
metadata->exif.size(), bytes.size());
|
||||
}
|
||||
metadata->exif = std::move(bytes);
|
||||
} else if (type == "iptc") {
|
||||
// TODO (jon): Deal with IPTC in some way
|
||||
} else if (type == "8bim") {
|
||||
// TODO (jon): Deal with 8bim in some way
|
||||
} else if (type == "xmp") {
|
||||
if (!metadata->xmp.empty()) {
|
||||
JXL_WARNING("overwriting XMP (%" PRIuS " bytes) with base16 (%" PRIuS
|
||||
" bytes)",
|
||||
metadata->xmp.size(), bytes.size());
|
||||
}
|
||||
metadata->xmp = std::move(bytes);
|
||||
} else {
|
||||
JXL_WARNING("Unknown type in 'Raw format type' text chunk: %s: %" PRIuS
|
||||
" bytes",
|
||||
type.c_str(), bytes.size());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Returns false if invalid.
|
||||
static JXL_INLINE Status DecodeNibble(const char c,
|
||||
uint32_t* JXL_RESTRICT nibble) {
|
||||
if ('a' <= c && c <= 'f') {
|
||||
*nibble = 10 + c - 'a';
|
||||
} else if ('0' <= c && c <= '9') {
|
||||
*nibble = c - '0';
|
||||
} else {
|
||||
*nibble = 0;
|
||||
return JXL_FAILURE("Invalid metadata nibble");
|
||||
}
|
||||
JXL_ASSERT(*nibble < 16);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parses a PNG text chunk with key of the form "Raw profile type ####", with
|
||||
// #### a type.
|
||||
// Returns whether it could successfully parse the content.
|
||||
// We trust key and encoded are null-terminated because they come from
|
||||
// libpng.
|
||||
static Status MaybeDecodeBase16(const char* key, const char* encoded,
|
||||
std::string* type,
|
||||
std::vector<uint8_t>* bytes) {
|
||||
const char* encoded_end = encoded + strlen(encoded);
|
||||
|
||||
const char* kKey = "Raw profile type ";
|
||||
if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
|
||||
*type = key + strlen(kKey);
|
||||
const size_t kMaxTypeLen = 20;
|
||||
if (type->length() > kMaxTypeLen) return false; // Type too long
|
||||
|
||||
// Header: freeform string and number of bytes
|
||||
// Expected format is:
|
||||
// \n
|
||||
// profile name/description\n
|
||||
// 40\n (the number of bytes after hex-decoding)
|
||||
// 01234566789abcdef....\n (72 bytes per line max).
|
||||
// 012345667\n (last line)
|
||||
const char* pos = encoded;
|
||||
|
||||
if (*(pos++) != '\n') return false;
|
||||
while (pos < encoded_end && *pos != '\n') {
|
||||
pos++;
|
||||
}
|
||||
if (pos == encoded_end) return false;
|
||||
// We parsed so far a \n, some number of non \n characters and are now
|
||||
// pointing at a \n.
|
||||
if (*(pos++) != '\n') return false;
|
||||
unsigned long bytes_to_decode;
|
||||
const int fields = sscanf(pos, "%8lu", &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.
|
||||
const unsigned long needed_bytes =
|
||||
bytes_to_decode * 2 + 1 + DivCeil(bytes_to_decode, 36);
|
||||
if (needed_bytes != static_cast<size_t>(encoded_end - pos)) {
|
||||
return JXL_FAILURE("Not enough bytes to parse %lu bytes in hex",
|
||||
bytes_to_decode);
|
||||
}
|
||||
JXL_ASSERT(bytes->empty());
|
||||
bytes->reserve(bytes_to_decode);
|
||||
|
||||
// Encoding: base16 with newline after 72 chars.
|
||||
// pos points to the \n before the first line of hex values.
|
||||
for (size_t i = 0; i < bytes_to_decode; ++i) {
|
||||
if (i % 36 == 0) {
|
||||
if (pos + 1 >= encoded_end) return false; // Truncated base16 1
|
||||
if (*pos != '\n') return false; // Expected newline
|
||||
++pos;
|
||||
}
|
||||
|
||||
if (pos + 2 >= encoded_end) return false; // Truncated base16 2;
|
||||
uint32_t nibble0, nibble1;
|
||||
JXL_RETURN_IF_ERROR(DecodeNibble(pos[0], &nibble0));
|
||||
JXL_RETURN_IF_ERROR(DecodeNibble(pos[1], &nibble1));
|
||||
bytes->push_back(static_cast<uint8_t>((nibble0 << 4) + nibble1));
|
||||
pos += 2;
|
||||
}
|
||||
if (pos + 1 != encoded_end) return false; // Too many encoded bytes
|
||||
if (pos[0] != '\n') return false; // Incorrect metadata terminator
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr bool isAbc(char c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
||||
}
|
||||
|
||||
constexpr uint32_t kId_IHDR = 0x52444849;
|
||||
constexpr uint32_t kId_acTL = 0x4C546361;
|
||||
constexpr uint32_t kId_fcTL = 0x4C546366;
|
||||
constexpr uint32_t kId_IDAT = 0x54414449;
|
||||
constexpr uint32_t kId_fdAT = 0x54416466;
|
||||
constexpr uint32_t kId_IEND = 0x444E4549;
|
||||
constexpr uint32_t kId_iCCP = 0x50434369;
|
||||
constexpr uint32_t kId_sRGB = 0x42475273;
|
||||
constexpr uint32_t kId_gAMA = 0x414D4167;
|
||||
constexpr uint32_t kId_cHRM = 0x4D524863;
|
||||
constexpr uint32_t kId_eXIf = 0x66495865;
|
||||
|
||||
struct APNGFrame {
|
||||
PaddedBytes pixels;
|
||||
std::vector<uint8_t*> rows;
|
||||
unsigned int w, h, delay_num, delay_den;
|
||||
};
|
||||
|
||||
struct Reader {
|
||||
const uint8_t* next;
|
||||
const uint8_t* last;
|
||||
bool Read(void* data, size_t len) {
|
||||
size_t cap = last - next;
|
||||
size_t to_copy = std::min(cap, len);
|
||||
memcpy(data, next, to_copy);
|
||||
next += to_copy;
|
||||
return (len == to_copy);
|
||||
}
|
||||
bool Eof() { return next == last; }
|
||||
};
|
||||
|
||||
const unsigned long cMaxPNGSize = 1000000UL;
|
||||
const size_t kMaxPNGChunkSize = 100000000; // 100 MB
|
||||
|
||||
void info_fn(png_structp png_ptr, png_infop info_ptr) {
|
||||
png_set_expand(png_ptr);
|
||||
png_set_palette_to_rgb(png_ptr);
|
||||
png_set_tRNS_to_alpha(png_ptr);
|
||||
(void)png_set_interlace_handling(png_ptr);
|
||||
png_read_update_info(png_ptr, info_ptr);
|
||||
}
|
||||
|
||||
void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num,
|
||||
int pass) {
|
||||
APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr);
|
||||
JXL_CHECK(frame);
|
||||
JXL_CHECK(frame->rows[row_num] < frame->pixels.data() + frame->pixels.size());
|
||||
png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row);
|
||||
}
|
||||
|
||||
inline unsigned int read_chunk(Reader* r, PaddedBytes* pChunk) {
|
||||
unsigned char len[4];
|
||||
if (r->Read(&len, 4)) {
|
||||
const auto size = png_get_uint_32(len);
|
||||
// Check first, to avoid overflow.
|
||||
if (size > kMaxPNGChunkSize) {
|
||||
JXL_WARNING("APNG chunk size is too big");
|
||||
return 0;
|
||||
}
|
||||
pChunk->resize(size + 12);
|
||||
memcpy(pChunk->data(), len, 4);
|
||||
if (r->Read(pChunk->data() + 4, pChunk->size() - 4)) {
|
||||
return LoadLE32(pChunk->data() + 4);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr,
|
||||
bool hasInfo, PaddedBytes& chunkIHDR,
|
||||
std::vector<PaddedBytes>& chunksInfo) {
|
||||
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);
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!png_ptr || !info_ptr) return 1;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
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_process_data(png_ptr, info_ptr, header, 8);
|
||||
png_process_data(png_ptr, info_ptr, chunkIHDR.data(), chunkIHDR.size());
|
||||
|
||||
if (hasInfo) {
|
||||
for (unsigned int i = 0; i < chunksInfo.size(); i++) {
|
||||
png_process_data(png_ptr, info_ptr, chunksInfo[i].data(),
|
||||
chunksInfo[i].size());
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int processing_data(png_structp png_ptr, png_infop info_ptr, unsigned char* p,
|
||||
unsigned int size) {
|
||||
if (!png_ptr || !info_ptr) return 1;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
png_process_data(png_ptr, info_ptr, p, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int processing_finish(png_structp png_ptr, png_infop info_ptr,
|
||||
PackedMetadata* metadata) {
|
||||
unsigned char footer[12] = {0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130};
|
||||
|
||||
if (!png_ptr || !info_ptr) return 1;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
png_process_data(png_ptr, info_ptr, footer, 12);
|
||||
// before destroying: check if we encountered any metadata chunks
|
||||
png_textp text_ptr;
|
||||
int num_text;
|
||||
png_get_text(png_ptr, info_ptr, &text_ptr, &num_text);
|
||||
for (int i = 0; i < num_text; i++) {
|
||||
(void)BlobsReaderPNG::Decode(text_ptr[i], metadata);
|
||||
}
|
||||
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
Reader r;
|
||||
unsigned int id, j, w, h, w0, h0, x0, y0;
|
||||
unsigned int delay_num, delay_den, dop, bop, rowbytes, imagesize;
|
||||
unsigned char sig[8];
|
||||
png_structp png_ptr;
|
||||
png_infop info_ptr;
|
||||
PaddedBytes chunk;
|
||||
PaddedBytes chunkIHDR;
|
||||
std::vector<PaddedBytes> chunksInfo;
|
||||
bool isAnimated = false;
|
||||
bool skipFirst = false;
|
||||
bool hasInfo = false;
|
||||
bool all_dispose_bg = true;
|
||||
APNGFrame frameRaw = {};
|
||||
uint32_t num_channels;
|
||||
JxlPixelFormat format;
|
||||
unsigned int bytes_per_pixel = 0;
|
||||
|
||||
r = {bytes.data(), bytes.data() + bytes.size()};
|
||||
// Not a PNG => not an error
|
||||
unsigned char png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10};
|
||||
if (!r.Read(sig, 8) || memcmp(sig, png_signature, 8) != 0) {
|
||||
return false;
|
||||
}
|
||||
id = read_chunk(&r, &chunkIHDR);
|
||||
|
||||
ppf->info.exponent_bits_per_sample = 0;
|
||||
ppf->info.alpha_exponent_bits = 0;
|
||||
ppf->info.orientation = JXL_ORIENT_IDENTITY;
|
||||
|
||||
ppf->frames.clear();
|
||||
|
||||
bool have_color = false, have_srgb = false;
|
||||
bool errorstate = true;
|
||||
if (id == kId_IHDR && chunkIHDR.size() == 25) {
|
||||
x0 = 0;
|
||||
y0 = 0;
|
||||
delay_num = 1;
|
||||
delay_den = 10;
|
||||
dop = 0;
|
||||
bop = 0;
|
||||
|
||||
w0 = w = png_get_uint_32(chunkIHDR.data() + 8);
|
||||
h0 = h = png_get_uint_32(chunkIHDR.data() + 12);
|
||||
if (w > cMaxPNGSize || h > cMaxPNGSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// default settings in case e.g. only gAMA is given
|
||||
ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
|
||||
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
|
||||
ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
|
||||
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
|
||||
|
||||
if (!processing_start(png_ptr, info_ptr, (void*)&frameRaw, hasInfo,
|
||||
chunkIHDR, chunksInfo)) {
|
||||
bool last_base_was_none = true;
|
||||
while (!r.Eof()) {
|
||||
id = read_chunk(&r, &chunk);
|
||||
if (!id) break;
|
||||
|
||||
if (id == kId_acTL && !hasInfo && !isAnimated) {
|
||||
isAnimated = true;
|
||||
skipFirst = true;
|
||||
ppf->info.have_animation = true;
|
||||
ppf->info.animation.tps_numerator = 1000;
|
||||
ppf->info.animation.tps_denominator = 1;
|
||||
} else if (id == kId_IEND ||
|
||||
(id == kId_fcTL && (!hasInfo || isAnimated))) {
|
||||
if (hasInfo) {
|
||||
if (!processing_finish(png_ptr, info_ptr, &ppf->metadata)) {
|
||||
// Allocates the frame buffer.
|
||||
ppf->frames.emplace_back(w0, h0, format);
|
||||
auto* frame = &ppf->frames.back();
|
||||
|
||||
frame->frame_info.duration = delay_num * 1000 / delay_den;
|
||||
frame->x0 = x0;
|
||||
frame->y0 = y0;
|
||||
// TODO(veluca): this could in principle be implemented.
|
||||
if (last_base_was_none && !all_dispose_bg &&
|
||||
(x0 != 0 || y0 != 0 || w0 != w || h0 != h || bop != 0)) {
|
||||
return JXL_FAILURE(
|
||||
"APNG with dispose-to-0 is not supported for non-full or "
|
||||
"blended frames");
|
||||
}
|
||||
switch (dop) {
|
||||
case 0:
|
||||
frame->use_for_next_frame = true;
|
||||
last_base_was_none = false;
|
||||
all_dispose_bg = false;
|
||||
break;
|
||||
case 2:
|
||||
frame->use_for_next_frame = false;
|
||||
all_dispose_bg = false;
|
||||
break;
|
||||
default:
|
||||
frame->use_for_next_frame = false;
|
||||
last_base_was_none = true;
|
||||
}
|
||||
frame->blend = bop != 0;
|
||||
|
||||
for (size_t y = 0; y < h0; ++y) {
|
||||
memcpy(static_cast<uint8_t*>(frame->color.pixels()) +
|
||||
frame->color.stride * y,
|
||||
frameRaw.rows[y], bytes_per_pixel * w0);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (id == kId_IEND) {
|
||||
errorstate = false;
|
||||
break;
|
||||
}
|
||||
// At this point the old frame is done. Let's start a new one.
|
||||
w0 = png_get_uint_32(chunk.data() + 12);
|
||||
h0 = png_get_uint_32(chunk.data() + 16);
|
||||
x0 = png_get_uint_32(chunk.data() + 20);
|
||||
y0 = png_get_uint_32(chunk.data() + 24);
|
||||
delay_num = png_get_uint_16(chunk.data() + 28);
|
||||
delay_den = png_get_uint_16(chunk.data() + 30);
|
||||
dop = chunk[32];
|
||||
bop = chunk[33];
|
||||
|
||||
if (!delay_den) delay_den = 100;
|
||||
|
||||
if (w0 > cMaxPNGSize || h0 > cMaxPNGSize || x0 > cMaxPNGSize ||
|
||||
y0 > cMaxPNGSize || x0 + w0 > w || y0 + h0 > h || dop > 2 ||
|
||||
bop > 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasInfo) {
|
||||
memcpy(chunkIHDR.data() + 8, chunk.data() + 12, 8);
|
||||
if (processing_start(png_ptr, info_ptr, (void*)&frameRaw, hasInfo,
|
||||
chunkIHDR, chunksInfo)) {
|
||||
break;
|
||||
}
|
||||
|
||||
} else
|
||||
skipFirst = false;
|
||||
|
||||
if (ppf->frames.size() == (skipFirst ? 1 : 0)) {
|
||||
bop = 0;
|
||||
if (dop == 2) dop = 1;
|
||||
}
|
||||
} else if (id == kId_IDAT) {
|
||||
// First IDAT chunk means we now have all header info
|
||||
hasInfo = true;
|
||||
JXL_CHECK(w == png_get_image_width(png_ptr, info_ptr));
|
||||
JXL_CHECK(h == png_get_image_height(png_ptr, info_ptr));
|
||||
int colortype = png_get_color_type(png_ptr, info_ptr);
|
||||
ppf->info.bits_per_sample = png_get_bit_depth(png_ptr, info_ptr);
|
||||
png_color_8p sigbits = NULL;
|
||||
png_get_sBIT(png_ptr, info_ptr, &sigbits);
|
||||
if (colortype & 1) {
|
||||
// palette will actually be 8-bit regardless of the index bitdepth
|
||||
ppf->info.bits_per_sample = 8;
|
||||
}
|
||||
if (colortype & 2) {
|
||||
ppf->info.num_color_channels = 3;
|
||||
ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
|
||||
if (sigbits && sigbits->red == sigbits->green &&
|
||||
sigbits->green == sigbits->blue)
|
||||
ppf->info.bits_per_sample = sigbits->red;
|
||||
} else {
|
||||
ppf->info.num_color_channels = 1;
|
||||
ppf->color_encoding.color_space = JXL_COLOR_SPACE_GRAY;
|
||||
if (sigbits) ppf->info.bits_per_sample = sigbits->gray;
|
||||
}
|
||||
if (colortype & 4 ||
|
||||
png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
|
||||
ppf->info.alpha_bits = ppf->info.bits_per_sample;
|
||||
if (sigbits) ppf->info.alpha_bits = sigbits->alpha;
|
||||
} else {
|
||||
ppf->info.alpha_bits = 0;
|
||||
}
|
||||
ppf->color_encoding.color_space =
|
||||
(ppf->info.num_color_channels == 1 ? JXL_COLOR_SPACE_GRAY
|
||||
: JXL_COLOR_SPACE_RGB);
|
||||
ppf->info.xsize = w;
|
||||
ppf->info.ysize = h;
|
||||
JXL_RETURN_IF_ERROR(VerifyDimensions(&constraints, w, h));
|
||||
num_channels =
|
||||
ppf->info.num_color_channels + (ppf->info.alpha_bits ? 1 : 0);
|
||||
format = {
|
||||
/*num_channels=*/num_channels,
|
||||
/*data_type=*/ppf->info.bits_per_sample > 8 ? JXL_TYPE_UINT16
|
||||
: JXL_TYPE_UINT8,
|
||||
/*endianness=*/JXL_BIG_ENDIAN,
|
||||
/*align=*/0,
|
||||
};
|
||||
bytes_per_pixel =
|
||||
num_channels * (format.data_type == JXL_TYPE_UINT16 ? 2 : 1);
|
||||
rowbytes = w * bytes_per_pixel;
|
||||
imagesize = h * rowbytes;
|
||||
frameRaw.pixels.resize(imagesize);
|
||||
frameRaw.rows.resize(h);
|
||||
for (j = 0; j < h; j++)
|
||||
frameRaw.rows[j] = frameRaw.pixels.data() + j * rowbytes;
|
||||
|
||||
if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
|
||||
break;
|
||||
}
|
||||
} else if (id == kId_fdAT && isAnimated) {
|
||||
png_save_uint_32(chunk.data() + 4, chunk.size() - 16);
|
||||
memcpy(chunk.data() + 8, "IDAT", 4);
|
||||
if (processing_data(png_ptr, info_ptr, chunk.data() + 4,
|
||||
chunk.size() - 4)) {
|
||||
break;
|
||||
}
|
||||
} else if (id == kId_iCCP) {
|
||||
if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
|
||||
JXL_WARNING("Corrupt iCCP chunk");
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO(jon): catch special case of PQ and synthesize color encoding
|
||||
// in that case
|
||||
int compression_type;
|
||||
png_bytep profile;
|
||||
png_charp name;
|
||||
png_uint_32 proflen;
|
||||
png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, &profile,
|
||||
&proflen);
|
||||
ppf->icc.resize(proflen);
|
||||
memcpy(ppf->icc.data(), profile, proflen);
|
||||
have_color = true;
|
||||
} else if (id == kId_sRGB) {
|
||||
JXL_RETURN_IF_ERROR(DecodeSRGB(chunk.data() + 8, chunk.size() - 12,
|
||||
&ppf->color_encoding));
|
||||
have_srgb = true;
|
||||
have_color = true;
|
||||
} else if (id == kId_gAMA) {
|
||||
JXL_RETURN_IF_ERROR(DecodeGAMA(chunk.data() + 8, chunk.size() - 12,
|
||||
&ppf->color_encoding));
|
||||
have_color = true;
|
||||
} else if (id == kId_cHRM) {
|
||||
JXL_RETURN_IF_ERROR(DecodeCHRM(chunk.data() + 8, chunk.size() - 12,
|
||||
&ppf->color_encoding));
|
||||
have_color = true;
|
||||
} else if (id == kId_eXIf) {
|
||||
ppf->metadata.exif.resize(chunk.size() - 12);
|
||||
memcpy(ppf->metadata.exif.data(), chunk.data() + 8,
|
||||
chunk.size() - 12);
|
||||
} else if (!isAbc(chunk[4]) || !isAbc(chunk[5]) || !isAbc(chunk[6]) ||
|
||||
!isAbc(chunk[7])) {
|
||||
break;
|
||||
} else {
|
||||
if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
|
||||
break;
|
||||
}
|
||||
if (!hasInfo) {
|
||||
chunksInfo.push_back(chunk);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (have_srgb) {
|
||||
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
|
||||
ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
|
||||
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
|
||||
ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL;
|
||||
}
|
||||
JXL_RETURN_IF_ERROR(ApplyColorHints(
|
||||
color_hints, have_color, ppf->info.num_color_channels == 1, ppf));
|
||||
}
|
||||
|
||||
if (errorstate) return false;
|
||||
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 jxl
|
|
@ -3,14 +3,14 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_CODEC_APNG_H_
|
||||
#define LIB_EXTRAS_CODEC_APNG_H_
|
||||
#ifndef LIB_EXTRAS_DEC_APNG_H_
|
||||
#define LIB_EXTRAS_DEC_APNG_H_
|
||||
|
||||
// Decodes APNG images in memory.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/extras/color_hints.h"
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
|
@ -21,13 +21,12 @@
|
|||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `io`. color_space_hint is ignored.
|
||||
Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
// Decodes `bytes` into `ppf`.
|
||||
Status DecodeImageAPNG(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_CODEC_APNG_H_
|
||||
#endif // LIB_EXTRAS_DEC_APNG_H_
|
|
@ -3,9 +3,10 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/color_description.h"
|
||||
#include "lib/extras/dec/color_description.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace jxl {
|
|
@ -3,7 +3,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/color_description.h"
|
||||
#include "lib/extras/dec/color_description.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
|
@ -3,10 +3,10 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/color_hints.h"
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
|
||||
#include "jxl/encode.h"
|
||||
#include "lib/extras/color_description.h"
|
||||
#include "lib/extras/dec/color_description.h"
|
||||
#include "lib/jxl/base/file_io.h"
|
||||
|
||||
namespace jxl {
|
||||
|
@ -54,7 +54,11 @@ Status ApplyColorHints(const ColorHints& color_hints,
|
|||
|
||||
if (!got_color_space) {
|
||||
JXL_WARNING("No color_space/icc_pathname given, assuming sRGB");
|
||||
JxlColorEncodingSetToSRGB(&ppf->color_encoding, is_gray);
|
||||
ppf->color_encoding.color_space =
|
||||
is_gray ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
|
||||
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
|
||||
ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
|
||||
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
|
||||
}
|
||||
|
||||
return true;
|
|
@ -21,6 +21,7 @@
|
|||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
class ColorHints {
|
||||
public:
|
||||
|
@ -59,8 +60,6 @@ class ColorHints {
|
|||
std::vector<KeyValue> kv_;
|
||||
};
|
||||
|
||||
namespace extras {
|
||||
|
||||
// Apply the color hints to the decoded image in PackedPixelFile if any.
|
||||
// color_already_set tells whether the color encoding was already set, in which
|
||||
// case the hints are ignored if any hint is passed.
|
|
@ -0,0 +1,139 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/dec/decode.h"
|
||||
|
||||
#include <locale>
|
||||
|
||||
#if JPEGXL_ENABLE_APNG
|
||||
#include "lib/extras/dec/apng.h"
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_EXR
|
||||
#include "lib/extras/dec/exr.h"
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_GIF
|
||||
#include "lib/extras/dec/gif.h"
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
#include "lib/extras/dec/jpg.h"
|
||||
#endif
|
||||
#include "lib/extras/dec/pgx.h"
|
||||
#include "lib/extras/dec/pnm.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
namespace {
|
||||
|
||||
// Any valid encoding is larger (ensures codecs can read the first few bytes)
|
||||
constexpr size_t kMinBytes = 9;
|
||||
|
||||
} // 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,
|
||||
size_t* JXL_RESTRICT bits_per_sample) {
|
||||
std::transform(
|
||||
extension.begin(), extension.end(), extension.begin(),
|
||||
[](char c) { return std::tolower(c, std::locale::classic()); });
|
||||
if (extension == ".png") return Codec::kPNG;
|
||||
|
||||
if (extension == ".jpg") return Codec::kJPG;
|
||||
if (extension == ".jpeg") return Codec::kJPG;
|
||||
|
||||
if (extension == ".pgx") return Codec::kPGX;
|
||||
|
||||
if (extension == ".pbm") {
|
||||
if (bits_per_sample != nullptr) *bits_per_sample = 1;
|
||||
return Codec::kPNM;
|
||||
}
|
||||
if (extension == ".pgm") return Codec::kPNM;
|
||||
if (extension == ".ppm") return Codec::kPNM;
|
||||
if (extension == ".pfm") {
|
||||
if (bits_per_sample != nullptr) *bits_per_sample = 32;
|
||||
return Codec::kPNM;
|
||||
}
|
||||
|
||||
if (extension == ".gif") return Codec::kGIF;
|
||||
|
||||
if (extension == ".exr") return Codec::kEXR;
|
||||
|
||||
if (extension == ".psd") return Codec::kPSD;
|
||||
|
||||
return Codec::kUnknown;
|
||||
}
|
||||
|
||||
Status DecodeBytes(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
extras::PackedPixelFile* ppf, ThreadPool* pool,
|
||||
Codec* orig_codec) {
|
||||
if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes");
|
||||
|
||||
*ppf = extras::PackedPixelFile();
|
||||
|
||||
// Default values when not set by decoders.
|
||||
ppf->info.uses_original_profile = true;
|
||||
ppf->info.orientation = JXL_ORIENT_IDENTITY;
|
||||
|
||||
Codec codec;
|
||||
#if JPEGXL_ENABLE_APNG
|
||||
if (DecodeImageAPNG(bytes, color_hints, constraints, ppf)) {
|
||||
codec = Codec::kPNG;
|
||||
} else
|
||||
#endif
|
||||
if (DecodeImagePGX(bytes, color_hints, constraints, ppf)) {
|
||||
codec = Codec::kPGX;
|
||||
} else if (DecodeImagePNM(bytes, color_hints, constraints, ppf)) {
|
||||
codec = Codec::kPNM;
|
||||
}
|
||||
#if JPEGXL_ENABLE_GIF
|
||||
else if (DecodeImageGIF(bytes, color_hints, constraints, ppf)) {
|
||||
codec = Codec::kGIF;
|
||||
}
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
else if (DecodeImageJPG(bytes, color_hints, constraints, ppf)) {
|
||||
codec = Codec::kJPG;
|
||||
}
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_EXR
|
||||
else if (DecodeImageEXR(bytes, color_hints, constraints, pool, ppf)) {
|
||||
codec = Codec::kEXR;
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
return JXL_FAILURE("Codecs failed to decode");
|
||||
}
|
||||
if (orig_codec) *orig_codec = codec;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_DEC_DECODE_H_
|
||||
#define LIB_EXTRAS_DEC_DECODE_H_
|
||||
|
||||
// Facade for image decoders (PNG, PNM, ...).
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
#include "lib/jxl/field_encodings.h" // MakeBit
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Codecs supported by CodecInOut::Encode.
|
||||
enum class Codec : uint32_t {
|
||||
kUnknown, // for CodecFromExtension
|
||||
kPNG,
|
||||
kPNM,
|
||||
kPGX,
|
||||
kJPG,
|
||||
kGIF,
|
||||
kEXR,
|
||||
kPSD
|
||||
};
|
||||
|
||||
static inline constexpr uint64_t EnumBits(Codec /*unused*/) {
|
||||
// Return only fully-supported codecs (kGIF is decode-only).
|
||||
return MakeBit(Codec::kPNM)
|
||||
#if JPEGXL_ENABLE_APNG
|
||||
| MakeBit(Codec::kPNG)
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
| MakeBit(Codec::kJPG)
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_EXR
|
||||
| MakeBit(Codec::kEXR)
|
||||
#endif
|
||||
| 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
|
||||
// that Encode() would encode to PFM instead of PPM.
|
||||
Codec CodecFromExtension(std::string extension,
|
||||
size_t* JXL_RESTRICT bits_per_sample = nullptr);
|
||||
|
||||
// Decodes "bytes" and sets io->metadata.m.
|
||||
// color_space_hint may specify the color space, otherwise, defaults to sRGB.
|
||||
Status DecodeBytes(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
extras::PackedPixelFile* ppf, ThreadPool* pool = nullptr,
|
||||
Codec* orig_codec = nullptr);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_DEC_DECODE_H_
|
|
@ -3,7 +3,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/codec_exr.h"
|
||||
#include "lib/extras/dec/exr.h"
|
||||
|
||||
#include <ImfChromaticitiesAttribute.h>
|
||||
#include <ImfIO.h>
|
||||
|
@ -12,11 +12,6 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jxl/alpha.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/color_management.h"
|
||||
#include "lib/jxl/enc_image_bundle.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
|
@ -34,38 +29,15 @@ using ExrInt64 = decltype(std::declval<OpenEXR::IStream>().tellg());
|
|||
constexpr int kExrBitsPerSample = 16;
|
||||
constexpr int kExrAlphaBits = 16;
|
||||
|
||||
float GetIntensityTarget(float target_nits, const OpenEXR::Header& exr_header) {
|
||||
if (OpenEXR::hasWhiteLuminance(exr_header)) {
|
||||
const float exr_luminance = OpenEXR::whiteLuminance(exr_header);
|
||||
if (target_nits != 0) {
|
||||
JXL_WARNING(
|
||||
"overriding OpenEXR whiteLuminance of %g with user-specified value "
|
||||
"of %g",
|
||||
exr_luminance, target_nits);
|
||||
return target_nits;
|
||||
}
|
||||
return exr_luminance;
|
||||
}
|
||||
if (target_nits != 0) {
|
||||
return target_nits;
|
||||
}
|
||||
JXL_WARNING(
|
||||
"no OpenEXR whiteLuminance tag found and no intensity_target specified, "
|
||||
"defaulting to %g",
|
||||
kDefaultIntensityTarget);
|
||||
return kDefaultIntensityTarget;
|
||||
}
|
||||
|
||||
size_t GetNumThreads(ThreadPool* pool) {
|
||||
size_t exr_num_threads = 1;
|
||||
RunOnPool(
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, 0, 1,
|
||||
[&](size_t num_threads) {
|
||||
exr_num_threads = num_threads;
|
||||
return true;
|
||||
},
|
||||
[&](const int /* task */, const int /*thread*/) {},
|
||||
"DecodeImageEXRThreads");
|
||||
[&](uint32_t /* task */, size_t /*thread*/) {}, "DecodeImageEXRThreads"));
|
||||
return exr_num_threads;
|
||||
}
|
||||
|
||||
|
@ -99,38 +71,11 @@ class InMemoryIStream : public OpenEXR::IStream {
|
|||
size_t pos_ = 0;
|
||||
};
|
||||
|
||||
class InMemoryOStream : public OpenEXR::OStream {
|
||||
public:
|
||||
// `bytes` must outlive the InMemoryOStream.
|
||||
explicit InMemoryOStream(PaddedBytes* const bytes)
|
||||
: OStream(/*fileName=*/""), bytes_(*bytes) {}
|
||||
|
||||
void write(const char c[], const int n) override {
|
||||
if (bytes_.size() < pos_ + n) {
|
||||
bytes_.resize(pos_ + n);
|
||||
}
|
||||
std::copy_n(c, n, bytes_.begin() + pos_);
|
||||
pos_ += n;
|
||||
}
|
||||
|
||||
ExrInt64 tellp() override { return pos_; }
|
||||
void seekp(const ExrInt64 pos) override {
|
||||
if (bytes_.size() + 1 < pos) {
|
||||
bytes_.resize(pos - 1);
|
||||
}
|
||||
pos_ = pos;
|
||||
}
|
||||
|
||||
private:
|
||||
PaddedBytes& bytes_;
|
||||
size_t pos_ = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, float target_nits,
|
||||
ThreadPool* pool, PackedPixelFile* ppf) {
|
||||
const SizeConstraints& constraints, ThreadPool* pool,
|
||||
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.
|
||||
|
@ -159,8 +104,9 @@ Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
|||
const bool has_alpha = (input.channels() & OpenEXR::RgbaChannels::WRITE_A) ==
|
||||
OpenEXR::RgbaChannels::WRITE_A;
|
||||
|
||||
const float intensity_target =
|
||||
GetIntensityTarget(target_nits, input.header());
|
||||
const float intensity_target = OpenEXR::hasWhiteLuminance(input.header())
|
||||
? OpenEXR::whiteLuminance(input.header())
|
||||
: kDefaultIntensityTarget;
|
||||
|
||||
auto image_size = input.displayWindow().size();
|
||||
// Size is computed as max - min, but both bounds are inclusive.
|
||||
|
@ -203,9 +149,9 @@ Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
|||
input_rows.data() - input.dataWindow().min.x - start_y * row_size,
|
||||
/*xStride=*/1, /*yStride=*/row_size);
|
||||
input.readPixels(start_y, end_y);
|
||||
RunOnPool(
|
||||
pool, start_y, end_y + 1, ThreadPool::SkipInit(),
|
||||
[&](const int exr_y, const int /*thread*/) {
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, start_y, end_y + 1, ThreadPool::NoInit,
|
||||
[&](const uint32_t exr_y, size_t /* thread */) {
|
||||
const int image_y = exr_y - input.displayWindow().min.y;
|
||||
const OpenEXR::Rgba* const JXL_RESTRICT input_row =
|
||||
&input_rows[(exr_y - start_y) * row_size];
|
||||
|
@ -223,7 +169,7 @@ Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
|||
input_row + (exr_x - input.dataWindow().min.x), pixel_size);
|
||||
}
|
||||
},
|
||||
"DecodeImageEXR");
|
||||
"DecodeImageEXR"));
|
||||
}
|
||||
|
||||
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
|
||||
|
@ -256,95 +202,5 @@ Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
|||
return true;
|
||||
}
|
||||
|
||||
Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||
ThreadPool* pool, PaddedBytes* bytes) {
|
||||
// As in `DecodeImageEXR`, `pool` is only used for pixel conversion, not for
|
||||
// actual OpenEXR I/O.
|
||||
OpenEXR::setGlobalThreadCount(GetNumThreads(pool));
|
||||
|
||||
ColorEncoding c_linear = c_desired;
|
||||
c_linear.tf.SetTransferFunction(TransferFunction::kLinear);
|
||||
JXL_RETURN_IF_ERROR(c_linear.CreateICC());
|
||||
ImageMetadata metadata = io->metadata.m;
|
||||
ImageBundle store(&metadata);
|
||||
const ImageBundle* linear;
|
||||
JXL_RETURN_IF_ERROR(
|
||||
TransformIfNeeded(io->Main(), c_linear, pool, &store, &linear));
|
||||
|
||||
const bool has_alpha = io->Main().HasAlpha();
|
||||
const bool alpha_is_premultiplied = io->Main().AlphaIsPremultiplied();
|
||||
|
||||
OpenEXR::Header header(io->xsize(), io->ysize());
|
||||
const PrimariesCIExy& primaries = c_linear.HasPrimaries()
|
||||
? c_linear.GetPrimaries()
|
||||
: ColorEncoding::SRGB().GetPrimaries();
|
||||
OpenEXR::Chromaticities chromaticities;
|
||||
chromaticities.red = Imath::V2f(primaries.r.x, primaries.r.y);
|
||||
chromaticities.green = Imath::V2f(primaries.g.x, primaries.g.y);
|
||||
chromaticities.blue = Imath::V2f(primaries.b.x, primaries.b.y);
|
||||
chromaticities.white =
|
||||
Imath::V2f(c_linear.GetWhitePoint().x, c_linear.GetWhitePoint().y);
|
||||
OpenEXR::addChromaticities(header, chromaticities);
|
||||
OpenEXR::addWhiteLuminance(header, io->metadata.m.IntensityTarget());
|
||||
|
||||
// Ensure that the destructor of RgbaOutputFile has run before we look at the
|
||||
// size of `bytes`.
|
||||
{
|
||||
InMemoryOStream os(bytes);
|
||||
OpenEXR::RgbaOutputFile output(
|
||||
os, header, has_alpha ? OpenEXR::WRITE_RGBA : OpenEXR::WRITE_RGB);
|
||||
// How many rows to write at once. Again, the OpenEXR documentation
|
||||
// recommends writing the whole image in one call.
|
||||
const int y_chunk_size = io->ysize();
|
||||
std::vector<OpenEXR::Rgba> output_rows(io->xsize() * y_chunk_size);
|
||||
|
||||
for (size_t start_y = 0; start_y < io->ysize(); start_y += y_chunk_size) {
|
||||
// Inclusive.
|
||||
const size_t end_y =
|
||||
std::min(start_y + y_chunk_size - 1, io->ysize() - 1);
|
||||
output.setFrameBuffer(output_rows.data() - start_y * io->xsize(),
|
||||
/*xStride=*/1, /*yStride=*/io->xsize());
|
||||
RunOnPool(
|
||||
pool, start_y, end_y + 1, ThreadPool::SkipInit(),
|
||||
[&](const int y, const int /*thread*/) {
|
||||
const float* const JXL_RESTRICT input_rows[] = {
|
||||
linear->color().ConstPlaneRow(0, y),
|
||||
linear->color().ConstPlaneRow(1, y),
|
||||
linear->color().ConstPlaneRow(2, y),
|
||||
};
|
||||
OpenEXR::Rgba* const JXL_RESTRICT row_data =
|
||||
&output_rows[(y - start_y) * io->xsize()];
|
||||
if (has_alpha) {
|
||||
const float* const JXL_RESTRICT alpha_row =
|
||||
io->Main().alpha().ConstRow(y);
|
||||
if (alpha_is_premultiplied) {
|
||||
for (size_t x = 0; x < io->xsize(); ++x) {
|
||||
row_data[x] =
|
||||
OpenEXR::Rgba(input_rows[0][x], input_rows[1][x],
|
||||
input_rows[2][x], alpha_row[x]);
|
||||
}
|
||||
} else {
|
||||
for (size_t x = 0; x < io->xsize(); ++x) {
|
||||
row_data[x] = OpenEXR::Rgba(alpha_row[x] * input_rows[0][x],
|
||||
alpha_row[x] * input_rows[1][x],
|
||||
alpha_row[x] * input_rows[2][x],
|
||||
alpha_row[x]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (size_t x = 0; x < io->xsize(); ++x) {
|
||||
row_data[x] = OpenEXR::Rgba(input_rows[0][x], input_rows[1][x],
|
||||
input_rows[2][x], 1.f);
|
||||
}
|
||||
}
|
||||
},
|
||||
"EncodeImageEXR");
|
||||
output.writePixels(/*numScanLines=*/end_y - start_y + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_DEC_EXR_H_
|
||||
#define LIB_EXTRAS_DEC_EXR_H_
|
||||
|
||||
// Decodes OpenEXR images in memory.
|
||||
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `ppf`. color_hints are ignored.
|
||||
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, ThreadPool* pool,
|
||||
PackedPixelFile* ppf);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_DEC_EXR_H_
|
|
@ -3,24 +3,15 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/codec_gif.h"
|
||||
#include "lib/extras/dec/gif.h"
|
||||
|
||||
#include <gif_lib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/frame_header.h"
|
||||
#include "lib/jxl/headers.h"
|
||||
#include "lib/jxl/image.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
#include "lib/jxl/image_ops.h"
|
||||
#include "lib/jxl/luminance.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
|
||||
namespace jxl {
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_DEC_GIF_H_
|
||||
#define LIB_EXTRAS_DEC_GIF_H_
|
||||
|
||||
// Decodes GIF images in memory.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `ppf`. color_hints are ignored.
|
||||
Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, PackedPixelFile* ppf);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_DEC_GIF_H_
|
|
@ -0,0 +1,288 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/dec/jpg.h"
|
||||
|
||||
#include <jpeglib.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr unsigned char kICCSignature[12] = {
|
||||
0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00};
|
||||
constexpr int kICCMarker = JPEG_APP0 + 2;
|
||||
|
||||
constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
|
||||
0x66, 0x00, 0x00};
|
||||
constexpr int kExifMarker = JPEG_APP0 + 1;
|
||||
|
||||
static inline bool IsJPG(const Span<const uint8_t> bytes) {
|
||||
if (bytes.size() < 2) return false;
|
||||
if (bytes[0] != 0xFF || bytes[1] != 0xD8) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MarkerIsICC(const jpeg_saved_marker_ptr marker) {
|
||||
return marker->marker == kICCMarker &&
|
||||
marker->data_length >= sizeof kICCSignature + 2 &&
|
||||
std::equal(std::begin(kICCSignature), std::end(kICCSignature),
|
||||
marker->data);
|
||||
}
|
||||
bool MarkerIsExif(const jpeg_saved_marker_ptr marker) {
|
||||
return marker->marker == kExifMarker &&
|
||||
marker->data_length >= sizeof kExifSignature + 2 &&
|
||||
std::equal(std::begin(kExifSignature), std::end(kExifSignature),
|
||||
marker->data);
|
||||
}
|
||||
|
||||
Status ReadICCProfile(jpeg_decompress_struct* const cinfo,
|
||||
std::vector<uint8_t>* const icc) {
|
||||
constexpr size_t kICCSignatureSize = sizeof kICCSignature;
|
||||
// ICC signature + uint8_t index + uint8_t max_index.
|
||||
constexpr size_t kICCHeadSize = kICCSignatureSize + 2;
|
||||
// Markers are 1-indexed, and we keep them that way in this vector to get a
|
||||
// convenient 0 at the front for when we compute the offsets later.
|
||||
std::vector<size_t> marker_lengths;
|
||||
int num_markers = 0;
|
||||
int seen_markers_count = 0;
|
||||
bool has_num_markers = false;
|
||||
for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr;
|
||||
marker = marker->next) {
|
||||
// marker is initialized by libjpeg, which we are not instrumenting with
|
||||
// msan.
|
||||
msan::UnpoisonMemory(marker, sizeof(*marker));
|
||||
msan::UnpoisonMemory(marker->data, marker->data_length);
|
||||
if (!MarkerIsICC(marker)) continue;
|
||||
|
||||
const int current_marker = marker->data[kICCSignatureSize];
|
||||
if (current_marker == 0) {
|
||||
return JXL_FAILURE("inconsistent JPEG ICC marker numbering");
|
||||
}
|
||||
const int current_num_markers = marker->data[kICCSignatureSize + 1];
|
||||
if (current_marker > current_num_markers) {
|
||||
return JXL_FAILURE("inconsistent JPEG ICC marker numbering");
|
||||
}
|
||||
if (has_num_markers) {
|
||||
if (current_num_markers != num_markers) {
|
||||
return JXL_FAILURE("inconsistent numbers of JPEG ICC markers");
|
||||
}
|
||||
} else {
|
||||
num_markers = current_num_markers;
|
||||
has_num_markers = true;
|
||||
marker_lengths.resize(num_markers + 1);
|
||||
}
|
||||
|
||||
size_t marker_length = marker->data_length - kICCHeadSize;
|
||||
|
||||
if (marker_length == 0) {
|
||||
// NB: if we allow empty chunks, then the next check is incorrect.
|
||||
return JXL_FAILURE("Empty ICC chunk");
|
||||
}
|
||||
|
||||
if (marker_lengths[current_marker] != 0) {
|
||||
return JXL_FAILURE("duplicate JPEG ICC marker number");
|
||||
}
|
||||
marker_lengths[current_marker] = marker_length;
|
||||
seen_markers_count++;
|
||||
}
|
||||
|
||||
if (marker_lengths.empty()) {
|
||||
// Not an error.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (seen_markers_count != num_markers) {
|
||||
JXL_DASSERT(has_num_markers);
|
||||
return JXL_FAILURE("Incomplete set of ICC chunks");
|
||||
}
|
||||
|
||||
std::vector<size_t> offsets = std::move(marker_lengths);
|
||||
std::partial_sum(offsets.begin(), offsets.end(), offsets.begin());
|
||||
icc->resize(offsets.back());
|
||||
|
||||
for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr;
|
||||
marker = marker->next) {
|
||||
if (!MarkerIsICC(marker)) continue;
|
||||
const uint8_t* first = marker->data + kICCHeadSize;
|
||||
uint8_t current_marker = marker->data[kICCSignatureSize];
|
||||
size_t offset = offsets[current_marker - 1];
|
||||
size_t marker_length = offsets[current_marker] - offset;
|
||||
std::copy_n(first, marker_length, icc->data() + offset);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReadExif(jpeg_decompress_struct* const cinfo,
|
||||
std::vector<uint8_t>* const exif) {
|
||||
constexpr size_t kExifSignatureSize = sizeof kExifSignature;
|
||||
for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr;
|
||||
marker = marker->next) {
|
||||
// marker is initialized by libjpeg, which we are not instrumenting with
|
||||
// msan.
|
||||
msan::UnpoisonMemory(marker, sizeof(*marker));
|
||||
msan::UnpoisonMemory(marker->data, marker->data_length);
|
||||
if (!MarkerIsExif(marker)) continue;
|
||||
size_t marker_length = marker->data_length - kExifSignatureSize;
|
||||
exif->resize(marker_length);
|
||||
std::copy_n(marker->data + kExifSignatureSize, marker_length, exif->data());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MyErrorExit(j_common_ptr cinfo) {
|
||||
jmp_buf* env = static_cast<jmp_buf*>(cinfo->client_data);
|
||||
(*cinfo->err->output_message)(cinfo);
|
||||
jpeg_destroy_decompress(reinterpret_cast<j_decompress_ptr>(cinfo));
|
||||
longjmp(*env, 1);
|
||||
}
|
||||
|
||||
void MyOutputMessage(j_common_ptr cinfo) {
|
||||
#if JXL_DEBUG_WARNING == 1
|
||||
char buf[JMSG_LENGTH_MAX];
|
||||
(*cinfo->err->format_message)(cinfo, buf);
|
||||
JXL_WARNING("%s", buf);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeImageJPG(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
// Don't do anything for non-JPEG files (no need to report an error)
|
||||
if (!IsJPG(bytes)) return false;
|
||||
|
||||
// TODO(veluca): use JPEGData also for pixels?
|
||||
|
||||
// We need to declare all the non-trivial destructor local variables before
|
||||
// the call to setjmp().
|
||||
std::unique_ptr<JSAMPLE[]> row;
|
||||
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
jpeg_decompress_struct cinfo;
|
||||
// cinfo is initialized by libjpeg, which we are not instrumenting with
|
||||
// msan, therefore we need to initialize cinfo here.
|
||||
msan::UnpoisonMemory(&cinfo, sizeof(cinfo));
|
||||
// Setup error handling in jpeg library so we can deal with broken jpegs in
|
||||
// the fuzzer.
|
||||
jpeg_error_mgr jerr;
|
||||
jmp_buf env;
|
||||
cinfo.err = jpeg_std_error(&jerr);
|
||||
jerr.error_exit = &MyErrorExit;
|
||||
jerr.output_message = &MyOutputMessage;
|
||||
if (setjmp(env)) {
|
||||
return false;
|
||||
}
|
||||
cinfo.client_data = static_cast<void*>(&env);
|
||||
|
||||
jpeg_create_decompress(&cinfo);
|
||||
jpeg_mem_src(&cinfo, reinterpret_cast<const unsigned char*>(bytes.data()),
|
||||
bytes.size());
|
||||
jpeg_save_markers(&cinfo, kICCMarker, 0xFFFF);
|
||||
jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
|
||||
const auto failure = [&cinfo](const char* str) -> Status {
|
||||
jpeg_abort_decompress(&cinfo);
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
return JXL_FAILURE("%s", str);
|
||||
};
|
||||
int read_header_result = jpeg_read_header(&cinfo, TRUE);
|
||||
// TODO(eustas): what about JPEG_HEADER_TABLES_ONLY?
|
||||
if (read_header_result == JPEG_SUSPENDED) {
|
||||
return failure("truncated JPEG input");
|
||||
}
|
||||
if (!VerifyDimensions(&constraints, cinfo.image_width,
|
||||
cinfo.image_height)) {
|
||||
return failure("image too big");
|
||||
}
|
||||
// Might cause CPU-zip bomb.
|
||||
if (cinfo.arith_code) {
|
||||
return failure("arithmetic code JPEGs are not supported");
|
||||
}
|
||||
int nbcomp = cinfo.num_components;
|
||||
if (nbcomp != 1 && nbcomp != 3) {
|
||||
return failure("unsupported number of components in JPEG");
|
||||
}
|
||||
if (!ReadICCProfile(&cinfo, &ppf->icc)) {
|
||||
ppf->icc.clear();
|
||||
// Default to SRGB
|
||||
// Actually, (cinfo.output_components == nbcomp) will be checked after
|
||||
// `jpeg_start_decompress`.
|
||||
ppf->color_encoding.color_space =
|
||||
(nbcomp == 1) ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
|
||||
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
|
||||
ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
|
||||
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
|
||||
ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL;
|
||||
}
|
||||
ReadExif(&cinfo, &ppf->metadata.exif);
|
||||
if (!ApplyColorHints(color_hints, /*color_already_set=*/true,
|
||||
/*is_gray=*/false, ppf)) {
|
||||
return failure("ApplyColorHints failed");
|
||||
}
|
||||
|
||||
ppf->info.xsize = cinfo.image_width;
|
||||
ppf->info.ysize = cinfo.image_height;
|
||||
// Original data is uint, so exponent_bits_per_sample = 0.
|
||||
ppf->info.bits_per_sample = BITS_IN_JSAMPLE;
|
||||
JXL_ASSERT(BITS_IN_JSAMPLE == 8 || BITS_IN_JSAMPLE == 16);
|
||||
ppf->info.exponent_bits_per_sample = 0;
|
||||
ppf->info.uses_original_profile = true;
|
||||
|
||||
// No alpha in JPG
|
||||
ppf->info.alpha_bits = 0;
|
||||
ppf->info.alpha_exponent_bits = 0;
|
||||
|
||||
ppf->info.num_color_channels = nbcomp;
|
||||
ppf->info.orientation = JXL_ORIENT_IDENTITY;
|
||||
|
||||
jpeg_start_decompress(&cinfo);
|
||||
JXL_ASSERT(cinfo.output_components == nbcomp);
|
||||
|
||||
const JxlPixelFormat format{
|
||||
/*num_channels=*/static_cast<uint32_t>(nbcomp),
|
||||
/*data_type=*/BITS_IN_JSAMPLE == 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16,
|
||||
/*endianness=*/JXL_NATIVE_ENDIAN,
|
||||
/*align=*/0,
|
||||
};
|
||||
ppf->frames.clear();
|
||||
// Allocates the frame buffer.
|
||||
ppf->frames.emplace_back(cinfo.image_width, cinfo.image_height, format);
|
||||
const auto& frame = ppf->frames.back();
|
||||
JXL_ASSERT(sizeof(JSAMPLE) * cinfo.output_components * cinfo.image_width <=
|
||||
frame.color.stride);
|
||||
|
||||
for (size_t y = 0; y < cinfo.image_height; ++y) {
|
||||
JSAMPROW rows[] = {reinterpret_cast<JSAMPLE*>(
|
||||
static_cast<uint8_t*>(frame.color.pixels()) +
|
||||
frame.color.stride * y)};
|
||||
jpeg_read_scanlines(&cinfo, rows, 1);
|
||||
msan::UnpoisonMemory(rows[0], sizeof(JSAMPLE) * cinfo.output_components *
|
||||
cinfo.image_width);
|
||||
}
|
||||
|
||||
jpeg_finish_decompress(&cinfo);
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
return true;
|
||||
};
|
||||
|
||||
return try_catch_block();
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_DEC_JPG_H_
|
||||
#define LIB_EXTRAS_DEC_JPG_H_
|
||||
|
||||
// Decodes JPG pixels and metadata in memory.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/extras/codec.h"
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `ppf`. color_hints are ignored.
|
||||
// `elapsed_deinterleave`, if non-null, will be set to the time (in seconds)
|
||||
// that it took to deinterleave the raw JSAMPLEs to planar floats.
|
||||
Status DecodeImageJPG(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, PackedPixelFile* ppf);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_DEC_JPG_H_
|
|
@ -3,30 +3,12 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/codec_pgx.h"
|
||||
#include "lib/extras/dec/pgx.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#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/dec_external_image.h"
|
||||
#include "lib/jxl/enc_external_image.h"
|
||||
#include "lib/jxl/enc_image_bundle.h"
|
||||
#include "lib/jxl/fields.h" // AllDefault
|
||||
#include "lib/jxl/image.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
#include "lib/jxl/luminance.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
@ -160,28 +142,6 @@ class Parser {
|
|||
const uint8_t* const end_;
|
||||
};
|
||||
|
||||
constexpr size_t kMaxHeaderSize = 200;
|
||||
|
||||
Status EncodeHeader(const ImageBundle& ib, const size_t bits_per_sample,
|
||||
char* header, int* JXL_RESTRICT chars_written) {
|
||||
if (ib.HasAlpha()) return JXL_FAILURE("PGX: can't store alpha");
|
||||
if (!ib.IsGray()) return JXL_FAILURE("PGX: must be grayscale");
|
||||
// TODO(lode): verify other bit depths: for other bit depths such as 1 or 4
|
||||
// bits, have a test case to verify it works correctly. For bits > 16, we may
|
||||
// need to change the way external_image works.
|
||||
if (bits_per_sample != 8 && bits_per_sample != 16) {
|
||||
return JXL_FAILURE("PGX: bits other than 8 or 16 not yet supported");
|
||||
}
|
||||
|
||||
// Use ML (Big Endian), LM may not be well supported by all decoders.
|
||||
*chars_written = snprintf(header, kMaxHeaderSize,
|
||||
"PG ML + %" PRIuS " %" PRIuS " %" PRIuS "\n",
|
||||
bits_per_sample, ib.xsize(), ib.ysize());
|
||||
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
|
||||
kMaxHeaderSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeImagePGX(const Span<const uint8_t> bytes,
|
||||
|
@ -240,45 +200,5 @@ Status DecodeImagePGX(const Span<const uint8_t> bytes,
|
|||
return true;
|
||||
}
|
||||
|
||||
Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||
size_t bits_per_sample, ThreadPool* pool,
|
||||
PaddedBytes* bytes) {
|
||||
if (!Bundle::AllDefault(io->metadata.m)) {
|
||||
JXL_WARNING("PGX encoder ignoring metadata - use a different codec");
|
||||
}
|
||||
if (!c_desired.IsSRGB()) {
|
||||
JXL_WARNING(
|
||||
"PGX encoder cannot store custom ICC profile; decoder\n"
|
||||
"will need hint key=color_space to get the same values");
|
||||
}
|
||||
|
||||
ImageBundle ib = io->Main().Copy();
|
||||
|
||||
ImageMetadata metadata = io->metadata.m;
|
||||
ImageBundle store(&metadata);
|
||||
const ImageBundle* transformed;
|
||||
JXL_RETURN_IF_ERROR(
|
||||
TransformIfNeeded(ib, c_desired, pool, &store, &transformed));
|
||||
PaddedBytes pixels(ib.xsize() * ib.ysize() *
|
||||
(bits_per_sample / kBitsPerByte));
|
||||
size_t stride = ib.xsize() * (bits_per_sample / kBitsPerByte);
|
||||
JXL_RETURN_IF_ERROR(
|
||||
ConvertToExternal(*transformed, bits_per_sample,
|
||||
/*float_out=*/false,
|
||||
/*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool,
|
||||
pixels.data(), pixels.size(), /*out_callback=*/nullptr,
|
||||
/*out_opaque=*/nullptr, metadata.GetOrientation()));
|
||||
|
||||
char header[kMaxHeaderSize];
|
||||
int header_size = 0;
|
||||
JXL_RETURN_IF_ERROR(EncodeHeader(ib, bits_per_sample, header, &header_size));
|
||||
|
||||
bytes->resize(static_cast<size_t>(header_size) + pixels.size());
|
||||
memcpy(bytes->data(), header, static_cast<size_t>(header_size));
|
||||
memcpy(bytes->data() + header_size, pixels.data(), pixels.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
|
@ -3,14 +3,15 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_CODEC_GIF_H_
|
||||
#define LIB_EXTRAS_CODEC_GIF_H_
|
||||
#ifndef LIB_EXTRAS_DEC_PGX_H_
|
||||
#define LIB_EXTRAS_DEC_PGX_H_
|
||||
|
||||
// Decodes GIF images in memory.
|
||||
// Decodes PGX pixels in memory.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/extras/color_hints.h"
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
|
@ -21,12 +22,11 @@
|
|||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `io`. color_hints are ignored.
|
||||
Status DecodeImageGIF(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
// Decodes `bytes` into `ppf`.
|
||||
Status DecodeImagePGX(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, PackedPixelFile* ppf);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_CODEC_GIF_H_
|
||||
#endif // LIB_EXTRAS_DEC_PGX_H_
|
|
@ -3,9 +3,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/codec_pgx.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include "lib/extras/dec/pgx.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "lib/extras/packed_image_convert.h"
|
|
@ -3,32 +3,14 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/codec_pnm.h"
|
||||
#include "lib/extras/dec/pnm.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#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/base/status.h"
|
||||
#include "lib/jxl/color_management.h"
|
||||
#include "lib/jxl/dec_external_image.h"
|
||||
#include "lib/jxl/enc_external_image.h"
|
||||
#include "lib/jxl/enc_image_bundle.h"
|
||||
#include "lib/jxl/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 {
|
||||
|
@ -248,64 +230,11 @@ class Parser {
|
|||
const uint8_t* const end_;
|
||||
};
|
||||
|
||||
constexpr size_t kMaxHeaderSize = 200;
|
||||
|
||||
Status EncodeHeader(const ImageBundle& ib, const size_t bits_per_sample,
|
||||
const bool little_endian, char* header,
|
||||
int* JXL_RESTRICT chars_written) {
|
||||
if (ib.HasAlpha()) return JXL_FAILURE("PNM: can't store alpha");
|
||||
|
||||
if (bits_per_sample == 32) { // PFM
|
||||
const char type = ib.IsGray() ? 'f' : 'F';
|
||||
const double scale = little_endian ? -1.0 : 1.0;
|
||||
*chars_written =
|
||||
snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%.1f\n",
|
||||
type, ib.oriented_xsize(), ib.oriented_ysize(), scale);
|
||||
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
|
||||
kMaxHeaderSize);
|
||||
} else if (bits_per_sample == 1) { // PBM
|
||||
if (!ib.IsGray()) {
|
||||
return JXL_FAILURE("Cannot encode color as PBM");
|
||||
}
|
||||
*chars_written =
|
||||
snprintf(header, kMaxHeaderSize, "P4\n%" PRIuS " %" PRIuS "\n",
|
||||
ib.oriented_xsize(), ib.oriented_ysize());
|
||||
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
|
||||
kMaxHeaderSize);
|
||||
} else { // PGM/PPM
|
||||
const uint32_t max_val = (1U << bits_per_sample) - 1;
|
||||
if (max_val >= 65536) return JXL_FAILURE("PNM cannot have > 16 bits");
|
||||
const char type = ib.IsGray() ? '5' : '6';
|
||||
*chars_written =
|
||||
snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%u\n",
|
||||
type, ib.oriented_xsize(), ib.oriented_ysize(), max_val);
|
||||
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
|
||||
kMaxHeaderSize);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Span<const uint8_t> MakeSpan(const char* str) {
|
||||
return Span<const uint8_t>(reinterpret_cast<const uint8_t*>(str),
|
||||
strlen(str));
|
||||
}
|
||||
|
||||
// Flip the image vertically for loading/saving PFM files which have the
|
||||
// scanlines inverted.
|
||||
void VerticallyFlipImage(Image3F* const image) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
for (size_t y = 0; y < image->ysize() / 2; y++) {
|
||||
float* first_row = image->PlaneRow(c, y);
|
||||
float* other_row = image->PlaneRow(c, image->ysize() - y - 1);
|
||||
for (size_t x = 0; x < image->xsize(); ++x) {
|
||||
float tmp = first_row[x];
|
||||
first_row[x] = other_row[x];
|
||||
other_row[x] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeImagePNM(const Span<const uint8_t> bytes,
|
||||
|
@ -378,63 +307,6 @@ Status DecodeImagePNM(const Span<const uint8_t> bytes,
|
|||
return true;
|
||||
}
|
||||
|
||||
Status EncodeImagePNM(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||
size_t bits_per_sample, ThreadPool* pool,
|
||||
PaddedBytes* bytes) {
|
||||
const bool floating_point = bits_per_sample > 16;
|
||||
// Choose native for PFM; PGM/PPM require big-endian (N/A for PBM)
|
||||
const JxlEndianness endianness =
|
||||
floating_point ? JXL_NATIVE_ENDIAN : JXL_BIG_ENDIAN;
|
||||
|
||||
ImageMetadata metadata_copy = io->metadata.m;
|
||||
// AllDefault sets all_default, which can cause a race condition.
|
||||
if (!Bundle::AllDefault(metadata_copy)) {
|
||||
JXL_WARNING("PNM encoder ignoring metadata - use a different codec");
|
||||
}
|
||||
if (!c_desired.IsSRGB()) {
|
||||
JXL_WARNING(
|
||||
"PNM encoder cannot store custom ICC profile; decoder\n"
|
||||
"will need hint key=color_space to get the same values");
|
||||
}
|
||||
|
||||
ImageBundle ib = io->Main().Copy();
|
||||
// In case of PFM the image must be flipped upside down since that format
|
||||
// is designed that way.
|
||||
const ImageBundle* to_color_transform = &ib;
|
||||
ImageBundle flipped;
|
||||
if (floating_point) {
|
||||
flipped = ib.Copy();
|
||||
VerticallyFlipImage(flipped.color());
|
||||
to_color_transform = &flipped;
|
||||
}
|
||||
ImageMetadata metadata = io->metadata.m;
|
||||
ImageBundle store(&metadata);
|
||||
const ImageBundle* transformed;
|
||||
JXL_RETURN_IF_ERROR(TransformIfNeeded(*to_color_transform, c_desired, pool,
|
||||
&store, &transformed));
|
||||
size_t stride = ib.oriented_xsize() *
|
||||
(c_desired.Channels() * bits_per_sample) / kBitsPerByte;
|
||||
PaddedBytes pixels(stride * ib.oriented_ysize());
|
||||
JXL_RETURN_IF_ERROR(ConvertToExternal(
|
||||
*transformed, bits_per_sample, floating_point, c_desired.Channels(),
|
||||
endianness, stride, pool, pixels.data(), pixels.size(),
|
||||
/*out_callback=*/nullptr, /*out_opaque=*/nullptr,
|
||||
metadata.GetOrientation()));
|
||||
|
||||
char header[kMaxHeaderSize];
|
||||
int header_size = 0;
|
||||
bool is_little_endian = endianness == JXL_LITTLE_ENDIAN ||
|
||||
(endianness == JXL_NATIVE_ENDIAN && IsLittleEndian());
|
||||
JXL_RETURN_IF_ERROR(EncodeHeader(*transformed, bits_per_sample,
|
||||
is_little_endian, header, &header_size));
|
||||
|
||||
bytes->resize(static_cast<size_t>(header_size) + pixels.size());
|
||||
memcpy(bytes->data(), header, static_cast<size_t>(header_size));
|
||||
memcpy(bytes->data() + header_size, pixels.data(), pixels.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TestCodecPNM() {
|
||||
size_t u = 77777; // Initialized to wrong value.
|
||||
double d = 77.77;
|
|
@ -3,10 +3,10 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_CODEC_PNG_H_
|
||||
#define LIB_EXTRAS_CODEC_PNG_H_
|
||||
#ifndef LIB_EXTRAS_DEC_PNM_H_
|
||||
#define LIB_EXTRAS_DEC_PNM_H_
|
||||
|
||||
// Encodes/decodes PNG pixels and metadata in memory.
|
||||
// Decodes PBM/PGM/PPM/PFM pixels in memory.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
@ -14,29 +14,25 @@
|
|||
// TODO(janwas): workaround for incorrect Win64 codegen (cause unknown)
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/extras/color_hints.h"
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `ppf`.
|
||||
Status DecodeImagePNG(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
// Decodes `bytes` into `ppf`. color_hints may specify "color_space", which
|
||||
// defaults to sRGB.
|
||||
Status DecodeImagePNM(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, PackedPixelFile* ppf);
|
||||
|
||||
// Transforms from io->c_current to `c_desired` and encodes into `bytes`.
|
||||
Status EncodeImagePNG(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||
size_t bits_per_sample, ThreadPool* pool,
|
||||
PaddedBytes* bytes);
|
||||
void TestCodecPNM();
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_CODEC_PNG_H_
|
||||
#endif // LIB_EXTRAS_DEC_PNM_H_
|
|
@ -0,0 +1,270 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/enc/apng.h"
|
||||
|
||||
// Parts of this code are taken from apngdis, which has the following license:
|
||||
/* APNG Disassembler 2.8
|
||||
*
|
||||
* Deconstructs APNG files into individual frames.
|
||||
*
|
||||
* http://apngdis.sourceforge.net
|
||||
*
|
||||
* Copyright (c) 2010-2015 Max Stepin
|
||||
* maxst at users.sourceforge.net
|
||||
*
|
||||
* zlib license
|
||||
* ------------
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/encode.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/dec_external_image.h"
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
#include "lib/jxl/enc_image_bundle.h"
|
||||
#include "lib/jxl/frame_header.h"
|
||||
#include "lib/jxl/headers.h"
|
||||
#include "lib/jxl/image.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
#include "png.h" /* original (unpatched) libpng is ok */
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
namespace {
|
||||
|
||||
static void PngWrite(png_structp png_ptr, png_bytep data, png_size_t length) {
|
||||
PaddedBytes* bytes = static_cast<PaddedBytes*>(png_get_io_ptr(png_ptr));
|
||||
bytes->append(data, data + length);
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||
size_t bits_per_sample, ThreadPool* pool,
|
||||
PaddedBytes* bytes) {
|
||||
if (bits_per_sample > 8) {
|
||||
bits_per_sample = 16;
|
||||
} else if (bits_per_sample < 8) {
|
||||
// PNG can also do 4, 2, and 1 bits per sample, but it isn't implemented
|
||||
bits_per_sample = 8;
|
||||
}
|
||||
|
||||
size_t count = 0;
|
||||
bool have_anim = io->metadata.m.have_animation;
|
||||
size_t anim_chunks = 0;
|
||||
int W = 0, H = 0;
|
||||
|
||||
for (auto& frame : io->frames) {
|
||||
png_structp png_ptr;
|
||||
png_infop info_ptr;
|
||||
|
||||
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
|
||||
if (!png_ptr) return JXL_FAILURE("Could not init png encoder");
|
||||
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!info_ptr) return JXL_FAILURE("Could not init png info struct");
|
||||
|
||||
png_set_write_fn(png_ptr, bytes, PngWrite, NULL);
|
||||
png_set_flush(png_ptr, 0);
|
||||
|
||||
ImageBundle ib = frame.Copy();
|
||||
const size_t alpha_bits = ib.HasAlpha() ? bits_per_sample : 0;
|
||||
ImageMetadata metadata = io->metadata.m;
|
||||
ImageBundle store(&metadata);
|
||||
const ImageBundle* transformed;
|
||||
JXL_RETURN_IF_ERROR(TransformIfNeeded(ib, c_desired, GetJxlCms(), pool,
|
||||
&store, &transformed));
|
||||
size_t stride = ib.oriented_xsize() *
|
||||
DivCeil(c_desired.Channels() * bits_per_sample + alpha_bits,
|
||||
kBitsPerByte);
|
||||
PaddedBytes raw_bytes(stride * ib.oriented_ysize());
|
||||
JXL_RETURN_IF_ERROR(ConvertToExternal(
|
||||
*transformed, bits_per_sample, /*float_out=*/false,
|
||||
c_desired.Channels() + (ib.HasAlpha() ? 1 : 0), JXL_BIG_ENDIAN, stride,
|
||||
pool, raw_bytes.data(), raw_bytes.size(), /*out_callback=*/nullptr,
|
||||
/*out_opaque=*/nullptr, metadata.GetOrientation()));
|
||||
|
||||
int width = ib.oriented_xsize();
|
||||
int height = ib.oriented_ysize();
|
||||
|
||||
png_byte color_type =
|
||||
(c_desired.Channels() == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_GRAY);
|
||||
if (ib.HasAlpha()) color_type |= PNG_COLOR_MASK_ALPHA;
|
||||
png_byte bit_depth = bits_per_sample;
|
||||
|
||||
png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type,
|
||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
|
||||
PNG_FILTER_TYPE_BASE);
|
||||
if (count == 0) {
|
||||
W = width;
|
||||
H = height;
|
||||
|
||||
// TODO(jon): instead of always setting an iCCP, could try to avoid that
|
||||
// have to avoid warnings on the ICC profile becoming fatal
|
||||
png_set_benign_errors(png_ptr, 1);
|
||||
png_set_iCCP(png_ptr, info_ptr, "1", 0, c_desired.ICC().data(),
|
||||
c_desired.ICC().size());
|
||||
|
||||
std::vector<std::string> textstrings;
|
||||
JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(io->blobs, &textstrings));
|
||||
for (size_t i = 0; i + 1 < textstrings.size(); i += 2) {
|
||||
png_text text;
|
||||
text.key = const_cast<png_charp>(textstrings[i].c_str());
|
||||
text.text = const_cast<png_charp>(textstrings[i + 1].c_str());
|
||||
text.compression = PNG_TEXT_COMPRESSION_zTXt;
|
||||
png_set_text(png_ptr, info_ptr, &text, 1);
|
||||
}
|
||||
|
||||
png_write_info(png_ptr, info_ptr);
|
||||
} else {
|
||||
// fake writing a header, otherwise libpng gets confused
|
||||
size_t pos = bytes->size();
|
||||
png_write_info(png_ptr, info_ptr);
|
||||
bytes->resize(pos);
|
||||
}
|
||||
|
||||
if (have_anim) {
|
||||
if (count == 0) {
|
||||
png_byte adata[8];
|
||||
png_save_uint_32(adata, io->frames.size());
|
||||
png_save_uint_32(adata + 4, io->metadata.m.animation.num_loops);
|
||||
png_byte actl[5] = "acTL";
|
||||
png_write_chunk(png_ptr, actl, adata, 8);
|
||||
}
|
||||
png_byte fdata[26];
|
||||
JXL_ASSERT(W == width);
|
||||
JXL_ASSERT(H == height);
|
||||
// TODO(jon): also make this work for the non-coalesced case
|
||||
png_save_uint_32(fdata, anim_chunks++);
|
||||
png_save_uint_32(fdata + 4, width);
|
||||
png_save_uint_32(fdata + 8, height);
|
||||
png_save_uint_32(fdata + 12, 0);
|
||||
png_save_uint_32(fdata + 16, 0);
|
||||
png_save_uint_16(
|
||||
fdata + 20,
|
||||
frame.duration * io->metadata.m.animation.tps_denominator);
|
||||
png_save_uint_16(fdata + 22, io->metadata.m.animation.tps_numerator);
|
||||
fdata[24] = 1;
|
||||
fdata[25] = 0;
|
||||
png_byte fctl[5] = "fcTL";
|
||||
png_write_chunk(png_ptr, fctl, fdata, 26);
|
||||
}
|
||||
|
||||
std::vector<uint8_t*> rows(height);
|
||||
for (int y = 0; y < height; ++y) {
|
||||
rows[y] = raw_bytes.data() + y * stride;
|
||||
}
|
||||
|
||||
png_write_flush(png_ptr);
|
||||
const size_t pos = bytes->size();
|
||||
png_write_image(png_ptr, &rows[0]);
|
||||
png_write_flush(png_ptr);
|
||||
if (count > 0) {
|
||||
PaddedBytes fdata(4);
|
||||
png_save_uint_32(fdata.data(), anim_chunks++);
|
||||
size_t p = pos;
|
||||
while (p + 8 < bytes->size()) {
|
||||
size_t len = png_get_uint_32(bytes->data() + p);
|
||||
JXL_ASSERT(bytes->operator[](p + 4) == 'I');
|
||||
JXL_ASSERT(bytes->operator[](p + 5) == 'D');
|
||||
JXL_ASSERT(bytes->operator[](p + 6) == 'A');
|
||||
JXL_ASSERT(bytes->operator[](p + 7) == 'T');
|
||||
fdata.append(bytes->data() + p + 8, bytes->data() + p + 8 + len);
|
||||
p += len + 12;
|
||||
}
|
||||
bytes->resize(pos);
|
||||
|
||||
png_byte fdat[5] = "fdAT";
|
||||
png_write_chunk(png_ptr, fdat, fdata.data(), fdata.size());
|
||||
}
|
||||
|
||||
count++;
|
||||
if (count == io->frames.size()) png_write_end(png_ptr, NULL);
|
||||
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_ENC_APNG_H_
|
||||
#define LIB_EXTRAS_ENC_APNG_H_
|
||||
|
||||
// Encodes APNG images in memory.
|
||||
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Encodes `io` into `bytes`.
|
||||
Status EncodeImageAPNG(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||
size_t bits_per_sample, ThreadPool* pool,
|
||||
PaddedBytes* bytes);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_ENC_APNG_H_
|
|
@ -0,0 +1,167 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/enc/exr.h"
|
||||
|
||||
#include <ImfChromaticitiesAttribute.h>
|
||||
#include <ImfIO.h>
|
||||
#include <ImfRgbaFile.h>
|
||||
#include <ImfStandardAttributes.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jxl/alpha.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/color_management.h"
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
#include "lib/jxl/enc_image_bundle.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace OpenEXR = OPENEXR_IMF_NAMESPACE;
|
||||
namespace Imath = IMATH_NAMESPACE;
|
||||
|
||||
// OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using
|
||||
// uint64_t as recommended causes build failures with previous OpenEXR versions
|
||||
// on macOS, where the definition for OpenEXR::Int64 was actually not equivalent
|
||||
// to uint64_t. This alternative should work in all cases.
|
||||
using ExrInt64 = decltype(std::declval<OpenEXR::IStream>().tellg());
|
||||
|
||||
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 InMemoryOStream : public OpenEXR::OStream {
|
||||
public:
|
||||
// `bytes` must outlive the InMemoryOStream.
|
||||
explicit InMemoryOStream(PaddedBytes* const bytes)
|
||||
: OStream(/*fileName=*/""), bytes_(*bytes) {}
|
||||
|
||||
void write(const char c[], const int n) override {
|
||||
if (bytes_.size() < pos_ + n) {
|
||||
bytes_.resize(pos_ + n);
|
||||
}
|
||||
std::copy_n(c, n, bytes_.begin() + pos_);
|
||||
pos_ += n;
|
||||
}
|
||||
|
||||
ExrInt64 tellp() override { return pos_; }
|
||||
void seekp(const ExrInt64 pos) override {
|
||||
if (bytes_.size() + 1 < pos) {
|
||||
bytes_.resize(pos - 1);
|
||||
}
|
||||
pos_ = pos;
|
||||
}
|
||||
|
||||
private:
|
||||
PaddedBytes& bytes_;
|
||||
size_t pos_ = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||
ThreadPool* pool, PaddedBytes* bytes) {
|
||||
// As in `DecodeImageEXR`, `pool` is only used for pixel conversion, not for
|
||||
// actual OpenEXR I/O.
|
||||
OpenEXR::setGlobalThreadCount(GetNumThreads(pool));
|
||||
|
||||
ColorEncoding c_linear = c_desired;
|
||||
c_linear.tf.SetTransferFunction(TransferFunction::kLinear);
|
||||
JXL_RETURN_IF_ERROR(c_linear.CreateICC());
|
||||
ImageMetadata metadata = io->metadata.m;
|
||||
ImageBundle store(&metadata);
|
||||
const ImageBundle* linear;
|
||||
JXL_RETURN_IF_ERROR(TransformIfNeeded(io->Main(), c_linear, GetJxlCms(), pool,
|
||||
&store, &linear));
|
||||
|
||||
const bool has_alpha = io->Main().HasAlpha();
|
||||
const bool alpha_is_premultiplied = io->Main().AlphaIsPremultiplied();
|
||||
|
||||
OpenEXR::Header header(io->xsize(), io->ysize());
|
||||
const PrimariesCIExy& primaries = c_linear.HasPrimaries()
|
||||
? c_linear.GetPrimaries()
|
||||
: ColorEncoding::SRGB().GetPrimaries();
|
||||
OpenEXR::Chromaticities chromaticities;
|
||||
chromaticities.red = Imath::V2f(primaries.r.x, primaries.r.y);
|
||||
chromaticities.green = Imath::V2f(primaries.g.x, primaries.g.y);
|
||||
chromaticities.blue = Imath::V2f(primaries.b.x, primaries.b.y);
|
||||
chromaticities.white =
|
||||
Imath::V2f(c_linear.GetWhitePoint().x, c_linear.GetWhitePoint().y);
|
||||
OpenEXR::addChromaticities(header, chromaticities);
|
||||
OpenEXR::addWhiteLuminance(header, io->metadata.m.IntensityTarget());
|
||||
|
||||
// Ensure that the destructor of RgbaOutputFile has run before we look at the
|
||||
// size of `bytes`.
|
||||
{
|
||||
InMemoryOStream os(bytes);
|
||||
OpenEXR::RgbaOutputFile output(
|
||||
os, header, has_alpha ? OpenEXR::WRITE_RGBA : OpenEXR::WRITE_RGB);
|
||||
// How many rows to write at once. Again, the OpenEXR documentation
|
||||
// recommends writing the whole image in one call.
|
||||
const int y_chunk_size = io->ysize();
|
||||
std::vector<OpenEXR::Rgba> output_rows(io->xsize() * y_chunk_size);
|
||||
|
||||
for (size_t start_y = 0; start_y < io->ysize(); start_y += y_chunk_size) {
|
||||
// Inclusive.
|
||||
const size_t end_y =
|
||||
std::min(start_y + y_chunk_size - 1, io->ysize() - 1);
|
||||
output.setFrameBuffer(output_rows.data() - start_y * io->xsize(),
|
||||
/*xStride=*/1, /*yStride=*/io->xsize());
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, start_y, end_y + 1, ThreadPool::NoInit,
|
||||
[&](const uint32_t y, size_t /* thread */) {
|
||||
const float* const JXL_RESTRICT input_rows[] = {
|
||||
linear->color().ConstPlaneRow(0, y),
|
||||
linear->color().ConstPlaneRow(1, y),
|
||||
linear->color().ConstPlaneRow(2, y),
|
||||
};
|
||||
OpenEXR::Rgba* const JXL_RESTRICT row_data =
|
||||
&output_rows[(y - start_y) * io->xsize()];
|
||||
if (has_alpha) {
|
||||
const float* const JXL_RESTRICT alpha_row =
|
||||
io->Main().alpha().ConstRow(y);
|
||||
if (alpha_is_premultiplied) {
|
||||
for (size_t x = 0; x < io->xsize(); ++x) {
|
||||
row_data[x] =
|
||||
OpenEXR::Rgba(input_rows[0][x], input_rows[1][x],
|
||||
input_rows[2][x], alpha_row[x]);
|
||||
}
|
||||
} else {
|
||||
for (size_t x = 0; x < io->xsize(); ++x) {
|
||||
row_data[x] = OpenEXR::Rgba(alpha_row[x] * input_rows[0][x],
|
||||
alpha_row[x] * input_rows[1][x],
|
||||
alpha_row[x] * input_rows[2][x],
|
||||
alpha_row[x]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (size_t x = 0; x < io->xsize(); ++x) {
|
||||
row_data[x] = OpenEXR::Rgba(input_rows[0][x], input_rows[1][x],
|
||||
input_rows[2][x], 1.f);
|
||||
}
|
||||
}
|
||||
},
|
||||
"EncodeImageEXR"));
|
||||
output.writePixels(/*numScanLines=*/end_y - start_y + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
|
@ -3,12 +3,11 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_CODEC_EXR_H_
|
||||
#define LIB_EXTRAS_CODEC_EXR_H_
|
||||
#ifndef LIB_EXTRAS_ENC_EXR_H_
|
||||
#define LIB_EXTRAS_ENC_EXR_H_
|
||||
|
||||
// Encodes OpenEXR images in memory.
|
||||
|
||||
#include "lib/extras/color_hints.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
|
@ -20,11 +19,6 @@
|
|||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `io`. color_hints are ignored.
|
||||
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, float target_nits,
|
||||
ThreadPool* pool, PackedPixelFile* ppf);
|
||||
|
||||
// Transforms from io->c_current to `c_desired` (with the transfer function set
|
||||
// to linear as that is the OpenEXR convention) and encodes into `bytes`.
|
||||
Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||
|
@ -33,4 +27,4 @@ Status EncodeImageEXR(const CodecInOut* io, const ColorEncoding& c_desired,
|
|||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_CODEC_EXR_H_
|
||||
#endif // LIB_EXTRAS_ENC_EXR_H_
|
|
@ -3,17 +3,11 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/codec_jpg.h"
|
||||
#include "lib/extras/enc/jpg.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
// After stddef/stdio
|
||||
#include <jpeglib.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdint.h>
|
||||
#endif // JPEGXL_ENABLE_JPEG
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
@ -21,20 +15,14 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/extras/time.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/color_management.h"
|
||||
#include "lib/jxl/common.h"
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
#include "lib/jxl/enc_image_bundle.h"
|
||||
#include "lib/jxl/image.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
#include "lib/jxl/image_ops.h"
|
||||
#include "lib/jxl/jpeg/dec_jpeg_data_writer.h"
|
||||
#include "lib/jxl/jpeg/enc_jpeg_data.h"
|
||||
#include "lib/jxl/jpeg/enc_jpeg_data_reader.h"
|
||||
#include "lib/jxl/luminance.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
#if JPEGXL_ENABLE_SJPEG
|
||||
#include "sjpeg.h"
|
||||
|
@ -43,7 +31,6 @@
|
|||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
namespace {
|
||||
|
||||
constexpr float kJPEGSampleMultiplier = MAXJSAMPLE;
|
||||
|
@ -59,6 +46,12 @@ constexpr int kExifMarker = JPEG_APP0 + 1;
|
|||
constexpr float kJPEGSampleMin = 0;
|
||||
constexpr float kJPEGSampleMax = MAXJSAMPLE;
|
||||
|
||||
static inline bool IsJPG(const Span<const uint8_t> bytes) {
|
||||
if (bytes.size() < 2) return false;
|
||||
if (bytes[0] != 0xFF || bytes[1] != 0xD8) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MarkerIsICC(const jpeg_saved_marker_ptr marker) {
|
||||
return marker->marker == kICCMarker &&
|
||||
marker->data_length >= sizeof kICCSignature + 2 &&
|
||||
|
@ -236,17 +229,6 @@ void MyOutputMessage(j_common_ptr cinfo) {
|
|||
}
|
||||
|
||||
} // namespace
|
||||
#endif // JPEGXL_ENABLE_JPEG
|
||||
|
||||
Status DecodeImageJPGCoefficients(Span<const uint8_t> bytes, CodecInOut* io) {
|
||||
if (!IsJPG(bytes)) return false;
|
||||
// Use brunsli JPEG decoder to read quantized coefficients.
|
||||
if (!jpeg::DecodeImageJPG(bytes, io)) {
|
||||
fprintf(stderr, "Corrupt or CMYK JPEG.\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Status DecodeImageJPG(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
|
@ -255,7 +237,6 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes,
|
|||
// Don't do anything for non-JPEG files (no need to report an error)
|
||||
if (!IsJPG(bytes)) return false;
|
||||
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
// TODO(veluca): use JPEGData also for pixels?
|
||||
|
||||
// We need to declare all the non-trivial destructor local variables before
|
||||
|
@ -287,12 +268,16 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes,
|
|||
bytes.size());
|
||||
jpeg_save_markers(&cinfo, kICCMarker, 0xFFFF);
|
||||
jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
|
||||
jpeg_read_header(&cinfo, TRUE);
|
||||
const auto failure = [&cinfo](const char* str) -> Status {
|
||||
jpeg_abort_decompress(&cinfo);
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
return JXL_FAILURE("%s", str);
|
||||
};
|
||||
int read_header_result = jpeg_read_header(&cinfo, TRUE);
|
||||
// TODO(eustas): what about JPEG_HEADER_TABLES_ONLY?
|
||||
if (read_header_result == JPEG_SUSPENDED) {
|
||||
return failure("truncated JPEG input");
|
||||
}
|
||||
if (!VerifyDimensions(&constraints, cinfo.image_width,
|
||||
cinfo.image_height)) {
|
||||
return failure("image too big");
|
||||
|
@ -369,12 +354,8 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes,
|
|||
};
|
||||
|
||||
return try_catch_block();
|
||||
#else // JPEGXL_ENABLE_JPEG
|
||||
return JXL_FAILURE("JPEG decoding not enabled at build time.");
|
||||
#endif // JPEGXL_ENABLE_JPEG
|
||||
}
|
||||
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
Status EncodeWithLibJpeg(const ImageBundle* ib, const CodecInOut* io,
|
||||
size_t quality,
|
||||
const YCbCrChromaSubsampling& chroma_subsampling,
|
||||
|
@ -483,15 +464,6 @@ Status EncodeWithSJpeg(const ImageBundle* ib, size_t quality,
|
|||
return true;
|
||||
#endif
|
||||
}
|
||||
#endif // JPEGXL_ENABLE_JPEG
|
||||
|
||||
Status EncodeImageJPGCoefficients(const CodecInOut* io, PaddedBytes* bytes) {
|
||||
auto write = [&bytes](const uint8_t* buf, size_t len) {
|
||||
bytes->append(buf, buf + len);
|
||||
return len;
|
||||
};
|
||||
return jpeg::WriteJpeg(*io->Main().jpeg_data, write);
|
||||
}
|
||||
|
||||
Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
|
||||
YCbCrChromaSubsampling chroma_subsampling,
|
||||
|
@ -503,12 +475,12 @@ Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
|
|||
return JXL_FAILURE("please specify a 0-100 JPEG quality");
|
||||
}
|
||||
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
const ImageBundle* ib;
|
||||
ImageMetadata metadata = io->metadata.m;
|
||||
ImageBundle ib_store(&metadata);
|
||||
JXL_RETURN_IF_ERROR(TransformIfNeeded(
|
||||
io->Main(), io->metadata.m.color_encoding, pool, &ib_store, &ib));
|
||||
JXL_RETURN_IF_ERROR(TransformIfNeeded(io->Main(),
|
||||
io->metadata.m.color_encoding,
|
||||
GetJxlCms(), pool, &ib_store, &ib));
|
||||
|
||||
switch (encoder) {
|
||||
case JpegEncoder::kLibJpeg:
|
||||
|
@ -524,9 +496,6 @@ Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
|
|||
}
|
||||
|
||||
return true;
|
||||
#else // JPEGXL_ENABLE_JPEG
|
||||
return JXL_FAILURE("JPEG pixel encoding not enabled at build time");
|
||||
#endif // JPEGXL_ENABLE_JPEG
|
||||
}
|
||||
|
||||
} // namespace extras
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_ENC_JPG_H_
|
||||
#define LIB_EXTRAS_ENC_JPG_H_
|
||||
|
||||
// Encodes JPG pixels and metadata in memory.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/extras/codec.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
enum class JpegEncoder {
|
||||
kLibJpeg,
|
||||
kSJpeg,
|
||||
};
|
||||
|
||||
// Encodes into `bytes`.
|
||||
Status EncodeImageJPG(const CodecInOut* io, JpegEncoder encoder, size_t quality,
|
||||
YCbCrChromaSubsampling chroma_subsampling,
|
||||
ThreadPool* pool, PaddedBytes* bytes);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_ENC_JPG_H_
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/enc/pgx.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "lib/jxl/base/bits.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/file_io.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
#include "lib/jxl/color_management.h"
|
||||
#include "lib/jxl/dec_external_image.h"
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
#include "lib/jxl/enc_external_image.h"
|
||||
#include "lib/jxl/enc_image_bundle.h"
|
||||
#include "lib/jxl/fields.h" // AllDefault
|
||||
#include "lib/jxl/image.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
namespace {
|
||||
|
||||
constexpr size_t kMaxHeaderSize = 200;
|
||||
|
||||
Status EncodeHeader(const ImageBundle& ib, const size_t bits_per_sample,
|
||||
char* header, int* JXL_RESTRICT chars_written) {
|
||||
if (ib.HasAlpha()) return JXL_FAILURE("PGX: can't store alpha");
|
||||
if (!ib.IsGray()) return JXL_FAILURE("PGX: must be grayscale");
|
||||
// TODO(lode): verify other bit depths: for other bit depths such as 1 or 4
|
||||
// bits, have a test case to verify it works correctly. For bits > 16, we may
|
||||
// need to change the way external_image works.
|
||||
if (bits_per_sample != 8 && bits_per_sample != 16) {
|
||||
return JXL_FAILURE("PGX: bits other than 8 or 16 not yet supported");
|
||||
}
|
||||
|
||||
// Use ML (Big Endian), LM may not be well supported by all decoders.
|
||||
*chars_written = snprintf(header, kMaxHeaderSize,
|
||||
"PG ML + %" PRIuS " %" PRIuS " %" PRIuS "\n",
|
||||
bits_per_sample, ib.xsize(), ib.ysize());
|
||||
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
|
||||
kMaxHeaderSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||
size_t bits_per_sample, ThreadPool* pool,
|
||||
PaddedBytes* bytes) {
|
||||
if (!Bundle::AllDefault(io->metadata.m)) {
|
||||
JXL_WARNING("PGX encoder ignoring metadata - use a different codec");
|
||||
}
|
||||
if (!c_desired.IsSRGB()) {
|
||||
JXL_WARNING(
|
||||
"PGX encoder cannot store custom ICC profile; decoder\n"
|
||||
"will need hint key=color_space to get the same values");
|
||||
}
|
||||
|
||||
ImageBundle ib = io->Main().Copy();
|
||||
|
||||
ImageMetadata metadata = io->metadata.m;
|
||||
ImageBundle store(&metadata);
|
||||
const ImageBundle* transformed;
|
||||
JXL_RETURN_IF_ERROR(TransformIfNeeded(ib, c_desired, GetJxlCms(), pool,
|
||||
&store, &transformed));
|
||||
PaddedBytes pixels(ib.xsize() * ib.ysize() *
|
||||
(bits_per_sample / kBitsPerByte));
|
||||
size_t stride = ib.xsize() * (bits_per_sample / kBitsPerByte);
|
||||
JXL_RETURN_IF_ERROR(
|
||||
ConvertToExternal(*transformed, bits_per_sample,
|
||||
/*float_out=*/false,
|
||||
/*num_channels=*/1, JXL_BIG_ENDIAN, stride, pool,
|
||||
pixels.data(), pixels.size(), /*out_callback=*/nullptr,
|
||||
/*out_opaque=*/nullptr, metadata.GetOrientation()));
|
||||
|
||||
char header[kMaxHeaderSize];
|
||||
int header_size = 0;
|
||||
JXL_RETURN_IF_ERROR(EncodeHeader(ib, bits_per_sample, header, &header_size));
|
||||
|
||||
bytes->resize(static_cast<size_t>(header_size) + pixels.size());
|
||||
memcpy(bytes->data(), header, static_cast<size_t>(header_size));
|
||||
memcpy(bytes->data() + header_size, pixels.data(), pixels.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
|
@ -3,15 +3,14 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_CODEC_PGX_H_
|
||||
#define LIB_EXTRAS_CODEC_PGX_H_
|
||||
#ifndef LIB_EXTRAS_ENC_PGX_H_
|
||||
#define LIB_EXTRAS_ENC_PGX_H_
|
||||
|
||||
// Encodes/decodes PGX pixels in memory.
|
||||
// Encodes PGX pixels in memory.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/extras/color_hints.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
|
@ -23,11 +22,6 @@
|
|||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `io`.
|
||||
Status DecodeImagePGX(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, PackedPixelFile* ppf);
|
||||
|
||||
// Transforms from io->c_current to `c_desired` and encodes into `bytes`.
|
||||
Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||
size_t bits_per_sample, ThreadPool* pool,
|
||||
|
@ -36,4 +30,4 @@ Status EncodeImagePGX(const CodecInOut* io, const ColorEncoding& c_desired,
|
|||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_CODEC_PGX_H_
|
||||
#endif // LIB_EXTRAS_ENC_PGX_H_
|
|
@ -0,0 +1,149 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/enc/pnm.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#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/base/status.h"
|
||||
#include "lib/jxl/color_management.h"
|
||||
#include "lib/jxl/dec_external_image.h"
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
#include "lib/jxl/enc_external_image.h"
|
||||
#include "lib/jxl/enc_image_bundle.h"
|
||||
#include "lib/jxl/fields.h" // AllDefault
|
||||
#include "lib/jxl/image.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
namespace {
|
||||
|
||||
constexpr size_t kMaxHeaderSize = 200;
|
||||
|
||||
Status EncodeHeader(const ImageBundle& ib, const size_t bits_per_sample,
|
||||
const bool little_endian, char* header,
|
||||
int* JXL_RESTRICT chars_written) {
|
||||
if (ib.HasAlpha()) return JXL_FAILURE("PNM: can't store alpha");
|
||||
|
||||
if (bits_per_sample == 32) { // PFM
|
||||
const char type = ib.IsGray() ? 'f' : 'F';
|
||||
const double scale = little_endian ? -1.0 : 1.0;
|
||||
*chars_written =
|
||||
snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%.1f\n",
|
||||
type, ib.oriented_xsize(), ib.oriented_ysize(), scale);
|
||||
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
|
||||
kMaxHeaderSize);
|
||||
} else if (bits_per_sample == 1) { // PBM
|
||||
if (!ib.IsGray()) {
|
||||
return JXL_FAILURE("Cannot encode color as PBM");
|
||||
}
|
||||
*chars_written =
|
||||
snprintf(header, kMaxHeaderSize, "P4\n%" PRIuS " %" PRIuS "\n",
|
||||
ib.oriented_xsize(), ib.oriented_ysize());
|
||||
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
|
||||
kMaxHeaderSize);
|
||||
} else { // PGM/PPM
|
||||
const uint32_t max_val = (1U << bits_per_sample) - 1;
|
||||
if (max_val >= 65536) return JXL_FAILURE("PNM cannot have > 16 bits");
|
||||
const char type = ib.IsGray() ? '5' : '6';
|
||||
*chars_written =
|
||||
snprintf(header, kMaxHeaderSize, "P%c\n%" PRIuS " %" PRIuS "\n%u\n",
|
||||
type, ib.oriented_xsize(), ib.oriented_ysize(), max_val);
|
||||
JXL_RETURN_IF_ERROR(static_cast<unsigned int>(*chars_written) <
|
||||
kMaxHeaderSize);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Span<const uint8_t> MakeSpan(const char* str) {
|
||||
return Span<const uint8_t>(reinterpret_cast<const uint8_t*>(str),
|
||||
strlen(str));
|
||||
}
|
||||
|
||||
// Flip the image vertically for loading/saving PFM files which have the
|
||||
// scanlines inverted.
|
||||
void VerticallyFlipImage(Image3F* const image) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
for (size_t y = 0; y < image->ysize() / 2; y++) {
|
||||
float* first_row = image->PlaneRow(c, y);
|
||||
float* other_row = image->PlaneRow(c, image->ysize() - y - 1);
|
||||
for (size_t x = 0; x < image->xsize(); ++x) {
|
||||
float tmp = first_row[x];
|
||||
first_row[x] = other_row[x];
|
||||
other_row[x] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status EncodeImagePNM(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||
size_t bits_per_sample, ThreadPool* pool,
|
||||
PaddedBytes* bytes) {
|
||||
const bool floating_point = bits_per_sample > 16;
|
||||
// Choose native for PFM; PGM/PPM require big-endian (N/A for PBM)
|
||||
const JxlEndianness endianness =
|
||||
floating_point ? JXL_NATIVE_ENDIAN : JXL_BIG_ENDIAN;
|
||||
|
||||
ImageMetadata metadata_copy = io->metadata.m;
|
||||
// AllDefault sets all_default, which can cause a race condition.
|
||||
if (!Bundle::AllDefault(metadata_copy)) {
|
||||
JXL_WARNING("PNM encoder ignoring metadata - use a different codec");
|
||||
}
|
||||
if (!c_desired.IsSRGB()) {
|
||||
JXL_WARNING(
|
||||
"PNM encoder cannot store custom ICC profile; decoder\n"
|
||||
"will need hint key=color_space to get the same values");
|
||||
}
|
||||
|
||||
ImageBundle ib = io->Main().Copy();
|
||||
// In case of PFM the image must be flipped upside down since that format
|
||||
// is designed that way.
|
||||
const ImageBundle* to_color_transform = &ib;
|
||||
ImageBundle flipped;
|
||||
if (floating_point) {
|
||||
flipped = ib.Copy();
|
||||
VerticallyFlipImage(flipped.color());
|
||||
to_color_transform = &flipped;
|
||||
}
|
||||
ImageMetadata metadata = io->metadata.m;
|
||||
ImageBundle store(&metadata);
|
||||
const ImageBundle* transformed;
|
||||
JXL_RETURN_IF_ERROR(TransformIfNeeded(
|
||||
*to_color_transform, c_desired, GetJxlCms(), pool, &store, &transformed));
|
||||
size_t stride = ib.oriented_xsize() *
|
||||
(c_desired.Channels() * bits_per_sample) / kBitsPerByte;
|
||||
PaddedBytes pixels(stride * ib.oriented_ysize());
|
||||
JXL_RETURN_IF_ERROR(ConvertToExternal(
|
||||
*transformed, bits_per_sample, floating_point, c_desired.Channels(),
|
||||
endianness, stride, pool, pixels.data(), pixels.size(),
|
||||
/*out_callback=*/nullptr, /*out_opaque=*/nullptr,
|
||||
metadata.GetOrientation()));
|
||||
|
||||
char header[kMaxHeaderSize];
|
||||
int header_size = 0;
|
||||
bool is_little_endian = endianness == JXL_LITTLE_ENDIAN ||
|
||||
(endianness == JXL_NATIVE_ENDIAN && IsLittleEndian());
|
||||
JXL_RETURN_IF_ERROR(EncodeHeader(*transformed, bits_per_sample,
|
||||
is_little_endian, header, &header_size));
|
||||
|
||||
bytes->resize(static_cast<size_t>(header_size) + pixels.size());
|
||||
memcpy(bytes->data(), header, static_cast<size_t>(header_size));
|
||||
memcpy(bytes->data() + header_size, pixels.data(), pixels.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
|
@ -3,22 +3,16 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_CODEC_PNM_H_
|
||||
#define LIB_EXTRAS_CODEC_PNM_H_
|
||||
#ifndef LIB_EXTRAS_ENC_PNM_H_
|
||||
#define LIB_EXTRAS_ENC_PNM_H_
|
||||
|
||||
// Encodes/decodes PBM/PGM/PPM/PFM pixels in memory.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// TODO(janwas): workaround for incorrect Win64 codegen (cause unknown)
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/extras/color_hints.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
|
@ -26,20 +20,12 @@
|
|||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `io`. color_hints may specify "color_space", which
|
||||
// defaults to sRGB.
|
||||
Status DecodeImagePNM(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, PackedPixelFile* ppf);
|
||||
|
||||
// Transforms from io->c_current to `c_desired` and encodes into `bytes`.
|
||||
Status EncodeImagePNM(const CodecInOut* io, const ColorEncoding& c_desired,
|
||||
size_t bits_per_sample, ThreadPool* pool,
|
||||
PaddedBytes* bytes);
|
||||
|
||||
void TestCodecPNM();
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_CODEC_PNM_H_
|
||||
#endif // LIB_EXTRAS_ENC_PNM_H_
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
#include <cmath>
|
||||
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
float GetHlgGamma(const float peak_luminance, const float surround_luminance) {
|
||||
|
@ -21,10 +23,10 @@ Status HlgOOTF(ImageBundle* ib, const float gamma, ThreadPool* pool) {
|
|||
linear_rec2020.white_point = WhitePoint::kD65;
|
||||
linear_rec2020.tf.SetTransferFunction(TransferFunction::kLinear);
|
||||
JXL_RETURN_IF_ERROR(linear_rec2020.CreateICC());
|
||||
JXL_RETURN_IF_ERROR(ib->TransformTo(linear_rec2020, pool));
|
||||
JXL_RETURN_IF_ERROR(ib->TransformTo(linear_rec2020, GetJxlCms(), pool));
|
||||
|
||||
return RunOnPool(
|
||||
pool, 0, ib->ysize(), ThreadPool::SkipInit(),
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, ib->ysize(), ThreadPool::NoInit,
|
||||
[&](const int y, const int thread) {
|
||||
float* const JXL_RESTRICT rows[3] = {ib->color()->PlaneRow(0, y),
|
||||
ib->color()->PlaneRow(1, y),
|
||||
|
@ -43,7 +45,8 @@ Status HlgOOTF(ImageBundle* ib, const float gamma, ThreadPool* pool) {
|
|||
}
|
||||
}
|
||||
},
|
||||
"HlgOOTF");
|
||||
"HlgOOTF"));
|
||||
return true;
|
||||
}
|
||||
|
||||
Status HlgInverseOOTF(ImageBundle* ib, const float gamma, ThreadPool* pool) {
|
||||
|
|
|
@ -147,7 +147,11 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
|
|||
// uint case.
|
||||
io->metadata.m.bit_depth.bits_per_sample = io->Main().DetectRealBitdepth();
|
||||
}
|
||||
SetIntensityTarget(io);
|
||||
if (ppf.info.intensity_target != 0) {
|
||||
io->metadata.m.SetIntensityTarget(ppf.info.intensity_target);
|
||||
} else {
|
||||
SetIntensityTarget(io);
|
||||
}
|
||||
io->CheckMetadata();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
#include "lib/jxl/transfer_functions-inl.h"
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
|
@ -31,7 +32,7 @@ Status ToneMapFrame(const std::pair<float, float> display_nits,
|
|||
linear_rec2020.white_point = WhitePoint::kD65;
|
||||
linear_rec2020.tf.SetTransferFunction(TransferFunction::kLinear);
|
||||
JXL_RETURN_IF_ERROR(linear_rec2020.CreateICC());
|
||||
JXL_RETURN_IF_ERROR(ib->TransformTo(linear_rec2020, pool));
|
||||
JXL_RETURN_IF_ERROR(ib->TransformTo(linear_rec2020, GetJxlCms(), pool));
|
||||
|
||||
const auto eotf_inv = [&df](const V luminance) -> V {
|
||||
return TF_PQ().EncodedFromDisplay(df, luminance * Set(df, 1. / 10000));
|
||||
|
@ -69,8 +70,8 @@ Status ToneMapFrame(const std::pair<float, float> display_nits,
|
|||
const V inv_max_display_nits = Set(df, 1 / display_nits.second);
|
||||
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, ib->ysize(), ThreadPool::SkipInit(),
|
||||
[&](const int y, const int thread) {
|
||||
pool, 0, ib->ysize(), ThreadPool::NoInit,
|
||||
[&](const uint32_t y, size_t /* thread */) {
|
||||
float* const JXL_RESTRICT row_r = ib->color()->PlaneRow(0, y);
|
||||
float* const JXL_RESTRICT row_g = ib->color()->PlaneRow(1, y);
|
||||
float* const JXL_RESTRICT row_b = ib->color()->PlaneRow(2, y);
|
||||
|
@ -128,11 +129,11 @@ Status GamutMapFrame(ImageBundle* const ib, float preserve_saturation,
|
|||
linear_rec2020.white_point = WhitePoint::kD65;
|
||||
linear_rec2020.tf.SetTransferFunction(TransferFunction::kLinear);
|
||||
JXL_RETURN_IF_ERROR(linear_rec2020.CreateICC());
|
||||
JXL_RETURN_IF_ERROR(ib->TransformTo(linear_rec2020, pool));
|
||||
JXL_RETURN_IF_ERROR(ib->TransformTo(linear_rec2020, GetJxlCms(), pool));
|
||||
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, ib->ysize(), ThreadPool::SkipInit(),
|
||||
[&](const int y, const int thread) {
|
||||
pool, 0, ib->ysize(), ThreadPool::NoInit,
|
||||
[&](const uint32_t y, size_t /* thread*/) {
|
||||
float* const JXL_RESTRICT row_r = ib->color()->PlaneRow(0, y);
|
||||
float* const JXL_RESTRICT row_g = ib->color()->PlaneRow(1, y);
|
||||
float* const JXL_RESTRICT row_b = ib->color()->PlaneRow(2, y);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "benchmark/benchmark.h"
|
||||
#include "lib/extras/codec.h"
|
||||
#include "lib/extras/tone_mapping.h"
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
#include "lib/jxl/testdata.h"
|
||||
|
||||
namespace jxl {
|
||||
|
@ -24,7 +25,7 @@ static void BM_ToneMapping(benchmark::State& state) {
|
|||
linear_rec2020.white_point = WhitePoint::kD65;
|
||||
linear_rec2020.tf.SetTransferFunction(TransferFunction::kLinear);
|
||||
JXL_CHECK(linear_rec2020.CreateICC());
|
||||
JXL_CHECK(image.TransformTo(linear_rec2020));
|
||||
JXL_CHECK(image.TransformTo(linear_rec2020, GetJxlCms()));
|
||||
|
||||
for (auto _ : state) {
|
||||
state.PauseTiming();
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
/* Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style
|
||||
* license that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
/** @addtogroup libjxl_common
|
||||
* @{
|
||||
* @file cms_interface.h
|
||||
* @brief Interface to allow the injection of different color management systems
|
||||
* (CMSes, also called color management modules, or CMMs) in JPEG XL.
|
||||
*
|
||||
* A CMS is needed by the JPEG XL encoder and decoder to perform colorspace
|
||||
* conversions. This defines an interface that can be implemented for different
|
||||
* CMSes and then passed to the library.
|
||||
*/
|
||||
|
||||
#ifndef JXL_CMS_INTERFACE_H_
|
||||
#define JXL_CMS_INTERFACE_H_
|
||||
|
||||
#include "jxl/color_encoding.h"
|
||||
#include "jxl/memory_manager.h"
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Represents an input or output colorspace to a color transform, as a
|
||||
* serialized ICC profile. */
|
||||
typedef struct {
|
||||
/** The serialized ICC profile. This is guaranteed to be present and valid. */
|
||||
struct {
|
||||
const uint8_t* data;
|
||||
size_t size;
|
||||
} icc;
|
||||
|
||||
/** Structured representation of the colorspace, if applicable. If all fields
|
||||
* are different from their "unknown" value, then this is equivalent to the
|
||||
* ICC representation of the colorspace. If some are "unknown", those that are
|
||||
* not are still valid and can still be used on their own if they are useful.
|
||||
*/
|
||||
JxlColorEncoding color_encoding;
|
||||
|
||||
/** Number of components per pixel. This can be deduced from the other
|
||||
* representations of the colorspace but is provided for convenience and
|
||||
* validation. */
|
||||
size_t num_channels;
|
||||
} JxlColorProfile;
|
||||
|
||||
/** Allocates and returns the data needed for @p num_threads parallel transforms
|
||||
* from the @p input colorspace to @p output, with up to @p pixels_per_thread
|
||||
* pixels to transform per call to JxlCmsInterface::run. @p init_data comes
|
||||
* directly from the JxlCmsInterface instance. Since @c run only receives the
|
||||
* data returned by @c init, a reference to @p init_data should be kept there
|
||||
* if access to it is desired in @c run. Likewise for JxlCmsInterface::destroy.
|
||||
*
|
||||
* The ICC data in @p input and @p output is guaranteed to outlive the @c init /
|
||||
* @c run / @c destroy cycle.
|
||||
*
|
||||
* @param init_data JxlCmsInterface::init_data passed as-is.
|
||||
* @param num_threads the maximum number of threads from which
|
||||
* JxlCmsInterface::run will be called.
|
||||
* @param pixels_per_thread the maximum number of pixels that each call to
|
||||
* JxlCmsInterface::run will have to transform.
|
||||
* @param input_profile the input colorspace for the transform.
|
||||
* @param output_profile the colorspace to which JxlCmsInterface::run should
|
||||
* convert the input data.
|
||||
* @param intensity_target for colorspaces where luminance is relative
|
||||
* (essentially: not PQ), indicates the luminance at which (1, 1, 1) will
|
||||
* be displayed. This is useful for conversions between PQ and a relative
|
||||
* luminance colorspace, in either direction: @p intensity_target cd/m²
|
||||
* in PQ should map to and from (1, 1, 1) in the relative one.\n
|
||||
* It is also used for conversions to and from HLG, as it is
|
||||
* scene-referred while other colorspaces are assumed to be
|
||||
* display-referred. That is, conversions from HLG should apply the OOTF
|
||||
* for a peak display luminance of @p intensity_target, and conversions
|
||||
* to HLG should undo it. The OOTF is a gamma function applied to the
|
||||
* luminance channel (https://www.itu.int/rec/R-REC-BT.2100-2-201807-I
|
||||
* page 7), with the gamma value computed as
|
||||
* <tt>1.2 * 1.111^log2(intensity_target / 1000)</tt> (footnote 2 page 8
|
||||
* of the same document).
|
||||
* @return The data needed for the transform, or @c NULL in case of failure.
|
||||
* This will be passed to the other functions as @c user_data.
|
||||
*/
|
||||
typedef void* (*jpegxl_cms_init_func)(void* init_data, size_t num_threads,
|
||||
size_t pixels_per_thread,
|
||||
const JxlColorProfile* input_profile,
|
||||
const JxlColorProfile* output_profile,
|
||||
float intensity_target);
|
||||
|
||||
/** Returns a buffer that can be used by callers of the interface to store the
|
||||
* input of the conversion or read its result, if they pass it as the input or
|
||||
* output of the @c run function.
|
||||
* @param user_data the data returned by @c init.
|
||||
* @param thread the index of the thread for which to return a buffer.
|
||||
* @return A buffer that can be used by the caller for passing to @c run.
|
||||
*/
|
||||
typedef float* (*jpegxl_cms_get_buffer_func)(void* user_data, size_t thread);
|
||||
|
||||
/** Executes one transform and returns true on success or false on error. It
|
||||
* must be possible to call this from different threads with different values
|
||||
* for @p thread, all between 0 (inclusive) and the value of @p num_threads
|
||||
* passed to @c init (exclusive). It is allowed to implement this by locking
|
||||
* such that the transforms are essentially performed sequentially, if such a
|
||||
* performance profile is acceptable. @p user_data is the data returned by
|
||||
* @c init.
|
||||
* The buffers each contain @p num_pixels × @c num_channels interleaved floating
|
||||
* point (0..1) samples where @c num_channels is the number of color channels of
|
||||
* their respective color profiles. It is guaranteed that the only case in which
|
||||
* they might overlap is if the output has fewer channels than the input, in
|
||||
* which case the pointers may be identical.
|
||||
* For CMYK data, 0 represents the maximum amount of ink while 1 represents no
|
||||
* ink.
|
||||
* @param user_data the data returned by @c init.
|
||||
* @param thread the index of the thread from which the function is being
|
||||
* called.
|
||||
* @param input_buffer the buffer containing the pixel data to be transformed.
|
||||
* @param output_buffer the buffer receiving the transformed pixel data.
|
||||
* @param num_pixels the number of pixels to transform from @p input to
|
||||
* @p output.
|
||||
* @return JXL_TRUE on success, JXL_FALSE on failure.
|
||||
*/
|
||||
typedef JXL_BOOL (*jpegxl_cms_run_func)(void* user_data, size_t thread,
|
||||
const float* input_buffer,
|
||||
float* output_buffer,
|
||||
size_t num_pixels);
|
||||
|
||||
/** Performs the necessary clean-up and frees the memory allocated for user
|
||||
* data.
|
||||
*/
|
||||
typedef void (*jpegxl_cms_destroy_func)(void*);
|
||||
|
||||
/**
|
||||
* Interface for performing colorspace transforms. The @c init function can be
|
||||
* called several times to instantiate several transforms, including before
|
||||
* other transforms have been destroyed.
|
||||
*
|
||||
* The call sequence for a given colorspace transform could look like the
|
||||
* following:
|
||||
* @dot
|
||||
* digraph calls {
|
||||
* newrank = true
|
||||
* node [shape = box, fontname = monospace]
|
||||
* init [label = "user_data <- init(\l\
|
||||
* init_data = data,\l\
|
||||
* num_threads = 3,\l\
|
||||
* pixels_per_thread = 20,\l\
|
||||
* input = (sRGB, 3 channels),\l\
|
||||
* output = (Display-P3, 3 channels),\l\
|
||||
* intensity_target = 255\l\
|
||||
* )\l"]
|
||||
* subgraph cluster_0 {
|
||||
* color = lightgrey
|
||||
* label = "thread 1"
|
||||
* labeljust = "c"
|
||||
* run_1_1 [label = "run(\l\
|
||||
* user_data,\l\
|
||||
* thread = 1,\l\
|
||||
* input = in[0],\l\
|
||||
* output = out[0],\l\
|
||||
* num_pixels = 20\l\
|
||||
* )\l"]
|
||||
* run_1_2 [label = "run(\l\
|
||||
* user_data,\l\
|
||||
* thread = 1,\l\
|
||||
* input = in[3],\l\
|
||||
* output = out[3],\l\
|
||||
* num_pixels = 20\l\
|
||||
* )\l"]
|
||||
* }
|
||||
* subgraph cluster_1 {
|
||||
* color = lightgrey
|
||||
* label = "thread 2"
|
||||
* labeljust = "l"
|
||||
* run_2_1 [label = "run(\l\
|
||||
* user_data,\l\
|
||||
* thread = 2,\l\
|
||||
* input = in[1],\l\
|
||||
* output = out[1],\l\
|
||||
* num_pixels = 20\l\
|
||||
* )\l"]
|
||||
* run_2_2 [label = "run(\l\
|
||||
* user_data,\l\
|
||||
* thread = 2,\l\
|
||||
* input = in[4],\l\
|
||||
* output = out[4],\l\
|
||||
* num_pixels = 13\l\
|
||||
* )\l"]
|
||||
* }
|
||||
* subgraph cluster_3 {
|
||||
* color = lightgrey
|
||||
* label = "thread 3"
|
||||
* labeljust = "c"
|
||||
* run_3_1 [label = "run(\l\
|
||||
* user_data,\l\
|
||||
* thread = 3,\l\
|
||||
* input = in[2],\l\
|
||||
* output = out[2],\l\
|
||||
* num_pixels = 20\l\
|
||||
* )\l"]
|
||||
* }
|
||||
* init -> {run_1_1; run_2_1; run_3_1; rank = same}
|
||||
* run_1_1 -> run_1_2
|
||||
* run_2_1 -> run_2_2
|
||||
* {run_1_2; run_2_2, run_3_1} -> "destroy(user_data)"
|
||||
* }
|
||||
* @enddot
|
||||
*/
|
||||
typedef struct {
|
||||
/** CMS-specific data that will be passed to @ref init. */
|
||||
void* init_data;
|
||||
/** Prepares a colorspace transform as described in the documentation of @ref
|
||||
* jpegxl_cms_init_func. */
|
||||
jpegxl_cms_init_func init;
|
||||
/** Returns a buffer that can be used as input to @c run. */
|
||||
jpegxl_cms_get_buffer_func get_src_buf;
|
||||
/** Returns a buffer that can be used as output from @c run. */
|
||||
jpegxl_cms_get_buffer_func get_dst_buf;
|
||||
/** Executes the transform on a batch of pixels, per @ref jpegxl_cms_run_func.
|
||||
*/
|
||||
jpegxl_cms_run_func run;
|
||||
/** Cleans up the transform. */
|
||||
jpegxl_cms_destroy_func destroy;
|
||||
} JxlCmsInterface;
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* JXL_CMS_INTERFACE_H_ */
|
||||
|
||||
/** @} */
|
|
@ -72,6 +72,15 @@ typedef struct {
|
|||
uint32_t ysize;
|
||||
} JxlPreviewHeader;
|
||||
|
||||
/** The intrinsic size header */
|
||||
typedef struct {
|
||||
/** Intrinsic width in pixels */
|
||||
uint32_t xsize;
|
||||
|
||||
/** Intrinsic height in pixels */
|
||||
uint32_t ysize;
|
||||
} JxlIntrinsicSizeHeader;
|
||||
|
||||
/** The codestream animation header, optionally present in the beginning of
|
||||
* the codestream, and if it is it applies to all animation frames, unlike
|
||||
* JxlFrameHeader which applies to an individual frame.
|
||||
|
@ -232,10 +241,26 @@ typedef struct {
|
|||
*/
|
||||
JxlAnimationHeader animation;
|
||||
|
||||
/** Intrinsic width of the image.
|
||||
* The intrinsic size can be different from the actual size in pixels
|
||||
* (as given by xsize and ysize) and it denotes the recommended dimensions
|
||||
* for displaying the image, i.e. applications are advised to resample the
|
||||
* decoded image to the intrinsic dimensions.
|
||||
*/
|
||||
uint32_t intrinsic_xsize;
|
||||
|
||||
/** Intrinsic heigth of the image.
|
||||
* The intrinsic size can be different from the actual size in pixels
|
||||
* (as given by xsize and ysize) and it denotes the recommended dimensions
|
||||
* for displaying the image, i.e. applications are advised to resample the
|
||||
* decoded image to the intrinsic dimensions.
|
||||
*/
|
||||
uint32_t intrinsic_ysize;
|
||||
|
||||
/** Padding for forwards-compatibility, in case more fields are exposed
|
||||
* in a future version of the library.
|
||||
*/
|
||||
uint8_t padding[108];
|
||||
uint8_t padding[100];
|
||||
} JxlBasicInfo;
|
||||
|
||||
/** Information for a single extra channel.
|
||||
|
@ -290,7 +315,7 @@ typedef struct {
|
|||
} JxlHeaderExtensions;
|
||||
|
||||
/** Frame blend modes.
|
||||
* If coalescing is enabled (default), this can be ignored.
|
||||
* When decoding, if coalescing is enabled (default), this can be ignored.
|
||||
*/
|
||||
typedef enum {
|
||||
JXL_BLEND_REPLACE = 0,
|
||||
|
@ -301,11 +326,12 @@ typedef enum {
|
|||
} JxlBlendMode;
|
||||
|
||||
/** The information about blending the color channels or a single extra channel.
|
||||
* If coalescing is enabled (default), this can be ignored.
|
||||
* When decoding, if coalescing is enabled (default), this can be ignored and
|
||||
* the blend mode is considered to be JXL_BLEND_REPLACE.
|
||||
* When encoding, these settings apply to the pixel data given to the encoder.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Blend mode.
|
||||
* Always equal to JXL_BLEND_REPLACE if coalescing is enabled.
|
||||
*/
|
||||
JxlBlendMode blendmode;
|
||||
/** Reference frame ID to use as the 'bottom' layer (0-3).
|
||||
|
@ -321,32 +347,43 @@ typedef struct {
|
|||
} JxlBlendInfo;
|
||||
|
||||
/** The information about layers.
|
||||
* If coalescing is enabled (default), this can be ignored.
|
||||
* When decoding, if coalescing is enabled (default), this can be ignored.
|
||||
* When encoding, these settings apply to the pixel data given to the encoder,
|
||||
* the encoder could choose an internal representation that differs.
|
||||
*/
|
||||
typedef struct {
|
||||
/** Horizontal offset of the frame (can be negative, always zero if coalescing
|
||||
* is enabled)
|
||||
/** Whether cropping is applied for this frame. When decoding, if false,
|
||||
* crop_x0 and crop_y0 are set to zero, and xsize and ysize to the main
|
||||
* image dimensions. When encoding and this is false, those fields are
|
||||
* ignored. When decoding, if coalescing is enabled (default), this is always
|
||||
* false, regardless of the internal encoding in the JPEG XL codestream.
|
||||
*/
|
||||
JXL_BOOL have_crop;
|
||||
|
||||
/** Horizontal offset of the frame (can be negative).
|
||||
*/
|
||||
int32_t crop_x0;
|
||||
/** Vertical offset of the frame (can be negative, always zero if coalescing
|
||||
* is enabled)
|
||||
|
||||
/** Vertical offset of the frame (can be negative).
|
||||
*/
|
||||
int32_t crop_y0;
|
||||
/** Width of the frame (number of columns, always equal to image width if
|
||||
* coalescing is enabled)
|
||||
|
||||
/** Width of the frame (number of columns).
|
||||
*/
|
||||
uint32_t xsize;
|
||||
/** Height of the frame (number of rows, always equal to image height if
|
||||
* coalescing is enabled)
|
||||
|
||||
/** Height of the frame (number of rows).
|
||||
*/
|
||||
uint32_t ysize;
|
||||
|
||||
/** The blending info for the color channels. Blending info for extra channels
|
||||
* has to be retrieved separately using JxlDecoderGetExtraChannelBlendInfo.
|
||||
*/
|
||||
JxlBlendInfo blend_info;
|
||||
|
||||
/** After blending, save the frame as reference frame with this ID (0-3).
|
||||
* Special case: if the frame duration is nonzero, ID 0 means "will not be
|
||||
* referenced in the future".
|
||||
* referenced in the future". This value is not used for the last frame.
|
||||
*/
|
||||
uint32_t save_as_reference;
|
||||
} JxlLayerInfo;
|
||||
|
@ -369,11 +406,16 @@ typedef struct {
|
|||
uint32_t timecode;
|
||||
|
||||
/** Length of the frame name in bytes, or 0 if no name.
|
||||
* Excludes null termination character.
|
||||
* Excludes null termination character. This value is set by the decoder.
|
||||
* For the encoder, this value is ignored and @ref JxlEncoderSetFrameName is
|
||||
* used instead to set the name and the length.
|
||||
*/
|
||||
uint32_t name_length;
|
||||
|
||||
/** Indicates this is the last animation frame.
|
||||
/** Indicates this is the last animation frame. This value is set by the
|
||||
* decoder to indicate no further frames follow. For the encoder, it is not
|
||||
* required to set this value and it is ignored, @ref JxlEncoderCloseFrames is
|
||||
* used to indicate the last frame to the encoder instead.
|
||||
*/
|
||||
JXL_BOOL is_last;
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "jxl/cms_interface.h"
|
||||
#include "jxl/codestream_header.h"
|
||||
#include "jxl/color_encoding.h"
|
||||
#include "jxl/jxl_export.h"
|
||||
|
@ -113,9 +114,9 @@ JXL_EXPORT void JxlDecoderDestroy(JxlDecoder* dec);
|
|||
|
||||
/**
|
||||
* Return value for JxlDecoderProcessInput.
|
||||
* The values above 0x40 are optional informal events that can be subscribed to,
|
||||
* they are never returned if they have not been registered with
|
||||
* JxlDecoderSubscribeEvents.
|
||||
* The values from JXL_DEC_BASIC_INFO onwards are optional informative
|
||||
* events that can be subscribed to, they are never returned if they
|
||||
* have not been registered with JxlDecoderSubscribeEvents.
|
||||
*/
|
||||
typedef enum {
|
||||
/** Function call finished successfully, or decoding is finished and there is
|
||||
|
@ -152,8 +153,8 @@ typedef enum {
|
|||
* requested and it is possible to decode a DC image from the codestream and
|
||||
* the DC out buffer was not yet set. This event re-occurs for new frames
|
||||
* if there are multiple animation frames.
|
||||
* DEPRECATED: the DC feature in this form will be removed. You can use
|
||||
* JxlDecoderFlushImage for progressive rendering.
|
||||
* DEPRECATED: the DC feature in this form will be removed. For progressive
|
||||
* rendering, JxlDecoderFlushImage should be used.
|
||||
*/
|
||||
JXL_DEC_NEED_DC_OUT_BUFFER = 4,
|
||||
|
||||
|
@ -228,8 +229,8 @@ typedef enum {
|
|||
* status only indicates we're past this point in the codestream. This event
|
||||
* occurs max once per frame and always later than JXL_DEC_FRAME_HEADER
|
||||
* and other header events and earlier than full resolution pixel data.
|
||||
* DEPRECATED: the DC feature in this form will be removed. You can use
|
||||
* JxlDecoderFlushImage for progressive rendering.
|
||||
* DEPRECATED: the DC feature in this form will be removed. For progressive
|
||||
* rendering, JxlDecoderFlushImage should be used.
|
||||
*/
|
||||
JXL_DEC_DC_IMAGE = 0x800,
|
||||
|
||||
|
@ -263,17 +264,18 @@ typedef enum {
|
|||
* @see JxlDecoderSetDecompressBoxes to configure whether to get the box
|
||||
* data decompressed, or possibly compressed.
|
||||
*
|
||||
* Boxes can be compressed. This is so when their box type is "brob". In that
|
||||
* case, they have an underlying decompressed box type and decompressed data.
|
||||
* Use JxlDecoderSetDecompressBoxes to configure which data to get,
|
||||
* decompressing them requires Brotli. JxlDecoderGetBoxType has a flag to
|
||||
* get the compressed box type, which can be "brob", or the decompressed box
|
||||
* type. If a box is not compressed (its compressed type is not "brob"), then
|
||||
* you get the same decompressed box type and data no matter what setting is
|
||||
* configured.
|
||||
* Boxes can be compressed. This is so when their box type is
|
||||
* "brob". In that case, they have an underlying decompressed box
|
||||
* type and decompressed data. JxlDecoderSetDecompressBoxes allows
|
||||
* configuring which data to get. Decompressing requires
|
||||
* Brotli. JxlDecoderGetBoxType has a flag to get the compressed box
|
||||
* type, which can be "brob", or the decompressed box type. If a box
|
||||
* is not compressed (its compressed type is not "brob"), then
|
||||
* the output decompressed box type and data is independent of what
|
||||
* setting is configured.
|
||||
*
|
||||
* The buffer set with JxlDecoderSetBoxBuffer must be set again for each next
|
||||
* box that you want to get, or can be left unset to skip outputting this box.
|
||||
* box to be obtained, or can be left unset to skip outputting this box.
|
||||
* The output buffer contains the full box data when the next JXL_DEC_BOX
|
||||
* event or JXL_DEC_SUCCESS occurs. JXL_DEC_BOX occurs for all boxes,
|
||||
* including non-metadata boxes such as the signature box or codestream boxes.
|
||||
|
@ -282,6 +284,19 @@ typedef enum {
|
|||
* "jumb" respectively.
|
||||
*/
|
||||
JXL_DEC_BOX = 0x4000,
|
||||
|
||||
/** Informative event by JxlDecoderProcessInput: a progressive step in
|
||||
* decoding the frame is reached. When calling @ref JxlDecoderFlushImage at
|
||||
* this point, the flushed image will correspond exactly to this point in
|
||||
* decoding, and not yet contain partial results (such as partially more fine
|
||||
* detail) of a next step. By default, this event will trigger maximum once
|
||||
* per frame, when a 8x8th resolution (DC) image is ready (the image data is
|
||||
* still returned at full resolution, giving upscaled DC). Use @ref
|
||||
* JxlDecoderSetProgressiveDetail to configure more fine-grainedness. The
|
||||
* event is not guaranteed to trigger, not all images have progressive steps
|
||||
* or DC encoded.
|
||||
*/
|
||||
JXL_DEC_FRAME_PROGRESSION = 0x8000,
|
||||
} JxlDecoderStatus;
|
||||
|
||||
/** Rewinds decoder to the beginning. The same input must be given again from
|
||||
|
@ -377,27 +392,28 @@ JXL_EXPORT size_t JxlDecoderSizeHintBasicInfo(const JxlDecoder* dec);
|
|||
JXL_EXPORT JxlDecoderStatus JxlDecoderSubscribeEvents(JxlDecoder* dec,
|
||||
int events_wanted);
|
||||
|
||||
/** Enables or disables preserving of original orientation. Some images are
|
||||
* encoded with an orientation tag indicating the image is rotated and/or
|
||||
* mirrored (here called the original orientation).
|
||||
/** Enables or disables preserving of as-in-bitstream pixeldata
|
||||
* orientation. Some images are encoded with an Orientation tag
|
||||
* indicating that the decoder must perform a rotation and/or
|
||||
* mirroring to the encoded image data.
|
||||
*
|
||||
* *) If keep_orientation is JXL_FALSE (the default): the decoder will perform
|
||||
* work to undo the transformation. This ensures the decoded pixels will not
|
||||
* be rotated or mirrored. The decoder will always set the orientation field
|
||||
* of the JxlBasicInfo to JXL_ORIENT_IDENTITY to match the returned pixel data.
|
||||
* The decoder may also swap xsize and ysize in the JxlBasicInfo compared to the
|
||||
* values inside of the codestream, to correctly match the decoded pixel data,
|
||||
* e.g. when a 90 degree rotation was performed.
|
||||
* *) If skip_reorientation is JXL_FALSE (the default): the decoder
|
||||
* will apply the transformation from the orientation setting, hence
|
||||
* rendering the image according to its specified intent. When
|
||||
* producing a JxlBasicInfo, the decoder will always set the
|
||||
* orientation field to JXL_ORIENT_IDENTITY (matching the returned
|
||||
* pixel data) and also align xsize and ysize so that they correspond
|
||||
* to the width and the height of the returned pixel data.
|
||||
*
|
||||
* *) If this option is JXL_TRUE: then the image is returned as-is, which may be
|
||||
* rotated or mirrored, and the user must check the orientation field in
|
||||
* JxlBasicInfo after decoding to correctly interpret the decoded pixel data.
|
||||
* *) If skip_reorientation is JXL_TRUE: the decoder will skip
|
||||
* applying the transformation from the orientation setting, returning
|
||||
* the image in the as-in-bitstream pixeldata orientation.
|
||||
* This may be faster to decode since the decoder doesn't have to apply the
|
||||
* transformation, but can cause wrong display of the image if the orientation
|
||||
* tag is not correctly taken into account by the user.
|
||||
*
|
||||
* By default, this option is disabled, and the decoder automatically corrects
|
||||
* the orientation.
|
||||
* By default, this option is disabled, and the returned pixel data is
|
||||
* re-oriented according to the image's Orientation setting.
|
||||
*
|
||||
* This function must be called at the beginning, before decoding is performed.
|
||||
*
|
||||
|
@ -405,11 +421,11 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSubscribeEvents(JxlDecoder* dec,
|
|||
* possible values.
|
||||
*
|
||||
* @param dec decoder object
|
||||
* @param keep_orientation JXL_TRUE to enable, JXL_FALSE to disable.
|
||||
* @param skip_reorientation JXL_TRUE to enable, JXL_FALSE to disable.
|
||||
* @return JXL_DEC_SUCCESS if no error, JXL_DEC_ERROR otherwise.
|
||||
*/
|
||||
JXL_EXPORT JxlDecoderStatus
|
||||
JxlDecoderSetKeepOrientation(JxlDecoder* dec, JXL_BOOL keep_orientation);
|
||||
JxlDecoderSetKeepOrientation(JxlDecoder* dec, JXL_BOOL skip_reorientation);
|
||||
|
||||
/** Enables or disables rendering spot colors. By default, spot colors
|
||||
* are rendered, which is OK for viewing the decoded image. If render_spotcolors
|
||||
|
@ -469,7 +485,7 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetCoalescing(JxlDecoder* dec,
|
|||
* have not been seen yet.
|
||||
* @return JXL_DEC_ERROR when decoding failed, e.g. invalid codestream.
|
||||
* TODO(lode) document the input data mechanism
|
||||
* @return JXL_DEC_NEED_MORE_INPUT more input data is necessary.
|
||||
* @return JXL_DEC_NEED_MORE_INPUT when more input data is necessary.
|
||||
* @return JXL_DEC_BASIC_INFO when basic info such as image dimensions is
|
||||
* available and this informative event is subscribed to.
|
||||
* @return JXL_DEC_EXTENSIONS when JPEG XL codestream user extensions are
|
||||
|
@ -479,7 +495,7 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetCoalescing(JxlDecoder* dec,
|
|||
* @return JXL_DEC_PREVIEW_IMAGE when preview pixel information is available and
|
||||
* output in the preview buffer.
|
||||
* @return JXL_DEC_DC_IMAGE when DC pixel information (8x8 downscaled version
|
||||
* of the image) is available and output in the DC buffer.
|
||||
* of the image) is available and output is in the DC buffer.
|
||||
* @return JXL_DEC_FULL_IMAGE when all pixel information at highest detail is
|
||||
* available and has been output in the pixel buffer.
|
||||
*/
|
||||
|
@ -608,8 +624,8 @@ typedef enum {
|
|||
* It is often possible to use JxlDecoderGetColorAsICCProfile as an
|
||||
* alternative anyway. The following scenarios are possible:
|
||||
* - The JPEG XL image has an attached ICC Profile, in that case, the encoded
|
||||
* structured data is not available, this function will return an error status
|
||||
* and you must use JxlDecoderGetColorAsICCProfile instead.
|
||||
* structured data is not available, this function will return an error
|
||||
* status. JxlDecoderGetColorAsICCProfile should be called instead.
|
||||
* - The JPEG XL image has an encoded structured color profile, and it
|
||||
* represents an RGB or grayscale color space. This function will return it.
|
||||
* You can still use JxlDecoderGetColorAsICCProfile as well as an
|
||||
|
@ -621,10 +637,10 @@ typedef enum {
|
|||
* an unknown or xyb color space. In that case,
|
||||
* JxlDecoderGetColorAsICCProfile is not available.
|
||||
*
|
||||
* If you wish to render the image using a system that supports ICC profiles,
|
||||
* use JxlDecoderGetColorAsICCProfile first. If you're looking for a specific
|
||||
* color space possibly indicated in the JPEG XL image, use
|
||||
* JxlDecoderGetColorAsEncodedProfile first.
|
||||
* When rendering an image on a system that supports ICC profiles,
|
||||
* JxlDecoderGetColorAsICCProfile should be used first. When rendering
|
||||
* for a specific color space, possibly indicated in the JPEG XL
|
||||
* image, JxlDecoderGetColorAsEncodedProfile should be used first.
|
||||
*
|
||||
* @param dec decoder object
|
||||
* @param format pixel format to output the data to. Only used for
|
||||
|
@ -818,7 +834,7 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetExtraChannelBlendInfo(
|
|||
* @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as
|
||||
* information not available yet.
|
||||
*
|
||||
* DEPRECATED: the DC feature in this form will be removed. You can use
|
||||
* DEPRECATED: the DC feature in this form will be removed. Use
|
||||
* JxlDecoderFlushImage for progressive rendering.
|
||||
*/
|
||||
JXL_EXPORT JXL_DEPRECATED JxlDecoderStatus JxlDecoderDCOutBufferSize(
|
||||
|
@ -839,7 +855,7 @@ JXL_EXPORT JXL_DEPRECATED JxlDecoderStatus JxlDecoderDCOutBufferSize(
|
|||
* @return JXL_DEC_SUCCESS on success, JXL_DEC_ERROR on error, such as
|
||||
* size too small.
|
||||
*
|
||||
* DEPRECATED: the DC feature in this form will be removed. You can use
|
||||
* DEPRECATED: the DC feature in this form will be removed. Use
|
||||
* JxlDecoderFlushImage for progressive rendering.
|
||||
*/
|
||||
JXL_EXPORT JXL_DEPRECATED JxlDecoderStatus JxlDecoderSetDCOutBuffer(
|
||||
|
@ -883,8 +899,8 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderSetImageOutBuffer(
|
|||
JxlDecoder* dec, const JxlPixelFormat* format, void* buffer, size_t size);
|
||||
|
||||
/**
|
||||
* Callback function type for JxlDecoderSetImageOutCallback. @see
|
||||
* JxlDecoderSetImageOutCallback for usage.
|
||||
* Function type for JxlDecoderSetImageOutCallback.
|
||||
* @see JxlDecoderSetImageOutCallback for usage.
|
||||
*
|
||||
* The callback may be called simultaneously by different threads when using a
|
||||
* threaded parallel runner, on different pixels.
|
||||
|
@ -1137,6 +1153,20 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderGetBoxType(JxlDecoder* dec,
|
|||
JXL_EXPORT JxlDecoderStatus JxlDecoderGetBoxSizeRaw(const JxlDecoder* dec,
|
||||
uint64_t* size);
|
||||
|
||||
/**
|
||||
* Configures at which progressive steps in frame decoding the @ref
|
||||
* JXL_DEC_FRAME_PROGRESSION event occurs. By default, this is 0. The detail
|
||||
* values mean: 0 = only trigger for the DC image, the 8x8th lower resolution
|
||||
* image. 1 = also trigger when a full pass of groups is ready. Higher values
|
||||
* indicate more steps but are not yet implemented. Higher values always include
|
||||
* the events of lower values as well.
|
||||
*
|
||||
* @param dec decoder object
|
||||
* @param detail at which level of detail to trigger JXL_DEC_FRAME_PROGRESSION
|
||||
*/
|
||||
JXL_EXPORT void JxlDecoderSetProgressiveDetail(JxlDecoder* dec,
|
||||
uint32_t detail);
|
||||
|
||||
/**
|
||||
* Outputs progressive step towards the decoded image so far when only partial
|
||||
* input was received. If the flush was successful, the buffer set with
|
||||
|
|
|
@ -41,14 +41,18 @@ JXL_EXPORT uint32_t JxlEncoderVersion(void);
|
|||
typedef struct JxlEncoderStruct JxlEncoder;
|
||||
|
||||
/**
|
||||
* Opaque structure that holds frame specific encoding options for a JPEG XL
|
||||
* encoder.
|
||||
* Settings and metadata for a single image frame. This includes encoder options
|
||||
* for a frame such as compression quality and speed.
|
||||
*
|
||||
* Allocated and initialized with JxlEncoderOptionsCreate().
|
||||
* Allocated and initialized with JxlEncoderFrameSettingsCreate().
|
||||
* Cleaned up and deallocated when the encoder is destroyed with
|
||||
* JxlEncoderDestroy().
|
||||
*/
|
||||
typedef struct JxlEncoderOptionsStruct JxlEncoderOptions;
|
||||
typedef struct JxlEncoderFrameSettingsStruct JxlEncoderFrameSettings;
|
||||
|
||||
/** DEPRECATED: Use JxlEncoderFrameSettings instead.
|
||||
*/
|
||||
typedef JxlEncoderFrameSettings JxlEncoderOptions;
|
||||
|
||||
/**
|
||||
* Return value for multiple encoder functions.
|
||||
|
@ -67,19 +71,17 @@ typedef enum {
|
|||
*/
|
||||
JXL_ENC_NEED_MORE_OUTPUT = 2,
|
||||
|
||||
/** The encoder doesn't (yet) support this.
|
||||
/** DEPRECATED: the encoder does not return this status and there is no need
|
||||
* to handle or expect it.
|
||||
*/
|
||||
JXL_ENC_NOT_SUPPORTED = 3,
|
||||
|
||||
} JxlEncoderStatus;
|
||||
|
||||
/**
|
||||
* Id of per-frame options to set to JxlEncoderOptions with
|
||||
* JxlEncoderOptionsSetInteger.
|
||||
* NOTE: this enum includes most but not all encoder options. The image quality
|
||||
* is a frame option that can be set with JxlEncoderOptionsSetDistance instead.
|
||||
* Options that apply globally, rather than per-frame, are set with their own
|
||||
* functions and do not use the per-frame JxlEncoderOptions.
|
||||
* Id of encoder options for a frame. This includes options such as the
|
||||
* image quality and compression speed for this frame. This does not include
|
||||
* non-frame related encoder options such as for boxes.
|
||||
*/
|
||||
typedef enum {
|
||||
/** Sets encoder effort/speed level without affecting decoding speed. Valid
|
||||
|
@ -87,13 +89,13 @@ typedef enum {
|
|||
* 4:cheetah 5:hare 6:wombat 7:squirrel 8:kitten 9:tortoise.
|
||||
* Default: squirrel (7).
|
||||
*/
|
||||
JXL_ENC_OPTION_EFFORT = 0,
|
||||
JXL_ENC_FRAME_SETTING_EFFORT = 0,
|
||||
|
||||
/** Sets the decoding speed tier for the provided options. Minimum is 0
|
||||
* (slowest to decode, best quality/density), and maximum is 4 (fastest to
|
||||
* decode, at the cost of some quality/density). Default is 0.
|
||||
*/
|
||||
JXL_ENC_OPTION_DECODING_SPEED = 1,
|
||||
JXL_ENC_FRAME_SETTING_DECODING_SPEED = 1,
|
||||
|
||||
/** Sets resampling option. If enabled, the image is downsampled before
|
||||
* compression, and upsampled to original size in the decoder. Integer option,
|
||||
|
@ -101,161 +103,161 @@ typedef enum {
|
|||
* 1 for no downsampling (1x1), 2 for 2x2 downsampling, 4 for 4x4
|
||||
* downsampling, 8 for 8x8 downsampling.
|
||||
*/
|
||||
JXL_ENC_OPTION_RESAMPLING = 2,
|
||||
JXL_ENC_FRAME_SETTING_RESAMPLING = 2,
|
||||
|
||||
/** Similar to JXL_ENC_OPTION_RESAMPLING, but for extra channels. Integer
|
||||
* option, use -1 for the default behavior (depends on encoder
|
||||
/** Similar to JXL_ENC_FRAME_SETTING_RESAMPLING, but for extra channels.
|
||||
* Integer option, use -1 for the default behavior (depends on encoder
|
||||
* implementation), 1 for no downsampling (1x1), 2 for 2x2 downsampling, 4 for
|
||||
* 4x4 downsampling, 8 for 8x8 downsampling.
|
||||
*/
|
||||
JXL_ENC_OPTION_EXTRA_CHANNEL_RESAMPLING = 3,
|
||||
JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING = 3,
|
||||
|
||||
/** Indicates the frame added with @ref JxlEncoderAddImageFrame is already
|
||||
* downsampled by the downsampling factor set with @ref
|
||||
* JXL_ENC_OPTION_RESAMPLING. The input frame must then be given in the
|
||||
* JXL_ENC_FRAME_SETTING_RESAMPLING. The input frame must then be given in the
|
||||
* downsampled resolution, not the full image resolution. The downsampled
|
||||
* resolution is given by ceil(xsize / resampling), ceil(ysize / resampling)
|
||||
* with xsize and ysize the dimensions given in the basic info, and resampling
|
||||
* the factor set with @ref JXL_ENC_OPTION_RESAMPLING.
|
||||
* the factor set with @ref JXL_ENC_FRAME_SETTING_RESAMPLING.
|
||||
* Use 0 to disable, 1 to enable. Default value is 0.
|
||||
*/
|
||||
JXL_ENC_OPTION_ALREADY_DOWNSAMPLED = 4,
|
||||
JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED = 4,
|
||||
|
||||
/** Adds noise to the image emulating photographic film noise, the higher the
|
||||
* given number, the grainier the image will be. As an example, a value of 100
|
||||
* gives low noise whereas a value of 3200 gives a lot of noise. The default
|
||||
* value is 0.
|
||||
*/
|
||||
JXL_ENC_OPTION_PHOTON_NOISE = 5,
|
||||
JXL_ENC_FRAME_SETTING_PHOTON_NOISE = 5,
|
||||
|
||||
/** Enables adaptive noise generation. This setting is not recommended for
|
||||
* use, please use JXL_ENC_OPTION_PHOTON_NOISE instead. Use -1 for the default
|
||||
* (encoder chooses), 0 to disable, 1 to enable.
|
||||
* use, please use JXL_ENC_FRAME_SETTING_PHOTON_NOISE instead. Use -1 for the
|
||||
* default (encoder chooses), 0 to disable, 1 to enable.
|
||||
*/
|
||||
JXL_ENC_OPTION_NOISE = 6,
|
||||
JXL_ENC_FRAME_SETTING_NOISE = 6,
|
||||
|
||||
/** Enables or disables dots generation. Use -1 for the default (encoder
|
||||
* chooses), 0 to disable, 1 to enable.
|
||||
*/
|
||||
JXL_ENC_OPTION_DOTS = 7,
|
||||
JXL_ENC_FRAME_SETTING_DOTS = 7,
|
||||
|
||||
/** Enables or disables patches generation. Use -1 for the default (encoder
|
||||
* chooses), 0 to disable, 1 to enable.
|
||||
*/
|
||||
JXL_ENC_OPTION_PATCHES = 8,
|
||||
JXL_ENC_FRAME_SETTING_PATCHES = 8,
|
||||
|
||||
/** Edge preserving filter level, -1 to 3. Use -1 for the default (encoder
|
||||
* chooses), 0 to 3 to set a strength.
|
||||
*/
|
||||
JXL_ENC_OPTION_EPF = 9,
|
||||
JXL_ENC_FRAME_SETTING_EPF = 9,
|
||||
|
||||
/** Enables or disables the gaborish filter. Use -1 for the default (encoder
|
||||
* chooses), 0 to disable, 1 to enable.
|
||||
*/
|
||||
JXL_ENC_OPTION_GABORISH = 10,
|
||||
JXL_ENC_FRAME_SETTING_GABORISH = 10,
|
||||
|
||||
/** Enables modular encoding. Use -1 for default (encoder
|
||||
* chooses), 0 to enforce VarDCT mode (e.g. for photographic images), 1 to
|
||||
* enforce modular mode (e.g. for lossless images).
|
||||
*/
|
||||
JXL_ENC_OPTION_MODULAR = 11,
|
||||
JXL_ENC_FRAME_SETTING_MODULAR = 11,
|
||||
|
||||
/** Enables or disables preserving color of invisible pixels. Use -1 for the
|
||||
* default (1 if lossless, 0 if lossy), 0 to disable, 1 to enable.
|
||||
*/
|
||||
JXL_ENC_OPTION_KEEP_INVISIBLE = 12,
|
||||
JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE = 12,
|
||||
|
||||
/** Determines the order in which 256x256 regions are stored in the codestream
|
||||
* for progressive rendering. Use -1 for the encoder
|
||||
* default, 0 for scanline order, 1 for center-first order.
|
||||
*/
|
||||
JXL_ENC_OPTION_GROUP_ORDER = 13,
|
||||
JXL_ENC_FRAME_SETTING_GROUP_ORDER = 13,
|
||||
|
||||
/** Determines the horizontal position of center for the center-first group
|
||||
* order. Use -1 to automatically use the middle of the image, 0..xsize to
|
||||
* specifically set it.
|
||||
*/
|
||||
JXL_ENC_OPTION_GROUP_ORDER_CENTER_X = 14,
|
||||
JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X = 14,
|
||||
|
||||
/** Determines the center for the center-first group order. Use -1 to
|
||||
* automatically use the middle of the image, 0..ysize to specifically set it.
|
||||
*/
|
||||
JXL_ENC_OPTION_GROUP_ORDER_CENTER_Y = 15,
|
||||
JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y = 15,
|
||||
|
||||
/** Enables or disables progressive encoding for modular mode. Use -1 for the
|
||||
* encoder default, 0 to disable, 1 to enable.
|
||||
*/
|
||||
JXL_ENC_OPTION_RESPONSIVE = 16,
|
||||
JXL_ENC_FRAME_SETTING_RESPONSIVE = 16,
|
||||
|
||||
/** Set the progressive mode for the AC coefficients of VarDCT, using spectral
|
||||
* progression from the DCT coefficients. Use -1 for the encoder default, 0 to
|
||||
* disable, 1 to enable.
|
||||
*/
|
||||
JXL_ENC_OPTION_PROGRESSIVE_AC = 17,
|
||||
JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC = 17,
|
||||
|
||||
/** Set the progressive mode for the AC coefficients of VarDCT, using
|
||||
* quantization of the least significant bits. Use -1 for the encoder default,
|
||||
* 0 to disable, 1 to enable.
|
||||
*/
|
||||
JXL_ENC_OPTION_QPROGRESSIVE_AC = 18,
|
||||
JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC = 18,
|
||||
|
||||
/** Set the progressive mode using lower-resolution DC images for VarDCT. Use
|
||||
* -1 for the encoder default, 0 to disable, 1 to have an extra 64x64 lower
|
||||
* resolution pass, 2 to have a 512x512 and 64x64 lower resolution pass.
|
||||
*/
|
||||
JXL_ENC_OPTION_PROGRESSIVE_DC = 19,
|
||||
JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC = 19,
|
||||
|
||||
/** Use Global channel palette if the amount of colors is smaller than this
|
||||
* percentage of range. Use 0-100 to set an explicit percentage, -1 to use the
|
||||
* encoder default. Used for modular encoding.
|
||||
*/
|
||||
JXL_ENC_OPTION_CHANNEL_COLORS_GLOBAL_PERCENT = 20,
|
||||
JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT = 20,
|
||||
|
||||
/** Use Local (per-group) channel palette if the amount of colors is smaller
|
||||
* than this percentage of range. Use 0-100 to set an explicit percentage, -1
|
||||
* to use the encoder default. Used for modular encoding.
|
||||
*/
|
||||
JXL_ENC_OPTION_CHANNEL_COLORS_GROUP_PERCENT = 21,
|
||||
JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT = 21,
|
||||
|
||||
/** Use color palette if amount of colors is smaller than or equal to this
|
||||
* amount, or -1 to use the encoder default. Used for modular encoding.
|
||||
*/
|
||||
JXL_ENC_OPTION_PALETTE_COLORS = 22,
|
||||
JXL_ENC_FRAME_SETTING_PALETTE_COLORS = 22,
|
||||
|
||||
/** Enables or disables delta palette. Use -1 for the default (encoder
|
||||
* chooses), 0 to disable, 1 to enable. Used in modular mode.
|
||||
*/
|
||||
JXL_ENC_OPTION_LOSSY_PALETTE = 23,
|
||||
JXL_ENC_FRAME_SETTING_LOSSY_PALETTE = 23,
|
||||
|
||||
/** Color transform for internal encoding: -1 = default, 0=XYB, 1=none (RGB),
|
||||
* 2=YCbCr. The XYB setting performs the forward XYB transform. None and
|
||||
* YCbCr both perform no transform, but YCbCr is used to indicate that the
|
||||
* encoded data losslessly represents YCbCr values.
|
||||
*/
|
||||
JXL_ENC_OPTION_COLOR_TRANSFORM = 24,
|
||||
JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM = 24,
|
||||
|
||||
/** Color space for modular encoding: -1=default, 0-35=reverse color transform
|
||||
* index, e.g. index 0 = none, index 6 = YCoCg.
|
||||
* The default behavior is to try several, depending on the speed setting.
|
||||
*/
|
||||
JXL_ENC_OPTION_MODULAR_COLOR_SPACE = 25,
|
||||
JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE = 25,
|
||||
|
||||
/** Group size for modular encoding: -1=default, 0=128, 1=256, 2=512, 3=1024.
|
||||
*/
|
||||
JXL_ENC_OPTION_MODULAR_GROUP_SIZE = 26,
|
||||
JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE = 26,
|
||||
|
||||
/** Predictor for modular encoding. -1 = default, 0=zero, 1=left, 2=top,
|
||||
* 3=avg0, 4=select, 5=gradient, 6=weighted, 7=topright, 8=topleft,
|
||||
* 9=leftleft, 10=avg1, 11=avg2, 12=avg3, 13=toptop predictive average 14=mix
|
||||
* 5 and 6, 15=mix everything.
|
||||
*/
|
||||
JXL_ENC_OPTION_MODULAR_PREDICTOR = 27,
|
||||
JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR = 27,
|
||||
|
||||
/** Fraction of pixels used to learn MA trees as a percentage. -1 = default,
|
||||
* 0 = no MA and fast decode, 50 = default value, 100 = all, values above
|
||||
* 100 are also permitted. Higher values use more encoder memory.
|
||||
*/
|
||||
JXL_ENC_OPTION_MODULAR_MA_TREE_LEARNING_PERCENT = 28,
|
||||
JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT = 28,
|
||||
|
||||
/** Number of extra (previous-channel) MA tree properties to use. -1 =
|
||||
* default, 0-11 = valid values. Recommended values are in the range 0 to 3,
|
||||
|
@ -263,19 +265,19 @@ typedef enum {
|
|||
* excluding color channels when using VarDCT mode). Higher value gives slower
|
||||
* encoding and slower decoding.
|
||||
*/
|
||||
JXL_ENC_OPTION_MODULAR_NB_PREV_CHANNELS = 29,
|
||||
JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS = 29,
|
||||
|
||||
/** Enable or disable CFL (chroma-from-luma) for lossless JPEG recompression.
|
||||
* -1 = default, 0 = disable CFL, 1 = enable CFL.
|
||||
*/
|
||||
JXL_ENC_OPTION_JPEG_RECON_CFL = 30,
|
||||
JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL = 30,
|
||||
|
||||
/** Enum value not to be used as an option. This value is added to force the
|
||||
* C compiler to have the enum to take a known size.
|
||||
*/
|
||||
JXL_ENC_OPTION_FILL_ENUM = 65535,
|
||||
JXL_ENC_FRAME_SETTING_FILL_ENUM = 65535,
|
||||
|
||||
} JxlEncoderOptionId;
|
||||
} JxlEncoderFrameSettingId;
|
||||
|
||||
/**
|
||||
* Creates an instance of JxlEncoder and initializes it.
|
||||
|
@ -307,6 +309,17 @@ JXL_EXPORT void JxlEncoderReset(JxlEncoder* enc);
|
|||
*/
|
||||
JXL_EXPORT void JxlEncoderDestroy(JxlEncoder* enc);
|
||||
|
||||
/**
|
||||
* Sets the color management system (CMS) that will be used for color conversion
|
||||
* (if applicable) during encoding. May only be set before starting encoding. If
|
||||
* left unset, the default CMS implementation will be used.
|
||||
*
|
||||
* @param enc encoder object.
|
||||
* @param cms structure representing a CMS implementation. See JxlCmsInterface
|
||||
* for more details.
|
||||
*/
|
||||
JXL_EXPORT void JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms);
|
||||
|
||||
/**
|
||||
* Set the parallel runner for multithreading. May only be set before starting
|
||||
* encoding.
|
||||
|
@ -335,6 +348,12 @@ JxlEncoderSetParallelRunner(JxlEncoder* enc, JxlParallelRunner parallel_runner,
|
|||
* When the return value is not JXL_ENC_ERROR or JXL_ENC_SUCCESS, the encoding
|
||||
* requires more JxlEncoderProcessOutput calls to continue.
|
||||
*
|
||||
* This encodes the frames and/or boxes added so far. If the last frame or last
|
||||
* box has been added, @ref JxlEncoderCloseInput, @ref JxlEncoderCloseFrames
|
||||
* and/or @ref JxlEncoderCloseBoxes must be called before the next
|
||||
* @ref JxlEncoderProcessOutput call, or the codestream won't be encoded
|
||||
* correctly.
|
||||
*
|
||||
* @param enc encoder object.
|
||||
* @param next_out pointer to next bytes to write to.
|
||||
* @param avail_out amount of bytes available starting from *next_out.
|
||||
|
@ -346,6 +365,80 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderProcessOutput(JxlEncoder* enc,
|
|||
uint8_t** next_out,
|
||||
size_t* avail_out);
|
||||
|
||||
/**
|
||||
* Sets the frame information for this frame to the encoder. This includes
|
||||
* animation information such as frame duration to store in the frame header.
|
||||
* The frame header fields represent the frame as passed to the encoder, but not
|
||||
* necessarily the exact values as they will be encoded file format: the encoder
|
||||
* could change crop and blending options of a frame for more efficient encoding
|
||||
* or introduce additional internal frames. Animation duration and time code
|
||||
* information is not altered since those are immutable metadata of the frame.
|
||||
*
|
||||
* It is not required to use this function, however if have_animation is set
|
||||
* to true in the basic info, then this function should be used to set the
|
||||
* time duration of this individual frame. By default individual frames have a
|
||||
* time duration of 0, making them form a composite still. See @ref
|
||||
* JxlFrameHeader for more information.
|
||||
*
|
||||
* This information is stored in the JxlEncoderFrameSettings and so is used for
|
||||
* any frame encoded with these JxlEncoderFrameSettings. It is ok to change
|
||||
* between @ref JxlEncoderAddImageFrame calls, each added image frame will have
|
||||
* the frame header that was set in the options at the time of calling
|
||||
* JxlEncoderAddImageFrame.
|
||||
*
|
||||
* The is_last and name_length fields of the JxlFrameHeader are ignored, use
|
||||
* @ref JxlEncoderCloseFrames to indicate last frame, and @ref
|
||||
* JxlEncoderSetFrameName to indicate the name and its length instead.
|
||||
* Calling this function will clear any name that was previously set with @ref
|
||||
* JxlEncoderSetFrameName.
|
||||
*
|
||||
* @param frame_settings set of options and metadata for this frame. Also
|
||||
* includes reference to the encoder object.
|
||||
* @param frame_header frame header data to set. Object owned by the caller and
|
||||
* does not need to be kept in memory, its information is copied internally.
|
||||
* @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error
|
||||
*/
|
||||
JXL_EXPORT JxlEncoderStatus
|
||||
JxlEncoderSetFrameHeader(JxlEncoderFrameSettings* frame_settings,
|
||||
const JxlFrameHeader* frame_header);
|
||||
|
||||
/**
|
||||
* Sets blend info of an extra channel. The blend info of extra channels is set
|
||||
* separately from that of the color channels, the color channels are set with
|
||||
* @ref JxlEncoderSetFrameHeader.
|
||||
*
|
||||
* @param frame_settings set of options and metadata for this frame. Also
|
||||
* includes reference to the encoder object.
|
||||
* @param index index of the extra channel to use.
|
||||
* @param blend_info blend info to set for the extra channel
|
||||
* @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error
|
||||
*/
|
||||
JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBlendInfo(
|
||||
JxlEncoderFrameSettings* frame_settings, size_t index,
|
||||
const JxlBlendInfo* blend_info);
|
||||
|
||||
/**
|
||||
* Sets the name of the animation frame. This function is optional, frames are
|
||||
* not required to have a name. This setting is a part of the frame header, and
|
||||
* the same principles as for @ref JxlEncoderSetFrameHeader apply. The
|
||||
* name_length field of JxlFrameHeader is ignored by the encoder, this function
|
||||
* determines the name length instead as the length in bytes of the C string.
|
||||
*
|
||||
* The maximum possible name length is 1071 bytes (excluding terminating null
|
||||
* character).
|
||||
*
|
||||
* Calling @ref JxlEncoderSetFrameHeader clears any name that was
|
||||
* previously set.
|
||||
*
|
||||
* @param frame_settings set of options and metadata for this frame. Also
|
||||
* includes reference to the encoder object.
|
||||
* @param frame_name name of the next frame to be encoded, as a UTF-8 encoded C
|
||||
* string (zero terminated). Owned by the caller, and copied internally.
|
||||
* @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error
|
||||
*/
|
||||
JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameName(
|
||||
JxlEncoderFrameSettings* frame_settings, const char* frame_name);
|
||||
|
||||
/**
|
||||
* Sets the buffer to read JPEG encoded bytes from for the next frame to encode.
|
||||
*
|
||||
|
@ -361,14 +454,20 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderProcessOutput(JxlEncoder* enc,
|
|||
* JxlEncoderStoreJPEGMetadata and a single JPEG frame is added, it will be
|
||||
* possible to losslessly reconstruct the JPEG codestream.
|
||||
*
|
||||
* @param options set of encoder options to use when encoding the frame.
|
||||
* If this is the last frame, @ref JxlEncoderCloseInput or @ref
|
||||
* JxlEncoderCloseFrames must be called before the next
|
||||
* @ref JxlEncoderProcessOutput call.
|
||||
*
|
||||
* @param frame_settings set of options and metadata for this frame. Also
|
||||
* includes reference to the encoder object.
|
||||
* @param buffer bytes to read JPEG from. Owned by the caller and its contents
|
||||
* are copied internally.
|
||||
* @param size size of buffer in bytes.
|
||||
* @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error
|
||||
*/
|
||||
JXL_EXPORT JxlEncoderStatus JxlEncoderAddJPEGFrame(
|
||||
const JxlEncoderOptions* options, const uint8_t* buffer, size_t size);
|
||||
JXL_EXPORT JxlEncoderStatus
|
||||
JxlEncoderAddJPEGFrame(const JxlEncoderFrameSettings* frame_settings,
|
||||
const uint8_t* buffer, size_t size);
|
||||
|
||||
/**
|
||||
* Sets the buffer to read pixels from for the next image to encode. Must call
|
||||
|
@ -411,7 +510,12 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderAddJPEGFrame(
|
|||
* uses_original_profile=false case. They are however not allowed to be NaN or
|
||||
* +-infinity.
|
||||
*
|
||||
* @param options set of encoder options to use when encoding the frame.
|
||||
* If this is the last frame, @ref JxlEncoderCloseInput or @ref
|
||||
* JxlEncoderCloseFrames must be called before the next
|
||||
* @ref JxlEncoderProcessOutput call.
|
||||
*
|
||||
* @param frame_settings set of options and metadata for this frame. Also
|
||||
* includes reference to the encoder object.
|
||||
* @param pixel_format format for pixels. Object owned by the caller and its
|
||||
* contents are copied internally.
|
||||
* @param buffer buffer type to input the pixel data from. Owned by the caller
|
||||
|
@ -420,8 +524,8 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderAddJPEGFrame(
|
|||
* @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error
|
||||
*/
|
||||
JXL_EXPORT JxlEncoderStatus JxlEncoderAddImageFrame(
|
||||
const JxlEncoderOptions* options, const JxlPixelFormat* pixel_format,
|
||||
const void* buffer, size_t size);
|
||||
const JxlEncoderFrameSettings* frame_settings,
|
||||
const JxlPixelFormat* pixel_format, const void* buffer, size_t size);
|
||||
|
||||
/**
|
||||
* Sets the buffer to read pixels from for an extra channel at a given index.
|
||||
|
@ -434,7 +538,8 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderAddImageFrame(
|
|||
* It is required to call this function for every extra channel, except for the
|
||||
* alpha channel if that was already set through @ref JxlEncoderAddImageFrame.
|
||||
*
|
||||
* @param options set of encoder options to use when encoding the extra channel.
|
||||
* @param frame_settings set of options and metadata for this frame. Also
|
||||
* includes reference to the encoder object.
|
||||
* @param pixel_format format for pixels. Object owned by the caller and its
|
||||
* contents are copied internally. The num_channels value is ignored, since the
|
||||
* number of channels for an extra channel is always assumed to be one.
|
||||
|
@ -445,8 +550,9 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderAddImageFrame(
|
|||
* @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error
|
||||
*/
|
||||
JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelBuffer(
|
||||
const JxlEncoderOptions* options, const JxlPixelFormat* pixel_format,
|
||||
const void* buffer, size_t size, uint32_t index);
|
||||
const JxlEncoderFrameSettings* frame_settings,
|
||||
const JxlPixelFormat* pixel_format, const void* buffer, size_t size,
|
||||
uint32_t index);
|
||||
|
||||
/** Adds a metadata box to the file format. JxlEncoderProcessOutput must be used
|
||||
* to effectively write the box to the output. @ref JxlEncoderUseBoxes must
|
||||
|
@ -582,7 +688,7 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderUseBoxes(JxlEncoder* enc);
|
|||
* @ref JxlEncoderUseBoxes is not used. Further frames may still be added.
|
||||
*
|
||||
* Must be called between JxlEncoderAddBox of the last box
|
||||
* and the next call to JxlEncoderProcessOutput, or JxlEncoderProcessOutput
|
||||
* and the next call to JxlEncoderProcessOutput, or @ref JxlEncoderProcessOutput
|
||||
* won't output the last box correctly.
|
||||
*
|
||||
* NOTE: if you don't need to close frames and boxes at separate times, you can
|
||||
|
@ -595,7 +701,9 @@ JXL_EXPORT void JxlEncoderCloseBoxes(JxlEncoder* enc);
|
|||
/**
|
||||
* Declares that no frames will be added and @ref JxlEncoderAddImageFrame and
|
||||
* @ref JxlEncoderAddJPEGFrame won't be called anymore. Further metadata boxes
|
||||
* may still be added.
|
||||
* may still be added. This function or @ref JxlEncoderCloseInput must be called
|
||||
* after adding the last frame and the next call to
|
||||
* @ref JxlEncoderProcessOutput, or the frame won't be properly marked as last.
|
||||
*
|
||||
* NOTE: if you don't need to close frames and boxes at separate times, you can
|
||||
* use @ref JxlEncoderCloseInput instead to close both at once.
|
||||
|
@ -611,7 +719,10 @@ JXL_EXPORT void JxlEncoderCloseFrames(JxlEncoder* enc);
|
|||
* calls should be done to create the final output.
|
||||
*
|
||||
* The requirements of both @ref JxlEncoderCloseFrames and @ref
|
||||
* JxlEncoderCloseBoxes apply to this function.
|
||||
* JxlEncoderCloseBoxes apply to this function. Either this function or the
|
||||
* other two must be called after the final frame and/or box, and the next
|
||||
* @ref JxlEncoderProcessOutput call, or the codestream won't be encoded
|
||||
* correctly.
|
||||
*
|
||||
* @param enc encoder object.
|
||||
*/
|
||||
|
@ -659,12 +770,35 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetICCProfile(JxlEncoder* enc,
|
|||
*/
|
||||
JXL_EXPORT void JxlEncoderInitBasicInfo(JxlBasicInfo* info);
|
||||
|
||||
/**
|
||||
* Initializes a JxlFrameHeader struct to default values.
|
||||
* For forwards-compatibility, this function has to be called before values
|
||||
* are assigned to the struct fields.
|
||||
* The default values correspond to a frame with no animation duration and the
|
||||
* 'replace' blend mode. After using this function, For animation duration must
|
||||
* be set, for composite still blend settings must be set.
|
||||
*
|
||||
* @param frame_header frame metadata. Object owned by the caller.
|
||||
*/
|
||||
JXL_EXPORT void JxlEncoderInitFrameHeader(JxlFrameHeader* frame_header);
|
||||
|
||||
/**
|
||||
* Initializes a JxlBlendInfo struct to default values.
|
||||
* For forwards-compatibility, this function has to be called before values
|
||||
* are assigned to the struct fields.
|
||||
*
|
||||
* @param blend_info blending info. Object owned by the caller.
|
||||
*/
|
||||
JXL_EXPORT void JxlEncoderInitBlendInfo(JxlBlendInfo* blend_info);
|
||||
|
||||
/**
|
||||
* Sets the global metadata of the image encoded by this encoder.
|
||||
*
|
||||
* If the JxlBasicInfo contains information of extra channels beyond an alpha
|
||||
* channel, then @ref JxlEncoderSetExtraChannelInfo must be called between
|
||||
* JxlEncoderSetBasicInfo and @ref JxlEncoderAddImageFrame.
|
||||
* JxlEncoderSetBasicInfo and @ref JxlEncoderAddImageFrame. In order to indicate
|
||||
* extra channels, the value of `info.num_extra_channels` should be set to the
|
||||
* number of extra channels, also counting the alpha channel if present.
|
||||
*
|
||||
* @param enc encoder object.
|
||||
* @param info global image metadata. Object owned by the caller and its
|
||||
|
@ -706,6 +840,9 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelInfo(
|
|||
* Sets the name for the extra channel at the given index in UTF-8. The index
|
||||
* must be smaller than the num_extra_channels in the associated JxlBasicInfo.
|
||||
*
|
||||
* TODO(lode): remove size parameter for consistency with
|
||||
* JxlEncoderSetFrameName
|
||||
*
|
||||
* @param enc encoder object
|
||||
* @param index index of the extra channel to set.
|
||||
* @param name buffer with the name of the extra channel.
|
||||
|
@ -720,19 +857,21 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetExtraChannelName(JxlEncoder* enc,
|
|||
|
||||
/**
|
||||
* Sets a frame-specific option of integer type to the encoder options.
|
||||
* The JxlEncoderOptionId argument determines which option is set.
|
||||
* The JxlEncoderFrameSettingId argument determines which option is set.
|
||||
*
|
||||
* @param options set of encoder options to update with the new mode.
|
||||
* @param frame_settings set of options and metadata for this frame. Also
|
||||
* includes reference to the encoder object.
|
||||
* @param option ID of the option to set.
|
||||
* @param value Integer value to set for this option.
|
||||
* @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR in
|
||||
* case of an error, such as invalid or unknown option id, or invalid integer
|
||||
* value for the given option. If an error is returned, the state of the
|
||||
* JxlEncoderOptions object is still valid and is the same as before this
|
||||
* JxlEncoderFrameSettings object is still valid and is the same as before this
|
||||
* function was called.
|
||||
*/
|
||||
JXL_EXPORT JxlEncoderStatus JxlEncoderOptionsSetInteger(
|
||||
JxlEncoderOptions* options, JxlEncoderOptionId option, int32_t value);
|
||||
JXL_EXPORT JxlEncoderStatus JxlEncoderFrameSettingsSetOption(
|
||||
JxlEncoderFrameSettings* frame_settings, JxlEncoderFrameSettingId option,
|
||||
int32_t value);
|
||||
|
||||
/** Forces the encoder to use the box-based container format (BMFF) even
|
||||
* when not necessary.
|
||||
|
@ -773,7 +912,8 @@ JXL_EXPORT JxlEncoderStatus
|
|||
JxlEncoderStoreJPEGMetadata(JxlEncoder* enc, JXL_BOOL store_jpeg_metadata);
|
||||
|
||||
/** Sets the feature level of the JPEG XL codestream. Valid values are 5 and
|
||||
* 10.
|
||||
* 10. Keeping the default value of 5 is recommended for compatibility with all
|
||||
* decoders.
|
||||
*
|
||||
* Level 5: for end-user image delivery, this level is the most widely
|
||||
* supported level by image decoders and the recommended level to use unless a
|
||||
|
@ -796,10 +936,36 @@ JxlEncoderStoreJPEGMetadata(JxlEncoder* enc, JXL_BOOL store_jpeg_metadata);
|
|||
* the encoder will only use those compatible with the level setting.
|
||||
*
|
||||
* This setting can only be set at the beginning, before encoding starts.
|
||||
*
|
||||
* @param enc encoder object.
|
||||
* @param level the level value to set, must be 5 or 10.
|
||||
* @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR
|
||||
* otherwise.
|
||||
*/
|
||||
JXL_EXPORT JxlEncoderStatus JxlEncoderSetCodestreamLevel(JxlEncoder* enc,
|
||||
int level);
|
||||
|
||||
/** Returns the codestream level required to support the currently configured
|
||||
* settings and basic info. This function can only be used at the beginning,
|
||||
* before encoding starts, but after setting basic info.
|
||||
*
|
||||
* This does not support per-frame settings, only global configuration, such as
|
||||
* the image dimensions, that are known at the time of writing the header of
|
||||
* the JPEG XL file.
|
||||
*
|
||||
* If this returns 5, nothing needs to be done and the codestream can be
|
||||
* compatible with any decoder. If this returns 10, JxlEncoderSetCodestreamLevel
|
||||
* has to be used to set the codestream level to 10, or the encoder can be
|
||||
* configured differently to allow using the more compatible level 5.
|
||||
*
|
||||
* @param enc encoder object.
|
||||
* @return -1 if no level can support the configuration (e.g. image dimensions
|
||||
* larger than even level 10 supports), 5 if level 5 is supported, 10 if setting
|
||||
* the codestream level to 10 is required.
|
||||
*
|
||||
*/
|
||||
JXL_EXPORT int JxlEncoderGetRequiredCodestreamLevel(const JxlEncoder* enc);
|
||||
|
||||
/**
|
||||
* Enables lossless encoding.
|
||||
*
|
||||
|
@ -812,54 +978,67 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderSetCodestreamLevel(JxlEncoder* enc,
|
|||
* using this function with lossless set to JXL_DEC_FALSE does not guarantee
|
||||
* lossy encoding, though the default set of options is lossy.
|
||||
*
|
||||
* @param options set of encoder options to update with the new mode
|
||||
* @param frame_settings set of options and metadata for this frame. Also
|
||||
* includes reference to the encoder object.
|
||||
* @param lossless whether to override options for lossless mode
|
||||
* @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR
|
||||
* otherwise.
|
||||
*/
|
||||
JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameLossless(
|
||||
JxlEncoderFrameSettings* frame_settings, JXL_BOOL lossless);
|
||||
|
||||
/** DEPRECATED: use JxlEncoderSetFrameLossless instead.
|
||||
*/
|
||||
JXL_EXPORT JxlEncoderStatus
|
||||
JxlEncoderOptionsSetLossless(JxlEncoderOptions* options, JXL_BOOL lossless);
|
||||
JxlEncoderOptionsSetLossless(JxlEncoderFrameSettings*, JXL_BOOL);
|
||||
|
||||
/**
|
||||
* @param options set of encoder options to update with the new mode.
|
||||
* @param frame_settings set of options and metadata for this frame. Also
|
||||
* includes reference to the encoder object.
|
||||
* @param effort the effort value to set.
|
||||
* @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR
|
||||
* otherwise.
|
||||
*
|
||||
* DEPRECATED: use JxlEncoderOptionsSetInteger(options, JXL_ENC_OPTION_EFFORT,
|
||||
* effort)) instead.
|
||||
* DEPRECATED: use JxlEncoderFrameSettingsSetOption(frame_settings,
|
||||
* JXL_ENC_FRAME_SETTING_EFFORT, effort) instead.
|
||||
*/
|
||||
JXL_EXPORT JXL_DEPRECATED JxlEncoderStatus
|
||||
JxlEncoderOptionsSetEffort(JxlEncoderOptions* options, int effort);
|
||||
JxlEncoderOptionsSetEffort(JxlEncoderFrameSettings* frame_settings, int effort);
|
||||
|
||||
/**
|
||||
* @param options set of encoder options to update with the new decoding speed
|
||||
* tier.
|
||||
* @param frame_settings set of options and metadata for this frame. Also
|
||||
* includes reference to the encoder object.
|
||||
* @param tier the decoding speed tier to set.
|
||||
* @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR
|
||||
* otherwise.
|
||||
*
|
||||
* DEPRECATED: use JxlEncoderOptionsSetInteger(options,
|
||||
* JXL_ENC_OPTION_DECODING_SPEED, tier)) instead.
|
||||
* DEPRECATED: use JxlEncoderFrameSettingsSetOption(frame_settings,
|
||||
* JXL_ENC_FRAME_SETTING_DECODING_SPEED, tier) instead.
|
||||
*/
|
||||
JXL_EXPORT JXL_DEPRECATED JxlEncoderStatus
|
||||
JxlEncoderOptionsSetDecodingSpeed(JxlEncoderOptions* options, int tier);
|
||||
JXL_EXPORT JXL_DEPRECATED JxlEncoderStatus JxlEncoderOptionsSetDecodingSpeed(
|
||||
JxlEncoderFrameSettings* frame_settings, int tier);
|
||||
|
||||
/**
|
||||
* Sets the distance level for lossy compression: target max butteraugli
|
||||
* distance, lower = higher quality. Range: 0 .. 15.
|
||||
* 0.0 = mathematically lossless (however, use JxlEncoderOptionsSetLossless
|
||||
* 0.0 = mathematically lossless (however, use JxlEncoderSetFrameLossless
|
||||
* instead to use true lossless, as setting distance to 0 alone is not the only
|
||||
* requirement). 1.0 = visually lossless. Recommended range: 0.5 .. 3.0. Default
|
||||
* value: 1.0.
|
||||
*
|
||||
* @param options set of encoder options to update with the new mode.
|
||||
* @param frame_settings set of options and metadata for this frame. Also
|
||||
* includes reference to the encoder object.
|
||||
* @param distance the distance value to set.
|
||||
* @return JXL_ENC_SUCCESS if the operation was successful, JXL_ENC_ERROR
|
||||
* otherwise.
|
||||
*/
|
||||
JXL_EXPORT JxlEncoderStatus JxlEncoderSetFrameDistance(
|
||||
JxlEncoderFrameSettings* frame_settings, float distance);
|
||||
|
||||
/** DEPRECATED: use JxlEncoderSetFrameDistance instead.
|
||||
*/
|
||||
JXL_EXPORT JxlEncoderStatus
|
||||
JxlEncoderOptionsSetDistance(JxlEncoderOptions* options, float distance);
|
||||
JxlEncoderOptionsSetDistance(JxlEncoderFrameSettings*, float);
|
||||
|
||||
/**
|
||||
* Create a new set of encoder options, with all values initially copied from
|
||||
|
@ -867,17 +1046,22 @@ JxlEncoderOptionsSetDistance(JxlEncoderOptions* options, float distance);
|
|||
*
|
||||
* The returned pointer is an opaque struct tied to the encoder and it will be
|
||||
* deallocated by the encoder when JxlEncoderDestroy() is called. For functions
|
||||
* taking both a @ref JxlEncoder and a @ref JxlEncoderOptions, only
|
||||
* JxlEncoderOptions created with this function for the same encoder instance
|
||||
* can be used.
|
||||
* taking both a @ref JxlEncoder and a @ref JxlEncoderFrameSettings, only
|
||||
* JxlEncoderFrameSettings created with this function for the same encoder
|
||||
* instance can be used.
|
||||
*
|
||||
* @param enc encoder object.
|
||||
* @param source source options to copy initial values from, or NULL to get
|
||||
* defaults initialized to defaults.
|
||||
* @return the opaque struct pointer identifying a new set of encoder options.
|
||||
*/
|
||||
JXL_EXPORT JxlEncoderOptions* JxlEncoderOptionsCreate(
|
||||
JxlEncoder* enc, const JxlEncoderOptions* source);
|
||||
JXL_EXPORT JxlEncoderFrameSettings* JxlEncoderFrameSettingsCreate(
|
||||
JxlEncoder* enc, const JxlEncoderFrameSettings* source);
|
||||
|
||||
/** DEPRECATED: use JxlEncoderFrameSettingsCreate instead.
|
||||
*/
|
||||
JXL_EXPORT JxlEncoderFrameSettings* JxlEncoderOptionsCreate(
|
||||
JxlEncoder*, const JxlEncoderFrameSettings*);
|
||||
|
||||
/**
|
||||
* Sets a color encoding to be sRGB.
|
||||
|
|
|
@ -94,7 +94,6 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
|
|||
jxl/dec_patch_dictionary.h
|
||||
jxl/dec_reconstruct.cc
|
||||
jxl/dec_reconstruct.h
|
||||
jxl/dec_render_pipeline.h
|
||||
jxl/dec_transforms-inl.h
|
||||
jxl/dec_upsample.cc
|
||||
jxl/dec_upsample.h
|
||||
|
@ -190,7 +189,36 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
|
|||
jxl/quantizer.cc
|
||||
jxl/quantizer.h
|
||||
jxl/rational_polynomial-inl.h
|
||||
jxl/render_pipeline/low_memory_render_pipeline.cc
|
||||
jxl/render_pipeline/low_memory_render_pipeline.h
|
||||
jxl/render_pipeline/render_pipeline.cc
|
||||
jxl/render_pipeline/render_pipeline.h
|
||||
jxl/render_pipeline/render_pipeline_stage.h
|
||||
jxl/render_pipeline/simple_render_pipeline.cc
|
||||
jxl/render_pipeline/simple_render_pipeline.h
|
||||
jxl/render_pipeline/stage_chroma_upsampling.cc
|
||||
jxl/render_pipeline/stage_chroma_upsampling.h
|
||||
jxl/render_pipeline/stage_epf.cc
|
||||
jxl/render_pipeline/stage_epf.h
|
||||
jxl/render_pipeline/stage_gaborish.cc
|
||||
jxl/render_pipeline/stage_gaborish.h
|
||||
jxl/render_pipeline/stage_noise.cc
|
||||
jxl/render_pipeline/stage_noise.h
|
||||
jxl/render_pipeline/stage_patches.cc
|
||||
jxl/render_pipeline/stage_patches.h
|
||||
jxl/render_pipeline/stage_splines.cc
|
||||
jxl/render_pipeline/stage_splines.h
|
||||
jxl/render_pipeline/stage_upsampling.cc
|
||||
jxl/render_pipeline/stage_upsampling.h
|
||||
jxl/render_pipeline/stage_write_to_ib.cc
|
||||
jxl/render_pipeline/stage_write_to_ib.h
|
||||
jxl/render_pipeline/stage_xyb.cc
|
||||
jxl/render_pipeline/stage_xyb.h
|
||||
jxl/render_pipeline/stage_ycbcr.cc
|
||||
jxl/render_pipeline/stage_ycbcr.h
|
||||
jxl/render_pipeline/test_render_pipeline_stages.h
|
||||
jxl/sanitizers.h
|
||||
jxl/simd_util-inl.h
|
||||
jxl/splines.cc
|
||||
jxl/splines.h
|
||||
jxl/toc.cc
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <hwy/tests/test_util-inl.h>
|
||||
#include <utility>
|
||||
|
||||
#include "lib/jxl/base/random.h"
|
||||
#include "lib/jxl/common.h"
|
||||
#include "lib/jxl/dct_scales.h"
|
||||
#include "lib/jxl/dec_transforms_testonly.h"
|
||||
|
@ -32,9 +33,12 @@ class AcStrategyRoundtrip : public ::hwy::TestWithParamTargetAndT<int> {
|
|||
float* scratch_space = mem.get();
|
||||
float* coeffs = scratch_space + AcStrategy::kMaxCoeffArea;
|
||||
float* idct = coeffs + AcStrategy::kMaxCoeffArea;
|
||||
Rng rng(type * 65537 + 13);
|
||||
|
||||
for (size_t i = 0; i < std::min(1024u, 64u << acs.log2_covered_blocks());
|
||||
i++) {
|
||||
for (size_t j = 0; j < 64; j++) {
|
||||
size_t i = (acs.log2_covered_blocks()
|
||||
? rng.UniformU(0, 64u << acs.log2_covered_blocks())
|
||||
: j);
|
||||
float* input = idct + AcStrategy::kMaxCoeffArea;
|
||||
std::fill_n(input, AcStrategy::kMaxCoeffArea, 0);
|
||||
input[i] = 0.2f;
|
||||
|
@ -88,9 +92,13 @@ class AcStrategyRoundtripDownsample
|
|||
float* coeffs = scratch_space + AcStrategy::kMaxCoeffArea;
|
||||
std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0.0f);
|
||||
float* idct = coeffs + AcStrategy::kMaxCoeffArea;
|
||||
Rng rng(type * 65537 + 13);
|
||||
|
||||
for (size_t y = 0; y < acs.covered_blocks_y(); y++) {
|
||||
for (size_t x = 0; x < acs.covered_blocks_x(); x++) {
|
||||
if (x > 4 || y > 4) {
|
||||
if (rng.Bernoulli(0.9f)) continue;
|
||||
}
|
||||
float* dc = idct + AcStrategy::kMaxCoeffArea;
|
||||
std::fill_n(dc, AcStrategy::kMaxCoeffArea, 0);
|
||||
dc[y * acs.covered_blocks_x() * 8 + x] = 0.2f;
|
||||
|
@ -141,9 +149,13 @@ class AcStrategyDownsample : public ::hwy::TestWithParamTargetAndT<int> {
|
|||
float* scratch_space = mem.get();
|
||||
float* idct = scratch_space + AcStrategy::kMaxCoeffArea;
|
||||
float* idct_acs_downsampled = idct + AcStrategy::kMaxCoeffArea;
|
||||
Rng rng(type * 65537 + 13);
|
||||
|
||||
for (size_t y = 0; y < cy; y++) {
|
||||
for (size_t x = 0; x < cx; x++) {
|
||||
if (x > 4 || y > 4) {
|
||||
if (rng.Bernoulli(0.9f)) continue;
|
||||
}
|
||||
float* coeffs = idct + AcStrategy::kMaxCoeffArea;
|
||||
std::fill_n(coeffs, AcStrategy::kMaxCoeffArea, 0);
|
||||
coeffs[y * cx * 8 + x] = 0.2f;
|
||||
|
|
|
@ -135,7 +135,7 @@ void EnsureUnchanged(const float background, const float foreground,
|
|||
JXL_CHECK(
|
||||
jxl::InitializePassesSharedState(frame_header, &state.shared_storage));
|
||||
JXL_CHECK(state.Init());
|
||||
state.InitForAC(/*pool=*/nullptr);
|
||||
JXL_CHECK(state.InitForAC(/*pool=*/nullptr));
|
||||
|
||||
JXL_CHECK(state.filter_weights.Init(lf, frame_dim));
|
||||
FillImage(-0.5f, &state.filter_weights.sigma);
|
||||
|
|
|
@ -87,9 +87,9 @@ TEST(ANSTest, SingleSymbolRoundtrip) {
|
|||
|
||||
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
|
||||
defined(THREAD_SANITIZER)
|
||||
constexpr size_t kReps = 10;
|
||||
constexpr size_t kReps = 3;
|
||||
#else
|
||||
constexpr size_t kReps = 100;
|
||||
constexpr size_t kReps = 10;
|
||||
#endif
|
||||
|
||||
void RoundtripRandomStream(int alphabet_size, size_t reps = kReps,
|
||||
|
|
|
@ -84,17 +84,6 @@
|
|||
#define JXL_UNLIKELY(expr) __builtin_expect(!!(expr), 0)
|
||||
#endif
|
||||
|
||||
#if JXL_COMPILER_MSVC
|
||||
#include <intrin.h>
|
||||
|
||||
#pragma intrinsic(_ReadWriteBarrier)
|
||||
#define JXL_COMPILER_FENCE _ReadWriteBarrier()
|
||||
#elif JXL_COMPILER_GCC || JXL_COMPILER_CLANG
|
||||
#define JXL_COMPILER_FENCE asm volatile("" : : : "memory")
|
||||
#else
|
||||
#define JXL_COMPILER_FENCE
|
||||
#endif
|
||||
|
||||
// Returns a void* pointer which the compiler then assumes is N-byte aligned.
|
||||
// Example: float* JXL_RESTRICT aligned = (float*)JXL_ASSUME_ALIGNED(in, 32);
|
||||
//
|
||||
|
|
|
@ -15,16 +15,15 @@
|
|||
#include "jxl/parallel_runner.h"
|
||||
#include "lib/jxl/base/bits.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#if JXL_COMPILER_MSVC
|
||||
// suppress warnings about the const & applied to function types
|
||||
#pragma warning(disable : 4180)
|
||||
#endif
|
||||
|
||||
namespace jxl {
|
||||
|
||||
class ThreadPool {
|
||||
public:
|
||||
// Use this type as an InitFunc to skip the initialization step in Run().
|
||||
// When this is used the return value of Run() is always true and does not
|
||||
// need to be checked.
|
||||
struct SkipInit {};
|
||||
|
||||
ThreadPool(JxlParallelRunner runner, void* runner_opaque)
|
||||
: runner_(runner ? runner : &ThreadPool::SequentialRunnerStatic),
|
||||
runner_opaque_(runner ? runner_opaque : static_cast<void*>(this)) {}
|
||||
|
@ -47,21 +46,16 @@ class ThreadPool {
|
|||
if (begin == end) return true;
|
||||
RunCallState<InitFunc, DataFunc> call_state(init_func, data_func);
|
||||
// The runner_ uses the C convention and returns 0 in case of error, so we
|
||||
// convert it to an Status.
|
||||
// convert it to a Status.
|
||||
return (*runner_)(runner_opaque_, static_cast<void*>(&call_state),
|
||||
&call_state.CallInitFunc, &call_state.CallDataFunc, begin,
|
||||
end) == 0;
|
||||
}
|
||||
|
||||
// Specialization that returns bool when SkipInit is used.
|
||||
template <class DataFunc>
|
||||
bool Run(uint32_t begin, uint32_t end, const SkipInit /* tag */,
|
||||
const DataFunc& data_func, const char* caller = "") {
|
||||
return Run(begin, end, ReturnTrueInit, data_func, caller);
|
||||
}
|
||||
// Use this as init_func when no initialization is needed.
|
||||
static Status NoInit(size_t num_threads) { return true; }
|
||||
|
||||
private:
|
||||
static Status ReturnTrueInit(size_t num_threads) { return true; }
|
||||
|
||||
// class holding the state of a Run() call to pass to the runner_ as an
|
||||
// opaque_jpegxl pointer.
|
||||
|
@ -104,21 +98,21 @@ class ThreadPool {
|
|||
void* const runner_opaque_;
|
||||
};
|
||||
|
||||
// TODO(deymo): Convert the return value to a Status when not using SkipInit.
|
||||
template <class InitFunc, class DataFunc>
|
||||
bool RunOnPool(ThreadPool* pool, const uint32_t begin, const uint32_t end,
|
||||
const InitFunc& init_func, const DataFunc& data_func,
|
||||
const char* caller) {
|
||||
Status ret = true;
|
||||
Status RunOnPool(ThreadPool* pool, const uint32_t begin, const uint32_t end,
|
||||
const InitFunc& init_func, const DataFunc& data_func,
|
||||
const char* caller) {
|
||||
if (pool == nullptr) {
|
||||
ThreadPool default_pool(nullptr, nullptr);
|
||||
ret = default_pool.Run(begin, end, init_func, data_func, caller);
|
||||
return default_pool.Run(begin, end, init_func, data_func, caller);
|
||||
} else {
|
||||
ret = pool->Run(begin, end, init_func, data_func, caller);
|
||||
return pool->Run(begin, end, init_func, data_func, caller);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
#if JXL_COMPILER_MSVC
|
||||
#pragma warning(default : 4180)
|
||||
#endif
|
||||
|
||||
#endif // LIB_JXL_BASE_DATA_PARALLEL_H_
|
||||
|
|
|
@ -53,88 +53,91 @@ struct Symbol {
|
|||
// Reading from output gives the same values.
|
||||
TEST(BitReaderTest, TestRoundTrip) {
|
||||
ThreadPoolInternal pool(8);
|
||||
pool.Run(0, 1000, ThreadPool::SkipInit(),
|
||||
[](const int task, const int /* thread */) {
|
||||
constexpr size_t kMaxBits = 8000;
|
||||
BitWriter writer;
|
||||
BitWriter::Allotment allotment(&writer, kMaxBits);
|
||||
EXPECT_TRUE(RunOnPool(
|
||||
&pool, 0, 1000, ThreadPool::NoInit,
|
||||
[](const uint32_t task, size_t /* thread */) {
|
||||
constexpr size_t kMaxBits = 8000;
|
||||
BitWriter writer;
|
||||
BitWriter::Allotment allotment(&writer, kMaxBits);
|
||||
|
||||
std::vector<Symbol> symbols;
|
||||
symbols.reserve(1000);
|
||||
std::vector<Symbol> symbols;
|
||||
symbols.reserve(1000);
|
||||
|
||||
Rng rng(55537 + 129 * task);
|
||||
Rng rng(55537 + 129 * task);
|
||||
|
||||
for (;;) {
|
||||
const uint32_t num_bits = rng.UniformU(1, 33);
|
||||
if (writer.BitsWritten() + num_bits > kMaxBits) break;
|
||||
const uint32_t value = rng.UniformU(0, 1ULL << num_bits);
|
||||
symbols.push_back({num_bits, value});
|
||||
writer.Write(num_bits, value);
|
||||
}
|
||||
for (;;) {
|
||||
const uint32_t num_bits = rng.UniformU(1, 33);
|
||||
if (writer.BitsWritten() + num_bits > kMaxBits) break;
|
||||
const uint32_t value = rng.UniformU(0, 1ULL << num_bits);
|
||||
symbols.push_back({num_bits, value});
|
||||
writer.Write(num_bits, value);
|
||||
}
|
||||
|
||||
writer.ZeroPadToByte();
|
||||
ReclaimAndCharge(&writer, &allotment, 0, nullptr);
|
||||
BitReader reader(writer.GetSpan());
|
||||
for (const Symbol& s : symbols) {
|
||||
EXPECT_EQ(s.value, reader.ReadBits(s.num_bits));
|
||||
}
|
||||
EXPECT_TRUE(reader.Close());
|
||||
});
|
||||
writer.ZeroPadToByte();
|
||||
ReclaimAndCharge(&writer, &allotment, 0, nullptr);
|
||||
BitReader reader(writer.GetSpan());
|
||||
for (const Symbol& s : symbols) {
|
||||
EXPECT_EQ(s.value, reader.ReadBits(s.num_bits));
|
||||
}
|
||||
EXPECT_TRUE(reader.Close());
|
||||
},
|
||||
"TestTBitReaderRoundTrip"));
|
||||
}
|
||||
|
||||
// SkipBits is the same as reading that many bits.
|
||||
TEST(BitReaderTest, TestSkip) {
|
||||
ThreadPoolInternal pool(8);
|
||||
pool.Run(0, 96, ThreadPool::SkipInit(),
|
||||
[](const int task, const int /* thread */) {
|
||||
constexpr size_t kSize = 100;
|
||||
EXPECT_TRUE(RunOnPool(
|
||||
&pool, 0, 96, ThreadPool::NoInit,
|
||||
[](const uint32_t task, size_t /* thread */) {
|
||||
constexpr size_t kSize = 100;
|
||||
|
||||
for (size_t skip = 0; skip < 128; ++skip) {
|
||||
BitWriter writer;
|
||||
BitWriter::Allotment allotment(&writer, kSize * kBitsPerByte);
|
||||
// Start with "task" 1-bits.
|
||||
for (int i = 0; i < task; ++i) {
|
||||
writer.Write(1, 1);
|
||||
}
|
||||
for (size_t skip = 0; skip < 128; ++skip) {
|
||||
BitWriter writer;
|
||||
BitWriter::Allotment allotment(&writer, kSize * kBitsPerByte);
|
||||
// Start with "task" 1-bits.
|
||||
for (size_t i = 0; i < task; ++i) {
|
||||
writer.Write(1, 1);
|
||||
}
|
||||
|
||||
// Write 0-bits that we will skip over
|
||||
for (size_t i = 0; i < skip; ++i) {
|
||||
writer.Write(1, 0);
|
||||
}
|
||||
// Write 0-bits that we will skip over
|
||||
for (size_t i = 0; i < skip; ++i) {
|
||||
writer.Write(1, 0);
|
||||
}
|
||||
|
||||
// Write terminator bits '101'
|
||||
writer.Write(3, 5);
|
||||
EXPECT_EQ(task + skip + 3, writer.BitsWritten());
|
||||
writer.ZeroPadToByte();
|
||||
AuxOut aux_out;
|
||||
ReclaimAndCharge(&writer, &allotment, 0, &aux_out);
|
||||
EXPECT_LT(aux_out.layers[0].total_bits, kSize * 8);
|
||||
// Write terminator bits '101'
|
||||
writer.Write(3, 5);
|
||||
EXPECT_EQ(task + skip + 3, writer.BitsWritten());
|
||||
writer.ZeroPadToByte();
|
||||
AuxOut aux_out;
|
||||
ReclaimAndCharge(&writer, &allotment, 0, &aux_out);
|
||||
EXPECT_LT(aux_out.layers[0].total_bits, kSize * 8);
|
||||
|
||||
BitReader reader1(writer.GetSpan());
|
||||
BitReader reader2(writer.GetSpan());
|
||||
// Verify initial 1-bits
|
||||
for (int i = 0; i < task; ++i) {
|
||||
EXPECT_EQ(1u, reader1.ReadBits(1));
|
||||
EXPECT_EQ(1u, reader2.ReadBits(1));
|
||||
}
|
||||
BitReader reader1(writer.GetSpan());
|
||||
BitReader reader2(writer.GetSpan());
|
||||
// Verify initial 1-bits
|
||||
for (size_t i = 0; i < task; ++i) {
|
||||
EXPECT_EQ(1u, reader1.ReadBits(1));
|
||||
EXPECT_EQ(1u, reader2.ReadBits(1));
|
||||
}
|
||||
|
||||
// SkipBits or manually read "skip" bits
|
||||
reader1.SkipBits(skip);
|
||||
for (size_t i = 0; i < skip; ++i) {
|
||||
EXPECT_EQ(0u, reader2.ReadBits(1))
|
||||
<< " skip=" << skip << " i=" << i;
|
||||
}
|
||||
EXPECT_EQ(reader1.TotalBitsConsumed(),
|
||||
reader2.TotalBitsConsumed());
|
||||
// SkipBits or manually read "skip" bits
|
||||
reader1.SkipBits(skip);
|
||||
for (size_t i = 0; i < skip; ++i) {
|
||||
EXPECT_EQ(0u, reader2.ReadBits(1))
|
||||
<< " skip=" << skip << " i=" << i;
|
||||
}
|
||||
EXPECT_EQ(reader1.TotalBitsConsumed(), reader2.TotalBitsConsumed());
|
||||
|
||||
// Ensure both readers see the terminator bits.
|
||||
EXPECT_EQ(5u, reader1.ReadBits(3));
|
||||
EXPECT_EQ(5u, reader2.ReadBits(3));
|
||||
// Ensure both readers see the terminator bits.
|
||||
EXPECT_EQ(5u, reader1.ReadBits(3));
|
||||
EXPECT_EQ(5u, reader2.ReadBits(3));
|
||||
|
||||
EXPECT_TRUE(reader1.Close());
|
||||
EXPECT_TRUE(reader2.Close());
|
||||
}
|
||||
});
|
||||
EXPECT_TRUE(reader1.Close());
|
||||
EXPECT_TRUE(reader2.Close());
|
||||
}
|
||||
},
|
||||
"TestSkip"));
|
||||
}
|
||||
|
||||
// Verifies byte order and different groupings of bits.
|
||||
|
|
|
@ -338,11 +338,11 @@ ImageBlender::RectBlender ImageBlender::PrepareRect(
|
|||
return blender;
|
||||
}
|
||||
|
||||
Status PerformBlending(
|
||||
const float* const* bg, const float* const* fg, float* const* out,
|
||||
size_t xsize, const PatchBlending& color_blending,
|
||||
const PatchBlending* ec_blending,
|
||||
const std::vector<ExtraChannelInfo>& extra_channel_info) {
|
||||
void PerformBlending(const float* const* bg, const float* const* fg,
|
||||
float* const* out, size_t x0, size_t xsize,
|
||||
const PatchBlending& color_blending,
|
||||
const PatchBlending* ec_blending,
|
||||
const std::vector<ExtraChannelInfo>& extra_channel_info) {
|
||||
bool has_alpha = false;
|
||||
size_t num_ec = extra_channel_info.size();
|
||||
for (size_t i = 0; i < num_ec; i++) {
|
||||
|
@ -356,35 +356,37 @@ Status PerformBlending(
|
|||
for (size_t i = 0; i < num_ec; i++) {
|
||||
if (ec_blending[i].mode == PatchBlendMode::kAdd) {
|
||||
for (size_t x = 0; x < xsize; x++) {
|
||||
tmp.Row(3 + i)[x] = bg[3 + i][x] + fg[3 + i][x];
|
||||
tmp.Row(3 + i)[x] = bg[3 + i][x + x0] + fg[3 + i][x + x0];
|
||||
}
|
||||
} else if (ec_blending[i].mode == PatchBlendMode::kBlendAbove) {
|
||||
size_t alpha = ec_blending[i].alpha_channel;
|
||||
bool is_premultiplied = extra_channel_info[alpha].alpha_associated;
|
||||
PerformAlphaBlending(bg[3 + i], bg[3 + alpha], fg[3 + i], fg[3 + alpha],
|
||||
tmp.Row(3 + i), xsize, is_premultiplied,
|
||||
ec_blending[i].clamp);
|
||||
PerformAlphaBlending(bg[3 + i] + x0, bg[3 + alpha] + x0, fg[3 + i] + x0,
|
||||
fg[3 + alpha] + x0, tmp.Row(3 + i), xsize,
|
||||
is_premultiplied, ec_blending[i].clamp);
|
||||
} else if (ec_blending[i].mode == PatchBlendMode::kBlendBelow) {
|
||||
size_t alpha = ec_blending[i].alpha_channel;
|
||||
bool is_premultiplied = extra_channel_info[alpha].alpha_associated;
|
||||
PerformAlphaBlending(fg[3 + i], fg[3 + alpha], bg[3 + i], bg[3 + alpha],
|
||||
tmp.Row(3 + i), xsize, is_premultiplied,
|
||||
ec_blending[i].clamp);
|
||||
PerformAlphaBlending(fg[3 + i] + x0, fg[3 + alpha] + x0, bg[3 + i] + x0,
|
||||
bg[3 + alpha] + x0, tmp.Row(3 + i), xsize,
|
||||
is_premultiplied, ec_blending[i].clamp);
|
||||
} else if (ec_blending[i].mode == PatchBlendMode::kAlphaWeightedAddAbove) {
|
||||
size_t alpha = ec_blending[i].alpha_channel;
|
||||
PerformAlphaWeightedAdd(bg[3 + i], fg[3 + i], fg[3 + alpha],
|
||||
tmp.Row(3 + i), xsize, ec_blending[i].clamp);
|
||||
PerformAlphaWeightedAdd(bg[3 + i] + x0, fg[3 + i] + x0,
|
||||
fg[3 + alpha] + x0, tmp.Row(3 + i), xsize,
|
||||
ec_blending[i].clamp);
|
||||
} else if (ec_blending[i].mode == PatchBlendMode::kAlphaWeightedAddBelow) {
|
||||
size_t alpha = ec_blending[i].alpha_channel;
|
||||
PerformAlphaWeightedAdd(fg[3 + i], bg[3 + i], bg[3 + alpha],
|
||||
tmp.Row(3 + i), xsize, ec_blending[i].clamp);
|
||||
PerformAlphaWeightedAdd(fg[3 + i] + x0, bg[3 + i] + x0,
|
||||
bg[3 + alpha] + x0, tmp.Row(3 + i), xsize,
|
||||
ec_blending[i].clamp);
|
||||
} else if (ec_blending[i].mode == PatchBlendMode::kMul) {
|
||||
PerformMulBlending(bg[3 + i], fg[3 + i], tmp.Row(3 + i), xsize,
|
||||
PerformMulBlending(bg[3 + i] + x0, fg[3 + i] + x0, tmp.Row(3 + i), xsize,
|
||||
ec_blending[i].clamp);
|
||||
} else if (ec_blending[i].mode == PatchBlendMode::kReplace) {
|
||||
memcpy(tmp.Row(3 + i), fg[3 + i], xsize * sizeof(**fg));
|
||||
memcpy(tmp.Row(3 + i), fg[3 + i] + x0, xsize * sizeof(**fg));
|
||||
} else if (ec_blending[i].mode == PatchBlendMode::kNone) {
|
||||
memcpy(tmp.Row(3 + i), bg[3 + i], xsize * sizeof(**fg));
|
||||
memcpy(tmp.Row(3 + i), bg[3 + i] + x0, xsize * sizeof(**fg));
|
||||
} else {
|
||||
JXL_ABORT("Unreachable");
|
||||
}
|
||||
|
@ -399,7 +401,7 @@ Status PerformBlending(
|
|||
for (int p = 0; p < 3; p++) {
|
||||
float* out = tmp.Row(p);
|
||||
for (size_t x = 0; x < xsize; x++) {
|
||||
out[x] = bg[p][x] + fg[p][x];
|
||||
out[x] = bg[p][x + x0] + fg[p][x + x0];
|
||||
}
|
||||
}
|
||||
} else if (color_blending.mode == PatchBlendMode::kBlendAbove
|
||||
|
@ -407,8 +409,8 @@ Status PerformBlending(
|
|||
&& has_alpha) {
|
||||
bool is_premultiplied = extra_channel_info[alpha].alpha_associated;
|
||||
PerformAlphaBlending(
|
||||
{bg[0], bg[1], bg[2], bg[3 + alpha]},
|
||||
{fg[0], fg[1], fg[2], fg[3 + alpha]},
|
||||
{bg[0] + x0, bg[1] + x0, bg[2] + x0, bg[3 + alpha] + x0},
|
||||
{fg[0] + x0, fg[1] + x0, fg[2] + x0, fg[3 + alpha] + x0},
|
||||
{tmp.Row(0), tmp.Row(1), tmp.Row(2), tmp.Row(3 + alpha)}, xsize,
|
||||
is_premultiplied, color_blending.clamp);
|
||||
} else if (color_blending.mode == PatchBlendMode::kBlendBelow
|
||||
|
@ -416,43 +418,43 @@ Status PerformBlending(
|
|||
&& has_alpha) {
|
||||
bool is_premultiplied = extra_channel_info[alpha].alpha_associated;
|
||||
PerformAlphaBlending(
|
||||
{fg[0], fg[1], fg[2], fg[3 + alpha]},
|
||||
{bg[0], bg[1], bg[2], bg[3 + alpha]},
|
||||
{fg[0] + x0, fg[1] + x0, fg[2] + x0, fg[3 + alpha] + x0},
|
||||
{bg[0] + x0, bg[1] + x0, bg[2] + x0, bg[3 + alpha] + x0},
|
||||
{tmp.Row(0), tmp.Row(1), tmp.Row(2), tmp.Row(3 + alpha)}, xsize,
|
||||
is_premultiplied, color_blending.clamp);
|
||||
} else if (color_blending.mode == PatchBlendMode::kAlphaWeightedAddAbove) {
|
||||
JXL_DASSERT(has_alpha);
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
PerformAlphaWeightedAdd(bg[c], fg[c], fg[3 + alpha], tmp.Row(c), xsize,
|
||||
color_blending.clamp);
|
||||
PerformAlphaWeightedAdd(bg[c] + x0, fg[c] + x0, fg[3 + alpha] + x0,
|
||||
tmp.Row(c), xsize, color_blending.clamp);
|
||||
}
|
||||
} else if (color_blending.mode == PatchBlendMode::kAlphaWeightedAddBelow) {
|
||||
JXL_DASSERT(has_alpha);
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
PerformAlphaWeightedAdd(fg[c], bg[c], bg[3 + alpha], tmp.Row(c), xsize,
|
||||
color_blending.clamp);
|
||||
PerformAlphaWeightedAdd(fg[c] + x0, bg[c] + x0, bg[3 + alpha] + x0,
|
||||
tmp.Row(c), xsize, color_blending.clamp);
|
||||
}
|
||||
} else if (color_blending.mode == PatchBlendMode::kMul) {
|
||||
for (int p = 0; p < 3; p++) {
|
||||
PerformMulBlending(bg[p], fg[p], tmp.Row(p), xsize, color_blending.clamp);
|
||||
PerformMulBlending(bg[p] + x0, fg[p] + x0, tmp.Row(p), xsize,
|
||||
color_blending.clamp);
|
||||
}
|
||||
} else if (color_blending.mode == PatchBlendMode::kReplace ||
|
||||
color_blending.mode == PatchBlendMode::kBlendAbove ||
|
||||
color_blending.mode == PatchBlendMode::kBlendBelow) { // kReplace
|
||||
for (size_t p = 0; p < 3; p++) {
|
||||
memcpy(tmp.Row(p), fg[p], xsize * sizeof(**fg));
|
||||
memcpy(tmp.Row(p), fg[p] + x0, xsize * sizeof(**fg));
|
||||
}
|
||||
} else if (color_blending.mode == PatchBlendMode::kNone) {
|
||||
for (size_t p = 0; p < 3; p++) {
|
||||
memcpy(tmp.Row(p), bg[p], xsize * sizeof(**fg));
|
||||
memcpy(tmp.Row(p), bg[p] + x0, xsize * sizeof(**fg));
|
||||
}
|
||||
} else {
|
||||
JXL_ABORT("Unreachable");
|
||||
}
|
||||
for (size_t i = 0; i < 3 + num_ec; i++) {
|
||||
memcpy(out[i], tmp.Row(i), xsize * sizeof(**out));
|
||||
memcpy(out[i] + x0, tmp.Row(i), xsize * sizeof(**out));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Status ImageBlender::RectBlender::DoBlending(size_t y) {
|
||||
|
@ -469,10 +471,11 @@ Status ImageBlender::RectBlender::DoBlending(size_t y) {
|
|||
bg_row_ptrs_[c] = bg_ptrs_[c] + y * bg_strides_[c];
|
||||
out_row_ptrs_[c] = out_ptrs_[c] + y * out_strides_[c];
|
||||
}
|
||||
return PerformBlending(bg_row_ptrs_.data(), fg_row_ptrs_.data(),
|
||||
out_row_ptrs_.data(), current_overlap_.xsize(),
|
||||
blending_info_[0], blending_info_.data() + 1,
|
||||
*extra_channel_info_);
|
||||
PerformBlending(bg_row_ptrs_.data(), fg_row_ptrs_.data(),
|
||||
out_row_ptrs_.data(), 0, current_overlap_.xsize(),
|
||||
blending_info_[0], blending_info_.data() + 1,
|
||||
*extra_channel_info_);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
|
|
@ -11,11 +11,11 @@
|
|||
|
||||
namespace jxl {
|
||||
|
||||
Status PerformBlending(const float* const* bg, const float* const* fg,
|
||||
float* const* out, size_t xsize,
|
||||
const PatchBlending& color_blending,
|
||||
const PatchBlending* ec_blending,
|
||||
const std::vector<ExtraChannelInfo>& extra_channel_info);
|
||||
void PerformBlending(const float* const* bg, const float* const* fg,
|
||||
float* const* out, size_t x0, size_t xsize,
|
||||
const PatchBlending& color_blending,
|
||||
const PatchBlending* ec_blending,
|
||||
const std::vector<ExtraChannelInfo>& extra_channel_info);
|
||||
|
||||
class ImageBlender {
|
||||
public:
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "lib/jxl/common.h"
|
||||
#include "lib/jxl/enc_butteraugli_comparator.h"
|
||||
#include "lib/jxl/enc_butteraugli_pnorm.h"
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
#include "lib/jxl/enc_external_image.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
#include "lib/jxl/memory_manager_internal.h"
|
||||
|
@ -77,6 +78,7 @@ struct JxlButteraugliApiStruct {
|
|||
// Number of nits that correspond to 1.0f input values.
|
||||
float intensity_target = jxl::kDefaultIntensityTarget;
|
||||
|
||||
JxlCmsInterface cms;
|
||||
JxlMemoryManager memory_manager;
|
||||
std::unique_ptr<jxl::ThreadPool> thread_pool{nullptr};
|
||||
};
|
||||
|
@ -92,6 +94,7 @@ JxlButteraugliApi* JxlButteraugliApiCreate(
|
|||
if (!alloc) return nullptr;
|
||||
// Placement new constructor on allocated memory
|
||||
JxlButteraugliApi* ret = new (alloc) JxlButteraugliApi();
|
||||
ret->cms = jxl::GetJxlCms();
|
||||
ret->memory_manager = local_memory_manager;
|
||||
return ret;
|
||||
}
|
||||
|
@ -164,8 +167,8 @@ JxlButteraugliResult* JxlButteraugliCompute(
|
|||
result->params.hf_asymmetry = api->hf_asymmetry;
|
||||
result->params.xmul = api->xmul;
|
||||
result->params.intensity_target = api->intensity_target;
|
||||
jxl::ButteraugliDistance(orig_ib, dist_ib, result->params, &result->distmap,
|
||||
api->thread_pool.get());
|
||||
jxl::ButteraugliDistance(orig_ib, dist_ib, result->params, api->cms,
|
||||
&result->distmap, api->thread_pool.get());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -69,11 +69,6 @@ struct Blobs {
|
|||
PaddedBytes xmp;
|
||||
};
|
||||
|
||||
// For Codec::kJPG, convert between JPEG and pixels or between JPEG and
|
||||
// quantized DCT coefficients
|
||||
// For pixel data, the nominal range is 0..1.
|
||||
enum class DecodeTarget { kPixels, kQuantizedCoeffs };
|
||||
|
||||
// Holds a preview, a main image or one or more frames, plus the inputs/outputs
|
||||
// to/from decoding/encoding.
|
||||
class CodecInOut {
|
||||
|
@ -135,13 +130,13 @@ class CodecInOut {
|
|||
}
|
||||
|
||||
// Calls TransformTo for each ImageBundle (preview/frames).
|
||||
Status TransformTo(const ColorEncoding& c_desired,
|
||||
Status TransformTo(const ColorEncoding& c_desired, const JxlCmsInterface& cms,
|
||||
ThreadPool* pool = nullptr) {
|
||||
if (metadata.m.have_preview) {
|
||||
JXL_RETURN_IF_ERROR(preview_frame.TransformTo(c_desired, pool));
|
||||
JXL_RETURN_IF_ERROR(preview_frame.TransformTo(c_desired, cms, pool));
|
||||
}
|
||||
for (ImageBundle& ib : frames) {
|
||||
JXL_RETURN_IF_ERROR(ib.TransformTo(c_desired, pool));
|
||||
JXL_RETURN_IF_ERROR(ib.TransformTo(c_desired, cms, pool));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -162,27 +157,6 @@ class CodecInOut {
|
|||
// -- DECODER INPUT:
|
||||
|
||||
SizeConstraints constraints;
|
||||
// Decode to pixels or keep JPEG as quantized DCT coefficients
|
||||
DecodeTarget dec_target = DecodeTarget::kPixels;
|
||||
|
||||
// Intended white luminance, in nits (cd/m^2).
|
||||
// It is used by codecs that do not know the absolute luminance of their
|
||||
// images. For those codecs, decoders map from white to this luminance. There
|
||||
// is no other way of knowing the target brightness for those codecs - depends
|
||||
// on source material. 709 typically targets 100 nits, BT.2100 PQ up to 10K,
|
||||
// but HDR content is more typically mastered to 4K nits. Codecs that do know
|
||||
// the absolute luminance of their images will typically ignore it as a
|
||||
// decoder input. The corresponding decoder output and encoder input is the
|
||||
// intensity target in the metadata. ALL decoders MUST set that metadata
|
||||
// appropriately, but it does not have to be identical to this hint. Encoders
|
||||
// for codecs that do not encode absolute luminance levels should use that
|
||||
// metadata to decide on what to map to white. Encoders for codecs that *do*
|
||||
// encode absolute luminance levels may use it to decide on encoding values,
|
||||
// but not in a way that would affect the range of interpreted luminance.
|
||||
//
|
||||
// 0 means that it is up to the codec to decide on a reasonable value to use.
|
||||
|
||||
float target_nits = 0;
|
||||
|
||||
// -- DECODER OUTPUT:
|
||||
|
||||
|
|
|
@ -40,7 +40,6 @@ void RoundtripPermutation(coeff_order_t* perm, coeff_order_t* out, size_t len,
|
|||
|
||||
enum Permutation { kIdentity, kFewSwaps, kFewSlides, kRandom };
|
||||
|
||||
constexpr size_t kNumReps = 128;
|
||||
constexpr size_t kSwaps = 32;
|
||||
|
||||
void TestPermutation(Permutation kind, size_t len) {
|
||||
|
@ -72,11 +71,9 @@ void TestPermutation(Permutation kind, size_t len) {
|
|||
}
|
||||
std::vector<coeff_order_t> out(len);
|
||||
size_t size = 0;
|
||||
for (size_t i = 0; i < kNumReps; i++) {
|
||||
RoundtripPermutation(perm.data(), out.data(), len, &size);
|
||||
for (size_t idx = 0; idx < len; idx++) {
|
||||
EXPECT_EQ(perm[idx], out[idx]);
|
||||
}
|
||||
RoundtripPermutation(perm.data(), out.data(), len, &size);
|
||||
for (size_t idx = 0; idx < len; idx++) {
|
||||
EXPECT_EQ(perm[idx], out[idx]);
|
||||
}
|
||||
printf("Encoded size: %" PRIuS "\n", size);
|
||||
}
|
||||
|
|
|
@ -703,8 +703,8 @@ Status AdaptToXYZD50(float wx, float wy, float matrix[9]) {
|
|||
return true;
|
||||
}
|
||||
|
||||
Status PrimariesToXYZD50(float rx, float ry, float gx, float gy, float bx,
|
||||
float by, float wx, float wy, float matrix[9]) {
|
||||
Status PrimariesToXYZ(float rx, float ry, float gx, float gy, float bx,
|
||||
float by, float wx, float wy, float matrix[9]) {
|
||||
if (wx < 0 || wx > 1 || wy <= 0 || wy > 1) {
|
||||
return JXL_FAILURE("Invalid white point");
|
||||
}
|
||||
|
@ -727,9 +727,14 @@ Status PrimariesToXYZD50(float rx, float ry, float gx, float gy, float bx,
|
|||
xyz[0], 0, 0, 0, xyz[1], 0, 0, 0, xyz[2],
|
||||
};
|
||||
|
||||
float toXYZ[9];
|
||||
MatMul(primaries, a, 3, 3, 3, toXYZ);
|
||||
MatMul(primaries, a, 3, 3, 3, matrix);
|
||||
return true;
|
||||
}
|
||||
|
||||
Status PrimariesToXYZD50(float rx, float ry, float gx, float gy, float bx,
|
||||
float by, float wx, float wy, float matrix[9]) {
|
||||
float toXYZ[9];
|
||||
JXL_RETURN_IF_ERROR(PrimariesToXYZ(rx, ry, gx, gy, bx, by, wx, wy, toXYZ));
|
||||
float d50[9];
|
||||
JXL_RETURN_IF_ERROR(AdaptToXYZD50(wx, wy, d50));
|
||||
|
||||
|
|
|
@ -450,6 +450,8 @@ void ConvertInternalToExternalColorEncoding(const jxl::ColorEncoding& internal,
|
|||
Status ConvertExternalToInternalColorEncoding(const JxlColorEncoding& external,
|
||||
jxl::ColorEncoding* internal);
|
||||
|
||||
Status PrimariesToXYZ(float rx, float ry, float gx, float gy, float bx,
|
||||
float by, float wx, float wy, float matrix[9]);
|
||||
Status PrimariesToXYZD50(float rx, float ry, float gx, float gy, float bx,
|
||||
float by, float wx, float wy, float matrix[9]);
|
||||
Status AdaptToXYZD50(float wx, float wy, float matrix[9]);
|
||||
|
|
|
@ -3,12 +3,6 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Defined by build system; this avoids IDE warnings. Must come before
|
||||
// color_management.h (affects header definitions).
|
||||
#ifndef JPEGXL_ENABLE_SKCMS
|
||||
#define JPEGXL_ENABLE_SKCMS 0
|
||||
#endif
|
||||
|
||||
#include "lib/jxl/color_management.h"
|
||||
|
||||
#include <math.h>
|
||||
|
|
|
@ -132,20 +132,21 @@ class ColorManagementTest
|
|||
static void VerifyPixelRoundTrip(const ColorEncoding& c) {
|
||||
Globals* g = Globals::GetInstance();
|
||||
const ColorEncoding& c_native = c.IsGray() ? g->c_gray : g->c_native;
|
||||
ColorSpaceTransform xform_fwd;
|
||||
ColorSpaceTransform xform_rev;
|
||||
ASSERT_TRUE(xform_fwd.Init(c_native, c, kDefaultIntensityTarget, kWidth,
|
||||
const JxlCmsInterface& cms = GetJxlCms();
|
||||
ColorSpaceTransform xform_fwd(cms);
|
||||
ColorSpaceTransform xform_rev(cms);
|
||||
const float intensity_target =
|
||||
c.tf.IsHLG() ? 1000 : kDefaultIntensityTarget;
|
||||
ASSERT_TRUE(xform_fwd.Init(c_native, c, intensity_target, kWidth,
|
||||
g->pool.NumThreads()));
|
||||
ASSERT_TRUE(xform_rev.Init(c, c_native, kDefaultIntensityTarget, kWidth,
|
||||
ASSERT_TRUE(xform_rev.Init(c, c_native, intensity_target, kWidth,
|
||||
g->pool.NumThreads()));
|
||||
|
||||
const size_t thread = 0;
|
||||
const ImageF& in = c.IsGray() ? g->in_gray : g->in_color;
|
||||
ImageF* JXL_RESTRICT out = c.IsGray() ? &g->out_gray : &g->out_color;
|
||||
DoColorSpaceTransform(&xform_fwd, thread, in.Row(0),
|
||||
xform_fwd.BufDst(thread));
|
||||
DoColorSpaceTransform(&xform_rev, thread, xform_fwd.BufDst(thread),
|
||||
out->Row(0));
|
||||
ASSERT_TRUE(xform_fwd.Run(thread, in.Row(0), xform_fwd.BufDst(thread)));
|
||||
ASSERT_TRUE(xform_rev.Run(thread, xform_fwd.BufDst(thread), out->Row(0)));
|
||||
|
||||
#if JPEGXL_ENABLE_SKCMS
|
||||
double max_l1 = 7E-4;
|
||||
|
@ -222,18 +223,18 @@ TEST_F(ColorManagementTest, D2700ToSRGB) {
|
|||
ColorEncoding sRGB_D2700;
|
||||
ASSERT_TRUE(sRGB_D2700.SetICC(std::move(icc)));
|
||||
|
||||
ColorSpaceTransform transform;
|
||||
ColorSpaceTransform transform(GetJxlCms());
|
||||
ASSERT_TRUE(transform.Init(sRGB_D2700, ColorEncoding::SRGB(),
|
||||
kDefaultIntensityTarget, 1, 1));
|
||||
const float sRGB_D2700_values[3] = {0.863, 0.737, 0.490};
|
||||
float sRGB_values[3];
|
||||
DoColorSpaceTransform(&transform, 0, sRGB_D2700_values, sRGB_values);
|
||||
ASSERT_TRUE(transform.Run(0, sRGB_D2700_values, sRGB_values));
|
||||
EXPECT_THAT(sRGB_values,
|
||||
ElementsAre(FloatNear(0.914, 1e-3), FloatNear(0.745, 1e-3),
|
||||
FloatNear(0.601, 1e-3)));
|
||||
}
|
||||
|
||||
TEST_F(ColorManagementTest, P3HLGTo2020HLG) {
|
||||
TEST_F(ColorManagementTest, P3HlgTo2020Hlg) {
|
||||
ColorEncoding p3_hlg;
|
||||
p3_hlg.SetColorSpace(ColorSpace::kRGB);
|
||||
p3_hlg.white_point = WhitePoint::kD65;
|
||||
|
@ -245,15 +246,76 @@ TEST_F(ColorManagementTest, P3HLGTo2020HLG) {
|
|||
rec2020_hlg.primaries = Primaries::k2100;
|
||||
ASSERT_TRUE(rec2020_hlg.CreateICC());
|
||||
|
||||
ColorSpaceTransform transform;
|
||||
ColorSpaceTransform transform(GetJxlCms());
|
||||
ASSERT_TRUE(transform.Init(p3_hlg, rec2020_hlg, 1000, 1, 1));
|
||||
const float p3_hlg_values[3] = {0., 0.75, 0.};
|
||||
float rec2020_hlg_values[3];
|
||||
DoColorSpaceTransform(&transform, 0, p3_hlg_values, rec2020_hlg_values);
|
||||
ASSERT_TRUE(transform.Run(0, p3_hlg_values, rec2020_hlg_values));
|
||||
EXPECT_THAT(rec2020_hlg_values,
|
||||
ElementsAre(FloatNear(0.3973, 1e-4), FloatNear(0.7382, 1e-4),
|
||||
FloatNear(0.1183, 1e-4)));
|
||||
}
|
||||
|
||||
TEST_F(ColorManagementTest, HlgOotf) {
|
||||
ColorEncoding p3_hlg;
|
||||
p3_hlg.SetColorSpace(ColorSpace::kRGB);
|
||||
p3_hlg.white_point = WhitePoint::kD65;
|
||||
p3_hlg.primaries = Primaries::kP3;
|
||||
p3_hlg.tf.SetTransferFunction(TransferFunction::kHLG);
|
||||
ASSERT_TRUE(p3_hlg.CreateICC());
|
||||
|
||||
ColorSpaceTransform transform_to_1000(GetJxlCms());
|
||||
ASSERT_TRUE(
|
||||
transform_to_1000.Init(p3_hlg, ColorEncoding::LinearSRGB(), 1000, 1, 1));
|
||||
// HDR reference white: https://www.itu.int/pub/R-REP-BT.2408-4-2021
|
||||
float p3_hlg_values[3] = {0.75, 0.75, 0.75};
|
||||
float linear_srgb_values[3];
|
||||
ASSERT_TRUE(transform_to_1000.Run(0, p3_hlg_values, linear_srgb_values));
|
||||
// On a 1000-nit display, HDR reference white should be 203 cd/m² which is
|
||||
// 0.203 times the maximum.
|
||||
EXPECT_THAT(linear_srgb_values,
|
||||
ElementsAre(FloatNear(0.203, 1e-3), FloatNear(0.203, 1e-3),
|
||||
FloatNear(0.203, 1e-3)));
|
||||
|
||||
ColorSpaceTransform transform_to_400(GetJxlCms());
|
||||
ASSERT_TRUE(
|
||||
transform_to_400.Init(p3_hlg, ColorEncoding::LinearSRGB(), 400, 1, 1));
|
||||
ASSERT_TRUE(transform_to_400.Run(0, p3_hlg_values, linear_srgb_values));
|
||||
// On a 400-nit display, it should be 100 cd/m².
|
||||
EXPECT_THAT(linear_srgb_values,
|
||||
ElementsAre(FloatNear(0.250, 1e-3), FloatNear(0.250, 1e-3),
|
||||
FloatNear(0.250, 1e-3)));
|
||||
|
||||
p3_hlg_values[2] = 0.50;
|
||||
ASSERT_TRUE(transform_to_1000.Run(0, p3_hlg_values, linear_srgb_values));
|
||||
EXPECT_THAT(linear_srgb_values,
|
||||
ElementsAre(FloatNear(0.201, 1e-3), FloatNear(0.201, 1e-3),
|
||||
FloatNear(0.050, 1e-3)));
|
||||
|
||||
ColorSpaceTransform transform_from_400(GetJxlCms());
|
||||
ASSERT_TRUE(
|
||||
transform_from_400.Init(ColorEncoding::LinearSRGB(), p3_hlg, 400, 1, 1));
|
||||
linear_srgb_values[0] = linear_srgb_values[1] = linear_srgb_values[2] = 0.250;
|
||||
ASSERT_TRUE(transform_from_400.Run(0, linear_srgb_values, p3_hlg_values));
|
||||
EXPECT_THAT(p3_hlg_values,
|
||||
ElementsAre(FloatNear(0.75, 1e-3), FloatNear(0.75, 1e-3),
|
||||
FloatNear(0.75, 1e-3)));
|
||||
|
||||
ColorEncoding grayscale_hlg;
|
||||
grayscale_hlg.SetColorSpace(ColorSpace::kGray);
|
||||
grayscale_hlg.white_point = WhitePoint::kD65;
|
||||
grayscale_hlg.tf.SetTransferFunction(TransferFunction::kHLG);
|
||||
ASSERT_TRUE(grayscale_hlg.CreateICC());
|
||||
|
||||
ColorSpaceTransform grayscale_transform(GetJxlCms());
|
||||
ASSERT_TRUE(grayscale_transform.Init(
|
||||
grayscale_hlg, ColorEncoding::LinearSRGB(/*is_gray=*/true), 1000, 1, 1));
|
||||
const float grayscale_hlg_value = 0.75;
|
||||
float linear_grayscale_value;
|
||||
ASSERT_TRUE(grayscale_transform.Run(0, &grayscale_hlg_value,
|
||||
&linear_grayscale_value));
|
||||
EXPECT_THAT(linear_grayscale_value, FloatNear(0.203, 1e-3));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace jxl
|
||||
|
|
|
@ -117,6 +117,13 @@ struct FrameDimensions {
|
|||
num_dc_groups = xsize_dc_groups * ysize_dc_groups;
|
||||
}
|
||||
|
||||
size_t GetUpsampledXSize(bool is_color_c) {
|
||||
return is_color_c ? xsize_upsampled_padded : xsize_upsampled;
|
||||
}
|
||||
size_t GetUpsampledYSize(bool is_color_c) {
|
||||
return is_color_c ? ysize_upsampled_padded : ysize_upsampled;
|
||||
}
|
||||
|
||||
// Image size without any upsampling, i.e. original_size / upsampling.
|
||||
size_t xsize;
|
||||
size_t ysize;
|
||||
|
|
|
@ -149,7 +149,7 @@ void AdaptiveDCSmoothing(const float* dc_factors, Image3F* dc,
|
|||
xsize * sizeof(float));
|
||||
}
|
||||
}
|
||||
auto process_row = [&](int y, int /*thread*/) {
|
||||
auto process_row = [&](const uint32_t y, size_t /*thread*/) {
|
||||
const float* JXL_RESTRICT rows_top[3]{
|
||||
dc->ConstPlaneRow(0, y - 1),
|
||||
dc->ConstPlaneRow(1, y - 1),
|
||||
|
@ -193,8 +193,8 @@ void AdaptiveDCSmoothing(const float* dc_factors, Image3F* dc,
|
|||
x);
|
||||
}
|
||||
};
|
||||
RunOnPool(pool, 1, ysize - 1, ThreadPool::SkipInit(), process_row,
|
||||
"DCSmoothingRow");
|
||||
JXL_CHECK(RunOnPool(pool, 1, ysize - 1, ThreadPool::NoInit, process_row,
|
||||
"DCSmoothingRow"));
|
||||
dc->Swap(smoothed);
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "lib/jxl/enc_adaptive_quantization.h"
|
||||
#include "lib/jxl/enc_butteraugli_comparator.h"
|
||||
#include "lib/jxl/enc_cache.h"
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
#include "lib/jxl/enc_params.h"
|
||||
#include "lib/jxl/enc_xyb.h"
|
||||
#include "lib/jxl/frame_header.h"
|
||||
|
@ -49,7 +50,7 @@ void RunRGBRoundTrip(float distance, bool fast) {
|
|||
io.ShrinkTo(std::min(io.xsize(), kGroupDim), std::min(io.ysize(), kGroupDim));
|
||||
|
||||
Image3F opsin(io.xsize(), io.ysize());
|
||||
(void)ToXYB(io.Main(), &pool, &opsin);
|
||||
(void)ToXYB(io.Main(), &pool, &opsin, GetJxlCms());
|
||||
opsin = PadImageToMultiple(opsin, kBlockDim);
|
||||
GaborishInverse(&opsin, 1.0f, &pool);
|
||||
|
||||
|
@ -82,10 +83,10 @@ void RunRGBRoundTrip(float distance, bool fast) {
|
|||
enc_state.cparams = cparams;
|
||||
ZeroFillImage(&enc_state.shared.epf_sharpness);
|
||||
CodecInOut io1;
|
||||
io1.Main() = RoundtripImage(opsin, &enc_state, &pool);
|
||||
io1.Main() = RoundtripImage(opsin, &enc_state, GetJxlCms(), &pool);
|
||||
io1.metadata.m.color_encoding = io1.Main().c_current();
|
||||
|
||||
EXPECT_LE(ButteraugliDistance(io, io1, cparams.ba_params,
|
||||
EXPECT_LE(ButteraugliDistance(io, io1, cparams.ba_params, GetJxlCms(),
|
||||
/*distmap=*/nullptr, &pool),
|
||||
1.2);
|
||||
}
|
||||
|
|
|
@ -839,13 +839,13 @@ class ConvolveT {
|
|||
const Weights& weights,
|
||||
ThreadPool* pool, ImageF* out) {
|
||||
const int64_t stride = in.PixelsPerRow();
|
||||
RunOnPool(
|
||||
pool, ybegin, yend, ThreadPool::SkipInit(),
|
||||
[&](const int y, int /*thread*/) HWY_ATTR {
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, ybegin, yend, ThreadPool::NoInit,
|
||||
[&](const uint32_t y, size_t /*thread*/) HWY_ATTR {
|
||||
RunRow<kSizeModN>(rect.ConstRow(in, y), rect.xsize(), stride,
|
||||
WrapRowUnchanged(), weights, out->Row(y));
|
||||
},
|
||||
"Convolve");
|
||||
"Convolve"));
|
||||
}
|
||||
|
||||
// Image3F.
|
||||
|
@ -856,16 +856,16 @@ class ConvolveT {
|
|||
const Weights& weights,
|
||||
ThreadPool* pool, Image3F* out) {
|
||||
const int64_t stride = in.PixelsPerRow();
|
||||
RunOnPool(
|
||||
pool, ybegin, yend, ThreadPool::SkipInit(),
|
||||
[&](const int y, int /*thread*/) HWY_ATTR {
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, ybegin, yend, ThreadPool::NoInit,
|
||||
[&](const uint32_t y, size_t /*thread*/) HWY_ATTR {
|
||||
for (size_t c = 0; c < 3; ++c) {
|
||||
RunRow<kSizeModN>(rect.ConstPlaneRow(in, c, y), rect.xsize(),
|
||||
stride, WrapRowUnchanged(), weights,
|
||||
out->PlaneRow(c, y));
|
||||
}
|
||||
},
|
||||
"Convolve3");
|
||||
"Convolve3"));
|
||||
}
|
||||
|
||||
template <size_t kSizeModN, class Image, class Weights>
|
||||
|
@ -949,9 +949,9 @@ void Symmetric5(const ImageF& in, const Rect& rect,
|
|||
PROFILER_FUNC;
|
||||
|
||||
const size_t ysize = rect.ysize();
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const int64_t iy = task;
|
||||
|
||||
if (iy < 2 || iy >= static_cast<ssize_t>(ysize) - 2) {
|
||||
|
@ -960,7 +960,7 @@ void Symmetric5(const ImageF& in, const Rect& rect,
|
|||
Symmetric5Row<WrapUnchanged>(in, rect, iy, weights, out->Row(iy));
|
||||
}
|
||||
},
|
||||
"Symmetric5x5Convolution");
|
||||
"Symmetric5x5Convolution"));
|
||||
}
|
||||
|
||||
void Symmetric5_3(const Image3F& in, const Rect& rect,
|
||||
|
@ -969,9 +969,9 @@ void Symmetric5_3(const Image3F& in, const Rect& rect,
|
|||
PROFILER_FUNC;
|
||||
|
||||
const size_t ysize = rect.ysize();
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const size_t iy = task;
|
||||
|
||||
if (iy < 2 || iy >= ysize - 2) {
|
||||
|
@ -986,7 +986,7 @@ void Symmetric5_3(const Image3F& in, const Rect& rect,
|
|||
}
|
||||
}
|
||||
},
|
||||
"Symmetric5x5Convolution3");
|
||||
"Symmetric5x5Convolution3"));
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
|
@ -1152,9 +1152,9 @@ void SlowSymmetric3(const ImageF& in, const Rect& rect,
|
|||
const int64_t ysize = static_cast<int64_t>(rect.ysize());
|
||||
const int64_t kRadius = 1;
|
||||
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const int64_t iy = task;
|
||||
float* JXL_RESTRICT out_row = out->Row(static_cast<size_t>(iy));
|
||||
|
||||
|
@ -1165,7 +1165,7 @@ void SlowSymmetric3(const ImageF& in, const Rect& rect,
|
|||
out_row);
|
||||
}
|
||||
},
|
||||
"SlowSymmetric3");
|
||||
"SlowSymmetric3"));
|
||||
}
|
||||
|
||||
void SlowSymmetric3(const Image3F& in, const Rect& rect,
|
||||
|
@ -1177,9 +1177,9 @@ void SlowSymmetric3(const Image3F& in, const Rect& rect,
|
|||
const int64_t ysize = static_cast<int64_t>(rect.ysize());
|
||||
const int64_t kRadius = 1;
|
||||
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const int64_t iy = task;
|
||||
const size_t oy = static_cast<size_t>(iy);
|
||||
|
||||
|
@ -1195,7 +1195,7 @@ void SlowSymmetric3(const Image3F& in, const Rect& rect,
|
|||
}
|
||||
}
|
||||
},
|
||||
"SlowSymmetric3");
|
||||
"SlowSymmetric3"));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -1235,9 +1235,9 @@ void SlowSeparable5(const ImageF& in, const Rect& rect,
|
|||
const float* vert_weights = &weights.vert[0];
|
||||
|
||||
const size_t ysize = rect.ysize();
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const int64_t y = task;
|
||||
|
||||
float* const JXL_RESTRICT row_out = out->Row(y);
|
||||
|
@ -1246,7 +1246,7 @@ void SlowSeparable5(const ImageF& in, const Rect& rect,
|
|||
horz_weights, vert_weights);
|
||||
}
|
||||
},
|
||||
"SlowSeparable5");
|
||||
"SlowSeparable5"));
|
||||
}
|
||||
|
||||
void SlowSeparable5(const Image3F& in, const Rect& rect,
|
||||
|
@ -1265,9 +1265,9 @@ void SlowSeparable7(const ImageF& in, const Rect& rect,
|
|||
const float* vert_weights = &weights.vert[0];
|
||||
|
||||
const size_t ysize = rect.ysize();
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const int64_t y = task;
|
||||
|
||||
float* const JXL_RESTRICT row_out = out->Row(y);
|
||||
|
@ -1276,7 +1276,7 @@ void SlowSeparable7(const ImageF& in, const Rect& rect,
|
|||
horz_weights, vert_weights);
|
||||
}
|
||||
},
|
||||
"SlowSeparable7");
|
||||
"SlowSeparable7"));
|
||||
}
|
||||
|
||||
void SlowSeparable7(const Image3F& in, const Rect& rect,
|
||||
|
@ -1296,9 +1296,9 @@ void SlowLaplacian5(const ImageF& in, const Rect& rect, ThreadPool* pool,
|
|||
const size_t ysize = rect.ysize();
|
||||
const WrapMirror wrap;
|
||||
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const int64_t y = task;
|
||||
|
||||
const float* const JXL_RESTRICT row_t =
|
||||
|
@ -1318,7 +1318,7 @@ void SlowLaplacian5(const ImageF& in, const Rect& rect, ThreadPool* pool,
|
|||
row_out[x] = r;
|
||||
}
|
||||
},
|
||||
"SlowLaplacian5");
|
||||
"SlowLaplacian5"));
|
||||
}
|
||||
|
||||
void SlowLaplacian5(const Image3F& in, const Rect& rect, ThreadPool* pool,
|
||||
|
|
|
@ -137,36 +137,39 @@ void TestConvolve() {
|
|||
TestNeighbors();
|
||||
|
||||
ThreadPoolInternal pool(4);
|
||||
pool.Run(kConvolveMaxRadius, 40, ThreadPool::SkipInit(),
|
||||
[](const int task, int /*thread*/) {
|
||||
const size_t xsize = task;
|
||||
Rng rng(129 + 13 * xsize);
|
||||
EXPECT_EQ(true,
|
||||
RunOnPool(
|
||||
&pool, kConvolveMaxRadius, 40, ThreadPool::NoInit,
|
||||
[](const uint32_t task, size_t /*thread*/) {
|
||||
const size_t xsize = task;
|
||||
Rng rng(129 + 13 * xsize);
|
||||
|
||||
ThreadPool* null_pool = nullptr;
|
||||
ThreadPoolInternal pool3(3);
|
||||
for (size_t ysize = kConvolveMaxRadius; ysize < 16; ++ysize) {
|
||||
JXL_DEBUG(JXL_DEBUG_CONVOLVE,
|
||||
"%" PRIuS " x %" PRIuS
|
||||
" (target %d)===============================",
|
||||
xsize, ysize, HWY_TARGET);
|
||||
ThreadPool* null_pool = nullptr;
|
||||
ThreadPoolInternal pool3(3);
|
||||
for (size_t ysize = kConvolveMaxRadius; ysize < 16; ++ysize) {
|
||||
JXL_DEBUG(JXL_DEBUG_CONVOLVE,
|
||||
"%" PRIuS " x %" PRIuS
|
||||
" (target %d)===============================",
|
||||
xsize, ysize, HWY_TARGET);
|
||||
|
||||
JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sym3------------------");
|
||||
VerifySymmetric3(xsize, ysize, null_pool, &rng);
|
||||
VerifySymmetric3(xsize, ysize, &pool3, &rng);
|
||||
JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sym3------------------");
|
||||
VerifySymmetric3(xsize, ysize, null_pool, &rng);
|
||||
VerifySymmetric3(xsize, ysize, &pool3, &rng);
|
||||
|
||||
JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sym5------------------");
|
||||
VerifySymmetric5(xsize, ysize, null_pool, &rng);
|
||||
VerifySymmetric5(xsize, ysize, &pool3, &rng);
|
||||
JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sym5------------------");
|
||||
VerifySymmetric5(xsize, ysize, null_pool, &rng);
|
||||
VerifySymmetric5(xsize, ysize, &pool3, &rng);
|
||||
|
||||
JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sep5------------------");
|
||||
VerifySeparable5(xsize, ysize, null_pool, &rng);
|
||||
VerifySeparable5(xsize, ysize, &pool3, &rng);
|
||||
JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sep5------------------");
|
||||
VerifySeparable5(xsize, ysize, null_pool, &rng);
|
||||
VerifySeparable5(xsize, ysize, &pool3, &rng);
|
||||
|
||||
JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sep7------------------");
|
||||
VerifySeparable7(xsize, ysize, null_pool, &rng);
|
||||
VerifySeparable7(xsize, ysize, &pool3, &rng);
|
||||
}
|
||||
});
|
||||
JXL_DEBUG(JXL_DEBUG_CONVOLVE, "Sep7------------------");
|
||||
VerifySeparable7(xsize, ysize, null_pool, &rng);
|
||||
VerifySeparable7(xsize, ysize, &pool3, &rng);
|
||||
}
|
||||
},
|
||||
"TestConvolve"));
|
||||
}
|
||||
|
||||
// Measures durations, verifies results, prints timings. `unpredictable1`
|
||||
|
|
|
@ -53,8 +53,8 @@ int TestInit(void* jpegxl_opaque, size_t num_threads) { return 0; }
|
|||
|
||||
TEST_F(DataParallelTest, RunnerCalledParamenters) {
|
||||
EXPECT_TRUE(pool_.Run(
|
||||
1234, 5678, [](const size_t num_threads) { return true; },
|
||||
[](const int task, const int thread) { return; }));
|
||||
1234, 5678, [](size_t /* num_threads */) { return true; },
|
||||
[](uint32_t /* task */, size_t /* thread */) { return; }));
|
||||
EXPECT_EQ(1, runner_called_);
|
||||
EXPECT_NE(nullptr, init_);
|
||||
EXPECT_NE(nullptr, func_);
|
||||
|
@ -66,21 +66,21 @@ TEST_F(DataParallelTest, RunnerCalledParamenters) {
|
|||
TEST_F(DataParallelTest, RunnerFailurePropagates) {
|
||||
runner_return_ = -1; // FakeRunner return value.
|
||||
EXPECT_FALSE(pool_.Run(
|
||||
1234, 5678, [](const size_t num_threads) { return false; },
|
||||
[](const int task, const int thread) { return; }));
|
||||
1234, 5678, [](size_t /* num_threads */) { return false; },
|
||||
[](uint32_t /* task */, size_t /* thread */) { return; }));
|
||||
EXPECT_FALSE(RunOnPool(
|
||||
nullptr, 1234, 5678, [](const size_t num_threads) { return false; },
|
||||
[](const int task, const int thread) { return; }, "Test"));
|
||||
nullptr, 1234, 5678, [](size_t /* num_threads */) { return false; },
|
||||
[](uint32_t /* task */, size_t /* thread */) { return; }, "Test"));
|
||||
}
|
||||
|
||||
TEST_F(DataParallelTest, RunnerNotCalledOnEmptyRange) {
|
||||
runner_return_ = -1; // FakeRunner return value.
|
||||
EXPECT_TRUE(pool_.Run(
|
||||
123, 123, [](const size_t num_threads) { return false; },
|
||||
[](const int task, const int thread) { return; }));
|
||||
123, 123, [](size_t /* num_threads */) { return false; },
|
||||
[](uint32_t /* task */, size_t /* thread */) { return; }));
|
||||
EXPECT_TRUE(RunOnPool(
|
||||
nullptr, 123, 123, [](const size_t num_threads) { return false; },
|
||||
[](const int task, const int thread) { return; }, "Test"));
|
||||
nullptr, 123, 123, [](size_t /* num_threads */) { return false; },
|
||||
[](uint32_t /* task */, size_t /* thread */) { return; }, "Test"));
|
||||
// We don't call the external runner when the range is empty. We don't even
|
||||
// need to call the init function.
|
||||
EXPECT_EQ(0, runner_called_);
|
||||
|
|
|
@ -159,9 +159,9 @@ template <size_t N>
|
|||
void TestInverseT(float accuracy) {
|
||||
ThreadPoolInternal pool(N < 32 ? 0 : 8);
|
||||
enum { kBlockSize = N * N };
|
||||
RunOnPool(
|
||||
&pool, 0, kBlockSize, ThreadPool::SkipInit(),
|
||||
[accuracy](const int task, int /*thread*/) {
|
||||
EXPECT_TRUE(RunOnPool(
|
||||
&pool, 0, kBlockSize, ThreadPool::NoInit,
|
||||
[accuracy](const uint32_t task, size_t /*thread*/) {
|
||||
const size_t i = static_cast<size_t>(task);
|
||||
HWY_ALIGN float x[kBlockSize] = {0.0f};
|
||||
x[i] = 1.0;
|
||||
|
@ -174,7 +174,7 @@ void TestInverseT(float accuracy) {
|
|||
<< "i = " << i << ", k = " << k;
|
||||
}
|
||||
},
|
||||
"TestInverse");
|
||||
"TestInverse"));
|
||||
}
|
||||
|
||||
void InverseTest() {
|
||||
|
|
|
@ -235,7 +235,11 @@ class ANSSymbolReader {
|
|||
return ReadSymbolWithoutRefill(histo_idx, br);
|
||||
}
|
||||
|
||||
bool CheckANSFinalState() { return state_ == (ANS_SIGNATURE << 16u); }
|
||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
bool CheckANSFinalState() const { return true; }
|
||||
#else
|
||||
bool CheckANSFinalState() const { return state_ == (ANS_SIGNATURE << 16u); }
|
||||
#endif
|
||||
|
||||
template <typename BitReader>
|
||||
static JXL_INLINE uint32_t ReadHybridUintConfig(
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "lib/jxl/image.h"
|
||||
#include "lib/jxl/passes_state.h"
|
||||
#include "lib/jxl/quant_weights.h"
|
||||
#include "lib/jxl/render_pipeline/render_pipeline.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
|
||||
namespace jxl {
|
||||
|
@ -106,6 +107,13 @@ struct PassesDecoderState {
|
|||
// Manages the status of borders.
|
||||
GroupBorderAssigner group_border_assigner;
|
||||
|
||||
// Rendering pipeline. TODO(veluca): eventually, this pipeline will replace
|
||||
// most of the state in this struct.
|
||||
std::unique_ptr<RenderPipeline> render_pipeline;
|
||||
|
||||
// Storage for the current frame if it can be referenced by future frames.
|
||||
ImageBundle frame_storage_for_referencing;
|
||||
|
||||
// TODO(veluca): this should eventually become "iff no global modular
|
||||
// transform was applied".
|
||||
bool EagerFinalizeImageRect() const {
|
||||
|
@ -279,7 +287,7 @@ struct PassesDecoderState {
|
|||
}
|
||||
|
||||
// Initialize the decoder state after all of DC is decoded.
|
||||
void InitForAC(ThreadPool* pool) {
|
||||
Status InitForAC(ThreadPool* pool) {
|
||||
shared_storage.coeff_order_size = 0;
|
||||
for (uint8_t o = 0; o < AcStrategy::kNumValidStrategies; ++o) {
|
||||
if (((1 << o) & used_acs) == 0) continue;
|
||||
|
@ -293,21 +301,23 @@ struct PassesDecoderState {
|
|||
if (sz > shared_storage.coeff_orders.size()) {
|
||||
shared_storage.coeff_orders.resize(sz);
|
||||
}
|
||||
if (shared->frame_header.flags & FrameHeader::kNoise) {
|
||||
if (shared->frame_header.flags & FrameHeader::kNoise && !render_pipeline) {
|
||||
noise = Image3F(shared->frame_dim.xsize_upsampled_padded,
|
||||
shared->frame_dim.ysize_upsampled_padded);
|
||||
size_t num_x_groups = DivCeil(noise.xsize(), kGroupDim);
|
||||
size_t num_y_groups = DivCeil(noise.ysize(), kGroupDim);
|
||||
PROFILER_ZONE("GenerateNoise");
|
||||
auto generate_noise = [&](int group_index, int _) {
|
||||
auto generate_noise = [&](const uint32_t group_index,
|
||||
size_t /* thread */) {
|
||||
size_t gx = group_index % num_x_groups;
|
||||
size_t gy = group_index / num_x_groups;
|
||||
Rect rect(gx * kGroupDim, gy * kGroupDim, kGroupDim, kGroupDim,
|
||||
noise.xsize(), noise.ysize());
|
||||
RandomImage3(noise_seed + group_index, rect, &noise);
|
||||
};
|
||||
RunOnPool(pool, 0, num_x_groups * num_y_groups, ThreadPool::SkipInit(),
|
||||
generate_noise, "Generate noise");
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, num_x_groups * num_y_groups,
|
||||
ThreadPool::NoInit, generate_noise,
|
||||
"Generate noise"));
|
||||
{
|
||||
PROFILER_ZONE("High pass noise");
|
||||
// 4 * (1 - box kernel)
|
||||
|
@ -336,6 +346,7 @@ struct PassesDecoderState {
|
|||
// Avoid errors due to loading vectors on the outermost padding.
|
||||
FillImage(msan::kSanitizerSentinel, &decoded);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void EnsureBordersStorage();
|
||||
|
|
|
@ -123,16 +123,16 @@ void StoreLEFloat(float value, uint8_t* p) {
|
|||
// The orientation may not be identity.
|
||||
// TODO(lode): SIMDify where possible
|
||||
template <typename T>
|
||||
void UndoOrientation(jxl::Orientation undo_orientation, const Plane<T>& image,
|
||||
Plane<T>& out, jxl::ThreadPool* pool) {
|
||||
Status UndoOrientation(jxl::Orientation undo_orientation, const Plane<T>& image,
|
||||
Plane<T>& out, jxl::ThreadPool* pool) {
|
||||
const size_t xsize = image.xsize();
|
||||
const size_t ysize = image.ysize();
|
||||
|
||||
if (undo_orientation == Orientation::kFlipHorizontal) {
|
||||
out = Plane<T>(xsize, ysize);
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const int64_t y = task;
|
||||
const T* JXL_RESTRICT row_in = image.Row(y);
|
||||
T* JXL_RESTRICT row_out = out.Row(y);
|
||||
|
@ -140,12 +140,12 @@ void UndoOrientation(jxl::Orientation undo_orientation, const Plane<T>& image,
|
|||
row_out[xsize - x - 1] = row_in[x];
|
||||
}
|
||||
},
|
||||
"UndoOrientation");
|
||||
"UndoOrientation"));
|
||||
} else if (undo_orientation == Orientation::kRotate180) {
|
||||
out = Plane<T>(xsize, ysize);
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const int64_t y = task;
|
||||
const T* JXL_RESTRICT row_in = image.Row(y);
|
||||
T* JXL_RESTRICT row_out = out.Row(ysize - y - 1);
|
||||
|
@ -153,12 +153,12 @@ void UndoOrientation(jxl::Orientation undo_orientation, const Plane<T>& image,
|
|||
row_out[xsize - x - 1] = row_in[x];
|
||||
}
|
||||
},
|
||||
"UndoOrientation");
|
||||
"UndoOrientation"));
|
||||
} else if (undo_orientation == Orientation::kFlipVertical) {
|
||||
out = Plane<T>(xsize, ysize);
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const int64_t y = task;
|
||||
const T* JXL_RESTRICT row_in = image.Row(y);
|
||||
T* JXL_RESTRICT row_out = out.Row(ysize - y - 1);
|
||||
|
@ -166,56 +166,57 @@ void UndoOrientation(jxl::Orientation undo_orientation, const Plane<T>& image,
|
|||
row_out[x] = row_in[x];
|
||||
}
|
||||
},
|
||||
"UndoOrientation");
|
||||
"UndoOrientation"));
|
||||
} else if (undo_orientation == Orientation::kTranspose) {
|
||||
out = Plane<T>(ysize, xsize);
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const int64_t y = task;
|
||||
const T* JXL_RESTRICT row_in = image.Row(y);
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
out.Row(x)[y] = row_in[x];
|
||||
}
|
||||
},
|
||||
"UndoOrientation");
|
||||
"UndoOrientation"));
|
||||
} else if (undo_orientation == Orientation::kRotate90) {
|
||||
out = Plane<T>(ysize, xsize);
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const int64_t y = task;
|
||||
const T* JXL_RESTRICT row_in = image.Row(y);
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
out.Row(x)[ysize - y - 1] = row_in[x];
|
||||
}
|
||||
},
|
||||
"UndoOrientation");
|
||||
"UndoOrientation"));
|
||||
} else if (undo_orientation == Orientation::kAntiTranspose) {
|
||||
out = Plane<T>(ysize, xsize);
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const int64_t y = task;
|
||||
const T* JXL_RESTRICT row_in = image.Row(y);
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
out.Row(xsize - x - 1)[ysize - y - 1] = row_in[x];
|
||||
}
|
||||
},
|
||||
"UndoOrientation");
|
||||
"UndoOrientation"));
|
||||
} else if (undo_orientation == Orientation::kRotate270) {
|
||||
out = Plane<T>(ysize, xsize);
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const int64_t y = task;
|
||||
const T* JXL_RESTRICT row_in = image.Row(y);
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
out.Row(xsize - x - 1)[y] = row_in[x];
|
||||
}
|
||||
},
|
||||
"UndoOrientation");
|
||||
"UndoOrientation"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
@ -306,7 +307,8 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
|
|||
if (undo_orientation != Orientation::kIdentity) {
|
||||
for (size_t c = 0; c < num_channels; ++c) {
|
||||
if (channels[c]) {
|
||||
UndoOrientation(undo_orientation, *channels[c], temp_channels[c], pool);
|
||||
JXL_RETURN_IF_ERROR(UndoOrientation(undo_orientation, *channels[c],
|
||||
temp_channels[c], pool));
|
||||
channels[c] = &(temp_channels[c]);
|
||||
}
|
||||
}
|
||||
|
@ -315,12 +317,15 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
|
|||
// First channel may not be nullptr.
|
||||
size_t xsize = channels[0]->xsize();
|
||||
size_t ysize = channels[0]->ysize();
|
||||
|
||||
if (stride < bytes_per_pixel * xsize) {
|
||||
return JXL_FAILURE("stride is smaller than scanline width in bytes: %" PRIuS
|
||||
" vs %" PRIuS,
|
||||
stride, bytes_per_pixel * xsize);
|
||||
}
|
||||
if (!out_callback &&
|
||||
out_size < (ysize - 1) * stride + bytes_per_pixel * xsize) {
|
||||
return JXL_FAILURE("out_size is too small to store image");
|
||||
}
|
||||
|
||||
const bool little_endian =
|
||||
endianness == JXL_LITTLE_ENDIAN ||
|
||||
|
@ -341,7 +346,7 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
|
|||
if (bits_per_sample == 16) {
|
||||
bool swap_endianness = little_endian != IsLittleEndian();
|
||||
Plane<hwy::float16_t> f16_cache;
|
||||
RunOnPool(
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize),
|
||||
[&](size_t num_threads) {
|
||||
f16_cache =
|
||||
|
@ -349,7 +354,7 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
|
|||
InitOutCallback(num_threads);
|
||||
return true;
|
||||
},
|
||||
[&](const int task, int thread) {
|
||||
[&](const uint32_t task, const size_t thread) {
|
||||
const int64_t y = task;
|
||||
const float* JXL_RESTRICT row_in[kConvertMaxChannels];
|
||||
for (size_t c = 0; c < num_channels; c++) {
|
||||
|
@ -383,15 +388,15 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
|
|||
(*out_callback)(out_opaque, 0, y, xsize, row_out);
|
||||
}
|
||||
},
|
||||
"ConvertF16");
|
||||
"ConvertF16"));
|
||||
} else if (bits_per_sample == 32) {
|
||||
RunOnPool(
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize),
|
||||
[&](size_t num_threads) {
|
||||
InitOutCallback(num_threads);
|
||||
return true;
|
||||
},
|
||||
[&](const int task, int thread) {
|
||||
[&](const uint32_t task, const size_t thread) {
|
||||
const int64_t y = task;
|
||||
uint8_t* row_out =
|
||||
out_callback
|
||||
|
@ -410,7 +415,7 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
|
|||
(*out_callback)(out_opaque, 0, y, xsize, row_out);
|
||||
}
|
||||
},
|
||||
"ConvertFloat");
|
||||
"ConvertFloat"));
|
||||
} else {
|
||||
return JXL_FAILURE("float other than 16-bit and 32-bit not supported");
|
||||
}
|
||||
|
@ -419,14 +424,14 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
|
|||
// range.
|
||||
float mul = (1ull << bits_per_sample) - 1;
|
||||
Plane<uint32_t> u32_cache;
|
||||
RunOnPool(
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, static_cast<uint32_t>(ysize),
|
||||
[&](size_t num_threads) {
|
||||
u32_cache = Plane<uint32_t>(xsize, num_channels * num_threads);
|
||||
InitOutCallback(num_threads);
|
||||
return true;
|
||||
},
|
||||
[&](const int task, int thread) {
|
||||
[&](const uint32_t task, const size_t thread) {
|
||||
const int64_t y = task;
|
||||
uint8_t* row_out =
|
||||
out_callback
|
||||
|
@ -465,7 +470,7 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
|
|||
(*out_callback)(out_opaque, 0, y, xsize, row_out);
|
||||
}
|
||||
},
|
||||
"ConvertUint");
|
||||
"ConvertUint"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -53,6 +53,16 @@
|
|||
#include "lib/jxl/passes_state.h"
|
||||
#include "lib/jxl/quant_weights.h"
|
||||
#include "lib/jxl/quantizer.h"
|
||||
#include "lib/jxl/render_pipeline/stage_chroma_upsampling.h"
|
||||
#include "lib/jxl/render_pipeline/stage_epf.h"
|
||||
#include "lib/jxl/render_pipeline/stage_gaborish.h"
|
||||
#include "lib/jxl/render_pipeline/stage_noise.h"
|
||||
#include "lib/jxl/render_pipeline/stage_patches.h"
|
||||
#include "lib/jxl/render_pipeline/stage_splines.h"
|
||||
#include "lib/jxl/render_pipeline/stage_upsampling.h"
|
||||
#include "lib/jxl/render_pipeline/stage_write_to_ib.h"
|
||||
#include "lib/jxl/render_pipeline/stage_xyb.h"
|
||||
#include "lib/jxl/render_pipeline/stage_ycbcr.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
#include "lib/jxl/splines.h"
|
||||
#include "lib/jxl/toc.h"
|
||||
|
@ -143,7 +153,8 @@ Status DecodeFrame(const DecompressParams& dparams,
|
|||
const SizeConstraints* constraints, bool is_preview) {
|
||||
PROFILER_ZONE("DecodeFrame uninstrumented");
|
||||
|
||||
FrameDecoder frame_decoder(dec_state, metadata, pool);
|
||||
FrameDecoder frame_decoder(dec_state, metadata, pool,
|
||||
dparams.use_slow_render_pipeline);
|
||||
|
||||
frame_decoder.SetFrameSizeLimits(constraints);
|
||||
|
||||
|
@ -291,6 +302,8 @@ Status FrameDecoder::InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded,
|
|||
decoded->RemoveColor();
|
||||
decoded->ClearExtraChannels();
|
||||
|
||||
decoded->duration = frame_header_.animation_frame.duration;
|
||||
|
||||
// Read TOC.
|
||||
uint64_t groups_total_size;
|
||||
const bool has_ac_global = true;
|
||||
|
@ -358,6 +371,7 @@ Status FrameDecoder::InitFrame(BitReader* JXL_RESTRICT br, ImageBundle* decoded,
|
|||
decoded_ac_global_ = false;
|
||||
is_finalized_ = false;
|
||||
finalized_dc_ = false;
|
||||
num_sections_done_ = 0;
|
||||
decoded_dc_groups_.clear();
|
||||
decoded_dc_groups_.resize(frame_dim_.num_dc_groups);
|
||||
decoded_passes_per_ac_group_.clear();
|
||||
|
@ -436,7 +450,7 @@ Status FrameDecoder::ProcessDCGroup(size_t dc_group_id, BitReader* br) {
|
|||
frame_dim_.dc_group_dim, frame_dim_.dc_group_dim);
|
||||
JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeGroup(
|
||||
mrect, br, 3, 1000, ModularStreamId::ModularDC(dc_group_id),
|
||||
/*zerofill=*/false, nullptr, nullptr, allow_partial_frames_));
|
||||
/*zerofill=*/false, nullptr, nullptr, nullptr, allow_partial_frames_));
|
||||
if (frame_header_.encoding == FrameEncoding::kVarDCT) {
|
||||
JXL_RETURN_IF_ERROR(
|
||||
modular_frame_decoder_.DecodeAcMetadata(dc_group_id, br, dec_state_));
|
||||
|
@ -444,7 +458,7 @@ Status FrameDecoder::ProcessDCGroup(size_t dc_group_id, BitReader* br) {
|
|||
FillImage(kInvSigmaNum / lf.epf_sigma_for_modular,
|
||||
&dec_state_->filter_weights.sigma);
|
||||
}
|
||||
decoded_dc_groups_[dc_group_id] = true;
|
||||
decoded_dc_groups_[dc_group_id] = uint8_t{true};
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -461,8 +475,8 @@ void FrameDecoder::FinalizeDC() {
|
|||
finalized_dc_ = true;
|
||||
}
|
||||
|
||||
void FrameDecoder::AllocateOutput() {
|
||||
if (allocated_) return;
|
||||
Status FrameDecoder::AllocateOutput() {
|
||||
if (allocated_) return true;
|
||||
const CodecMetadata& metadata = *frame_header_.nonserialized_metadata;
|
||||
if (dec_state_->rgb_output == nullptr && !dec_state_->pixel_callback) {
|
||||
modular_frame_decoder_.MaybeDropFullImage();
|
||||
|
@ -470,34 +484,54 @@ void FrameDecoder::AllocateOutput() {
|
|||
frame_dim_.ysize_upsampled_padded),
|
||||
dec_state_->output_encoding_info.color_encoding);
|
||||
}
|
||||
dec_state_->extra_channels.clear();
|
||||
if (metadata.m.num_extra_channels > 0) {
|
||||
if (dec_state_->render_pipeline) {
|
||||
// TODO(veluca): consider not reallocating ECs if not needed.
|
||||
decoded_->extra_channels().clear();
|
||||
for (size_t i = 0; i < metadata.m.num_extra_channels; i++) {
|
||||
uint32_t ecups = frame_header_.extra_channel_upsampling[i];
|
||||
dec_state_->extra_channels.emplace_back(
|
||||
DivCeil(frame_dim_.xsize_upsampled_padded, ecups),
|
||||
DivCeil(frame_dim_.ysize_upsampled_padded, ecups));
|
||||
decoded_->extra_channels().emplace_back(
|
||||
frame_dim_.xsize_upsampled_padded, frame_dim_.ysize_upsampled_padded);
|
||||
}
|
||||
if (frame_header_.dc_level != 0) {
|
||||
dec_state_->shared_storage.dc_frames[frame_header_.dc_level - 1] =
|
||||
Image3F(frame_dim_.xsize, frame_dim_.ysize);
|
||||
}
|
||||
if (frame_header_.CanBeReferenced()) {
|
||||
// TODO(veluca): this will need to be adapted for RGB output.
|
||||
JXL_ASSERT(dec_state_->rgb_output == nullptr &&
|
||||
!dec_state_->pixel_callback);
|
||||
dec_state_->frame_storage_for_referencing = decoded_->Copy();
|
||||
}
|
||||
} else {
|
||||
dec_state_->extra_channels.clear();
|
||||
if (metadata.m.num_extra_channels > 0) {
|
||||
for (size_t i = 0; i < metadata.m.num_extra_channels; i++) {
|
||||
uint32_t ecups = frame_header_.extra_channel_upsampling[i];
|
||||
dec_state_->extra_channels.emplace_back(
|
||||
DivCeil(frame_dim_.xsize_upsampled_padded, ecups),
|
||||
DivCeil(frame_dim_.ysize_upsampled_padded, ecups));
|
||||
#if JXL_MEMORY_SANITIZER
|
||||
// Avoid errors due to loading vectors on the outermost padding.
|
||||
// Upsample of extra channels requires this padding to be initialized.
|
||||
// TODO(deymo): Remove this and use rects up to {x,y}size_upsampled
|
||||
// instead of the padded one.
|
||||
for (size_t y = 0; y < DivCeil(frame_dim_.ysize_upsampled_padded, ecups);
|
||||
y++) {
|
||||
for (size_t x = (y < DivCeil(frame_dim_.ysize_upsampled, ecups)
|
||||
? DivCeil(frame_dim_.xsize_upsampled, ecups)
|
||||
: 0);
|
||||
x < DivCeil(frame_dim_.xsize_upsampled_padded, ecups); x++) {
|
||||
dec_state_->extra_channels.back().Row(y)[x] =
|
||||
msan::kSanitizerSentinel;
|
||||
// Avoid errors due to loading vectors on the outermost padding.
|
||||
// Upsample of extra channels requires this padding to be initialized.
|
||||
// TODO(deymo): Remove this and use rects up to {x,y}size_upsampled
|
||||
// instead of the padded one.
|
||||
for (size_t y = 0;
|
||||
y < DivCeil(frame_dim_.ysize_upsampled_padded, ecups); y++) {
|
||||
for (size_t x = (y < DivCeil(frame_dim_.ysize_upsampled, ecups)
|
||||
? DivCeil(frame_dim_.xsize_upsampled, ecups)
|
||||
: 0);
|
||||
x < DivCeil(frame_dim_.xsize_upsampled_padded, ecups); x++) {
|
||||
dec_state_->extra_channels.back().Row(y)[x] =
|
||||
msan::kSanitizerSentinel;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
decoded_->origin = dec_state_->shared->frame_header.frame_origin;
|
||||
dec_state_->InitForAC(nullptr);
|
||||
JXL_RETURN_IF_ERROR(dec_state_->InitForAC(nullptr));
|
||||
allocated_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Status FrameDecoder::ProcessACGlobal(BitReader* br) {
|
||||
|
@ -619,13 +653,21 @@ Status FrameDecoder::ProcessACGroup(size_t ac_group_id,
|
|||
const size_t x = gx * frame_dim_.group_dim;
|
||||
const size_t y = gy * frame_dim_.group_dim;
|
||||
|
||||
RenderPipelineInput render_pipeline_input_storage;
|
||||
RenderPipelineInput* render_pipeline_input = nullptr;
|
||||
if (dec_state_->render_pipeline) {
|
||||
render_pipeline_input_storage =
|
||||
dec_state_->render_pipeline->GetInputBuffers(ac_group_id, thread);
|
||||
render_pipeline_input = &render_pipeline_input_storage;
|
||||
}
|
||||
|
||||
if (frame_header_.encoding == FrameEncoding::kVarDCT) {
|
||||
group_dec_caches_[thread].InitOnce(frame_header_.passes.num_passes,
|
||||
dec_state_->used_acs);
|
||||
JXL_RETURN_IF_ERROR(DecodeGroup(
|
||||
br, num_passes, ac_group_id, dec_state_, &group_dec_caches_[thread],
|
||||
thread, decoded_, decoded_passes_per_ac_group_[ac_group_id], force_draw,
|
||||
dc_only));
|
||||
thread, render_pipeline_input, decoded_,
|
||||
decoded_passes_per_ac_group_[ac_group_id], force_draw, dc_only));
|
||||
}
|
||||
|
||||
// don't limit to image dimensions here (is done in DecodeGroup)
|
||||
|
@ -638,19 +680,203 @@ Status FrameDecoder::ProcessACGroup(size_t ac_group_id,
|
|||
JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeGroup(
|
||||
mrect, br[i - decoded_passes_per_ac_group_[ac_group_id]], minShift,
|
||||
maxShift, ModularStreamId::ModularAC(ac_group_id, i),
|
||||
/*zerofill=*/false, dec_state_, decoded_, allow_partial_frames_));
|
||||
/*zerofill=*/false, dec_state_, render_pipeline_input, decoded_,
|
||||
allow_partial_frames_));
|
||||
} else if (i >= decoded_passes_per_ac_group_[ac_group_id] + num_passes &&
|
||||
force_draw) {
|
||||
JXL_RETURN_IF_ERROR(modular_frame_decoder_.DecodeGroup(
|
||||
mrect, nullptr, minShift, maxShift,
|
||||
ModularStreamId::ModularAC(ac_group_id, i), /*zerofill=*/true,
|
||||
dec_state_, decoded_, allow_partial_frames_));
|
||||
dec_state_, render_pipeline_input, decoded_, allow_partial_frames_));
|
||||
}
|
||||
}
|
||||
decoded_passes_per_ac_group_[ac_group_id] += num_passes;
|
||||
|
||||
if ((frame_header_.flags & FrameHeader::kNoise) != 0 &&
|
||||
render_pipeline_input) {
|
||||
PROFILER_ZONE("GenerateNoise");
|
||||
size_t num_x_groups = DivCeil(frame_dim_.xsize_upsampled_padded, kGroupDim);
|
||||
size_t noise_c_start =
|
||||
3 + frame_header_.nonserialized_metadata->m.num_extra_channels;
|
||||
// When the color channels are downsampled, we need to generate more noise
|
||||
// input for the current group than just the group dimensions.
|
||||
std::pair<ImageF*, Rect> rects[3];
|
||||
for (size_t iy = 0; iy < frame_header_.upsampling; iy++) {
|
||||
for (size_t ix = 0; ix < frame_header_.upsampling; ix++) {
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
auto r = render_pipeline_input->GetBuffer(noise_c_start + c);
|
||||
rects[c].first = r.first;
|
||||
size_t x1 = r.second.x0() + r.second.xsize();
|
||||
size_t y1 = r.second.y0() + r.second.ysize();
|
||||
if (frame_header_.encoding == FrameEncoding::kVarDCT) {
|
||||
x1 = RoundUpTo(x1, kBlockDim * frame_header_.upsampling);
|
||||
y1 = RoundUpTo(y1, kBlockDim * frame_header_.upsampling);
|
||||
}
|
||||
rects[c].second = Rect(r.second.x0() + ix * kGroupDim,
|
||||
r.second.y0() + iy * kGroupDim, kGroupDim,
|
||||
kGroupDim, x1, y1);
|
||||
}
|
||||
Random3Planes(dec_state_->noise_seed +
|
||||
(gx * frame_header_.upsampling + ix) +
|
||||
(gy * frame_header_.upsampling + iy) * num_x_groups,
|
||||
rects[0], rects[1], rects[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (render_pipeline_input && !modular_frame_decoder_.UsesFullImage()) {
|
||||
render_pipeline_input->Done();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void FrameDecoder::PreparePipeline() {
|
||||
size_t num_c = 3 + frame_header_.nonserialized_metadata->m.num_extra_channels;
|
||||
if ((frame_header_.flags & FrameHeader::kNoise) != 0) {
|
||||
num_c += 3;
|
||||
}
|
||||
|
||||
RenderPipeline::Builder builder(num_c, frame_header_.passes.num_passes);
|
||||
|
||||
if (use_slow_rendering_pipeline_) {
|
||||
builder.UseSimpleImplementation();
|
||||
} else {
|
||||
JXL_ABORT("Not implemented: fast pipeline");
|
||||
}
|
||||
|
||||
if (!frame_header_.chroma_subsampling.Is444()) {
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
if (frame_header_.chroma_subsampling.HShift(c) != 0) {
|
||||
builder.AddStage(GetChromaUpsamplingStage(c, /*horizontal=*/true));
|
||||
}
|
||||
if (frame_header_.chroma_subsampling.VShift(c) != 0) {
|
||||
builder.AddStage(GetChromaUpsamplingStage(c, /*horizontal=*/false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frame_header_.loop_filter.gab) {
|
||||
builder.AddStage(GetGaborishStage(frame_header_.loop_filter));
|
||||
}
|
||||
|
||||
{
|
||||
const LoopFilter& lf = frame_header_.loop_filter;
|
||||
if (lf.epf_iters >= 3) {
|
||||
builder.AddStage(GetEPFStage(lf, dec_state_->filter_weights.sigma, 0));
|
||||
}
|
||||
if (lf.epf_iters >= 1) {
|
||||
builder.AddStage(GetEPFStage(lf, dec_state_->filter_weights.sigma, 1));
|
||||
}
|
||||
if (lf.epf_iters >= 2) {
|
||||
builder.AddStage(GetEPFStage(lf, dec_state_->filter_weights.sigma, 2));
|
||||
}
|
||||
}
|
||||
|
||||
bool late_ec_upsample = frame_header_.upsampling != 1;
|
||||
for (auto ecups : frame_header_.extra_channel_upsampling) {
|
||||
if (ecups != frame_header_.upsampling) {
|
||||
// If patches are applied, either frame_header.upsampling == 1 or
|
||||
// late_ec_upsample is true.
|
||||
late_ec_upsample = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!late_ec_upsample) {
|
||||
for (size_t ec = 0; ec < frame_header_.extra_channel_upsampling.size();
|
||||
ec++) {
|
||||
if (frame_header_.extra_channel_upsampling[ec] != 1) {
|
||||
builder.AddStage(GetUpsamplingStage(
|
||||
frame_header_.nonserialized_metadata->transform_data, 3 + ec,
|
||||
CeilLog2Nonzero(frame_header_.extra_channel_upsampling[ec])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((frame_header_.flags & FrameHeader::kPatches) != 0) {
|
||||
builder.AddStage(
|
||||
GetPatchesStage(&dec_state_->shared->image_features.patches));
|
||||
}
|
||||
if ((frame_header_.flags & FrameHeader::kSplines) != 0) {
|
||||
builder.AddStage(
|
||||
GetSplineStage(&dec_state_->shared->image_features.splines));
|
||||
}
|
||||
|
||||
if (frame_header_.upsampling != 1) {
|
||||
size_t nb_channels =
|
||||
3 +
|
||||
(late_ec_upsample ? frame_header_.extra_channel_upsampling.size() : 0);
|
||||
for (size_t c = 0; c < nb_channels; c++) {
|
||||
builder.AddStage(GetUpsamplingStage(
|
||||
frame_header_.nonserialized_metadata->transform_data, c,
|
||||
CeilLog2Nonzero(frame_header_.upsampling)));
|
||||
}
|
||||
}
|
||||
|
||||
if ((frame_header_.flags & FrameHeader::kNoise) != 0) {
|
||||
builder.AddStage(GetConvolveNoiseStage(num_c - 3));
|
||||
builder.AddStage(
|
||||
GetAddNoiseStage(dec_state_->shared->image_features.noise_params,
|
||||
dec_state_->shared->cmap, num_c - 3));
|
||||
builder.UsesNoise();
|
||||
}
|
||||
if (frame_header_.dc_level != 0) {
|
||||
builder.AddStage(GetWriteToImage3FStage(
|
||||
&dec_state_->shared_storage.dc_frames[frame_header_.dc_level - 1]));
|
||||
}
|
||||
if (!coalescing_) {
|
||||
JXL_ABORT("Not implemented: skip coalescing");
|
||||
}
|
||||
|
||||
if (frame_header_.CanBeReferenced() &&
|
||||
frame_header_.save_before_color_transform) {
|
||||
builder.AddStage(
|
||||
GetWriteToImageBundleStage(&dec_state_->frame_storage_for_referencing));
|
||||
}
|
||||
|
||||
if (frame_header_.color_transform == ColorTransform::kYCbCr) {
|
||||
builder.AddStage(GetYCbCrStage());
|
||||
} else if (frame_header_.color_transform == ColorTransform::kXYB) {
|
||||
builder.AddStage(GetXYBStage(dec_state_->output_encoding_info));
|
||||
} // Nothing to do for kNone.
|
||||
|
||||
if (ImageBlender::NeedsBlending(dec_state_)) {
|
||||
JXL_ABORT("Not implemented: blending");
|
||||
}
|
||||
|
||||
if (frame_header_.CanBeReferenced() &&
|
||||
!frame_header_.save_before_color_transform) {
|
||||
builder.AddStage(
|
||||
GetWriteToImageBundleStage(&dec_state_->frame_storage_for_referencing));
|
||||
}
|
||||
|
||||
if (render_spotcolors_ &&
|
||||
frame_header_.nonserialized_metadata->m.Find(ExtraChannel::kSpotColor)) {
|
||||
JXL_ABORT("Not implemented: rendering spot colors");
|
||||
}
|
||||
if (dec_state_->pixel_callback) {
|
||||
JXL_ABORT("Not implemented: pixel callback");
|
||||
} else if (dec_state_->fast_xyb_srgb8_conversion) {
|
||||
JXL_ABORT("Not implemented: fast xyb->srgb conversion");
|
||||
} else if (dec_state_->rgb_output) {
|
||||
JXL_ABORT("Not implemented: u8 output");
|
||||
} else {
|
||||
builder.AddStage(GetWriteToImageBundleStage(decoded_));
|
||||
}
|
||||
dec_state_->render_pipeline = std::move(builder).Finalize(frame_dim_);
|
||||
}
|
||||
|
||||
void FrameDecoder::MarkSections(const SectionInfo* sections, size_t num,
|
||||
SectionStatus* section_status) {
|
||||
num_sections_done_ = num;
|
||||
for (size_t i = 0; i < num; i++) {
|
||||
if (section_status[i] == SectionStatus::kSkipped ||
|
||||
section_status[i] == SectionStatus::kPartial) {
|
||||
processed_section_[sections[i].id] = false;
|
||||
num_sections_done_--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
|
||||
SectionStatus* section_status) {
|
||||
if (num == 0) return true; // Nothing to process
|
||||
|
@ -662,7 +888,9 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
|
|||
frame_dim_.num_groups,
|
||||
std::vector<size_t>(frame_header_.passes.num_passes, num));
|
||||
std::vector<size_t> num_ac_passes(frame_dim_.num_groups);
|
||||
if (frame_dim_.num_groups == 1 && frame_header_.passes.num_passes == 1) {
|
||||
bool single_section =
|
||||
frame_dim_.num_groups == 1 && frame_header_.passes.num_passes == 1;
|
||||
if (single_section) {
|
||||
JXL_ASSERT(num == 1);
|
||||
JXL_ASSERT(sections[0].id == 0);
|
||||
if (processed_section_[0] == false) {
|
||||
|
@ -724,8 +952,8 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
|
|||
|
||||
std::atomic<bool> has_error{false};
|
||||
if (decoded_dc_global_) {
|
||||
RunOnPool(
|
||||
pool_, 0, dc_group_sec.size(), ThreadPool::SkipInit(),
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool_, 0, dc_group_sec.size(), ThreadPool::NoInit,
|
||||
[this, &dc_group_sec, &num, §ions, §ion_status, &has_error](
|
||||
size_t i, size_t thread) {
|
||||
if (dc_group_sec[i] != num) {
|
||||
|
@ -736,18 +964,49 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
|
|||
}
|
||||
}
|
||||
},
|
||||
"DecodeDCGroup");
|
||||
"DecodeDCGroup"));
|
||||
}
|
||||
if (has_error) return JXL_FAILURE("Error in DC group");
|
||||
|
||||
if (*std::min_element(decoded_dc_groups_.begin(), decoded_dc_groups_.end()) ==
|
||||
true &&
|
||||
if (*std::min_element(decoded_dc_groups_.begin(), decoded_dc_groups_.end()) &&
|
||||
!finalized_dc_) {
|
||||
if (use_slow_rendering_pipeline_) {
|
||||
PreparePipeline();
|
||||
}
|
||||
FinalizeDC();
|
||||
AllocateOutput();
|
||||
JXL_RETURN_IF_ERROR(AllocateOutput());
|
||||
if (pause_at_progressive_ && !single_section) {
|
||||
bool can_return_dc = true;
|
||||
if (single_section) {
|
||||
// If there's only one group and one pass, there is no separate section
|
||||
// for DC and the entire full resolution image is available at once.
|
||||
can_return_dc = false;
|
||||
}
|
||||
if (!decoded_->metadata()->extra_channel_info.empty()) {
|
||||
// If extra channels are encoded with modular without squeeze, they
|
||||
// don't support DC. If the are encoded with squeeze, DC works in theory
|
||||
// but the implementation may not yet correctly support this for Flush.
|
||||
// Therefore, can't correctly pause for a progressive step if there is
|
||||
// an extra channel (including alpha channel)
|
||||
can_return_dc = false;
|
||||
}
|
||||
if (frame_header_.encoding != FrameEncoding::kVarDCT) {
|
||||
// DC is not guaranteed to be available in modular mode and may be a
|
||||
// black image. If squeeze is used, it may be available depending on the
|
||||
// current implementation.
|
||||
// TODO(lode): do return DC if it's known that flushing at this point
|
||||
// will produce a valid 1/8th downscaled image with modular encoding.
|
||||
can_return_dc = false;
|
||||
}
|
||||
if (can_return_dc) {
|
||||
MarkSections(sections, num, section_status);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (finalized_dc_) dec_state_->EnsureBordersStorage();
|
||||
|
||||
if (finalized_dc_ && ac_global_sec != num && !decoded_ac_global_) {
|
||||
JXL_RETURN_IF_ERROR(ProcessACGlobal(sections[ac_global_sec].br));
|
||||
section_status[ac_global_sec] = SectionStatus::kDone;
|
||||
|
@ -769,7 +1028,7 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
|
|||
dec_state_->group_border_assigner.ClearDone(i);
|
||||
}
|
||||
|
||||
RunOnPool(
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool_, 0, ac_group_sec.size(),
|
||||
[this](size_t num_threads) {
|
||||
PrepareStorage(num_threads, decoded_passes_per_ac_group_.size());
|
||||
|
@ -798,16 +1057,11 @@ Status FrameDecoder::ProcessSections(const SectionInfo* sections, size_t num,
|
|||
}
|
||||
}
|
||||
},
|
||||
"DecodeGroup");
|
||||
"DecodeGroup"));
|
||||
}
|
||||
if (has_error) return JXL_FAILURE("Error in AC group");
|
||||
|
||||
for (size_t i = 0; i < num; i++) {
|
||||
if (section_status[i] == SectionStatus::kSkipped ||
|
||||
section_status[i] == SectionStatus::kPartial) {
|
||||
processed_section_[sections[i].id] = false;
|
||||
}
|
||||
}
|
||||
MarkSections(sections, num, section_status);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -832,7 +1086,7 @@ Status FrameDecoder::Flush() {
|
|||
// Nothing to do.
|
||||
return true;
|
||||
}
|
||||
AllocateOutput();
|
||||
JXL_RETURN_IF_ERROR(AllocateOutput());
|
||||
|
||||
uint32_t completely_decoded_ac_pass = *std::min_element(
|
||||
decoded_passes_per_ac_group_.begin(), decoded_passes_per_ac_group_.end());
|
||||
|
@ -845,13 +1099,13 @@ Status FrameDecoder::Flush() {
|
|||
dec_state_->group_border_assigner.ClearDone(i);
|
||||
}
|
||||
std::atomic<bool> has_error{false};
|
||||
RunOnPool(
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool_, 0, decoded_passes_per_ac_group_.size(),
|
||||
[this](size_t num_threads) {
|
||||
[this](const size_t num_threads) {
|
||||
PrepareStorage(num_threads, decoded_passes_per_ac_group_.size());
|
||||
return true;
|
||||
},
|
||||
[this, &has_error](size_t g, size_t thread) {
|
||||
[this, &has_error](const uint32_t g, size_t thread) {
|
||||
if (decoded_passes_per_ac_group_[g] ==
|
||||
frame_header_.passes.num_passes) {
|
||||
// This group was drawn already, nothing to do.
|
||||
|
@ -863,7 +1117,7 @@ Status FrameDecoder::Flush() {
|
|||
/*force_draw=*/true, /*dc_only=*/!decoded_ac_global_);
|
||||
if (!ok) has_error = true;
|
||||
},
|
||||
"ForceDrawGroup");
|
||||
"ForceDrawGroup"));
|
||||
if (has_error) {
|
||||
return JXL_FAILURE("Drawing groups failed");
|
||||
}
|
||||
|
@ -895,17 +1149,23 @@ int FrameDecoder::SavedAs(const FrameHeader& header) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool FrameDecoder::HasEverything() const {
|
||||
if (!decoded_dc_global_) return false;
|
||||
if (!decoded_ac_global_) return false;
|
||||
for (auto& have_dc_group : decoded_dc_groups_) {
|
||||
if (!have_dc_group) return false;
|
||||
}
|
||||
for (auto& nb_passes : decoded_passes_per_ac_group_) {
|
||||
if (nb_passes < max_passes_) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int FrameDecoder::References() const {
|
||||
if (is_finalized_) {
|
||||
return 0;
|
||||
}
|
||||
if ((!decoded_dc_global_ || !decoded_ac_global_ ||
|
||||
*std::min_element(decoded_dc_groups_.begin(),
|
||||
decoded_dc_groups_.end()) != 1 ||
|
||||
*std::min_element(decoded_passes_per_ac_group_.begin(),
|
||||
decoded_passes_per_ac_group_.end()) < max_passes_)) {
|
||||
return 0;
|
||||
}
|
||||
if (!HasEverything()) return 0;
|
||||
|
||||
int result = 0;
|
||||
|
||||
|
@ -954,63 +1214,61 @@ Status FrameDecoder::FinalizeFrame() {
|
|||
// particularly useful anyway on upsampling results), so we disable it.
|
||||
dec_state_->shared_storage.frame_header.loop_filter.epf_iters = 0;
|
||||
}
|
||||
if ((!decoded_dc_global_ || !decoded_ac_global_ ||
|
||||
*std::min_element(decoded_dc_groups_.begin(),
|
||||
decoded_dc_groups_.end()) != 1 ||
|
||||
*std::min_element(decoded_passes_per_ac_group_.begin(),
|
||||
decoded_passes_per_ac_group_.end()) < max_passes_) &&
|
||||
!allow_partial_frames_) {
|
||||
if (!HasEverything() && !allow_partial_frames_) {
|
||||
return JXL_FAILURE(
|
||||
"FinalizeFrame called before the frame was fully decoded");
|
||||
}
|
||||
|
||||
if (!finalized_dc_) {
|
||||
JXL_ASSERT(allow_partial_frames_);
|
||||
AllocateOutput();
|
||||
JXL_RETURN_IF_ERROR(AllocateOutput());
|
||||
}
|
||||
|
||||
JXL_RETURN_IF_ERROR(Flush());
|
||||
|
||||
if (dec_state_->shared->frame_header.CanBeReferenced() &&
|
||||
(frame_header_.frame_type != kRegularFrame || coalescing_)) {
|
||||
size_t id = dec_state_->shared->frame_header.save_as_reference;
|
||||
auto& reference_frame = dec_state_->shared_storage.reference_frames[id];
|
||||
if (dec_state_->pre_color_transform_frame.xsize() == 0) {
|
||||
reference_frame.storage = decoded_->Copy();
|
||||
} else {
|
||||
reference_frame.storage = ImageBundle(decoded_->metadata());
|
||||
reference_frame.storage.SetFromImage(
|
||||
CopyImage(dec_state_->pre_color_transform_frame),
|
||||
decoded_->c_current());
|
||||
if (decoded_->HasExtraChannels()) {
|
||||
const std::vector<ImageF>* ecs = &dec_state_->pre_color_transform_ec;
|
||||
if (ecs->empty()) ecs = &decoded_->extra_channels();
|
||||
std::vector<ImageF> extra_channels;
|
||||
for (const auto& ec : *ecs) {
|
||||
extra_channels.push_back(CopyImage(ec));
|
||||
if (!dec_state_->render_pipeline) {
|
||||
if (dec_state_->shared->frame_header.CanBeReferenced() &&
|
||||
(frame_header_.frame_type != kRegularFrame || coalescing_)) {
|
||||
size_t id = dec_state_->shared->frame_header.save_as_reference;
|
||||
auto& reference_frame = dec_state_->shared_storage.reference_frames[id];
|
||||
if (dec_state_->pre_color_transform_frame.xsize() == 0) {
|
||||
reference_frame.storage = decoded_->Copy();
|
||||
} else {
|
||||
reference_frame.storage = ImageBundle(decoded_->metadata());
|
||||
reference_frame.storage.SetFromImage(
|
||||
CopyImage(dec_state_->pre_color_transform_frame),
|
||||
decoded_->c_current());
|
||||
if (decoded_->HasExtraChannels()) {
|
||||
const std::vector<ImageF>* ecs = &dec_state_->pre_color_transform_ec;
|
||||
if (ecs->empty()) ecs = &decoded_->extra_channels();
|
||||
std::vector<ImageF> extra_channels;
|
||||
for (const auto& ec : *ecs) {
|
||||
extra_channels.push_back(CopyImage(ec));
|
||||
}
|
||||
reference_frame.storage.SetExtraChannels(std::move(extra_channels));
|
||||
}
|
||||
reference_frame.storage.SetExtraChannels(std::move(extra_channels));
|
||||
}
|
||||
}
|
||||
reference_frame.frame = &reference_frame.storage;
|
||||
reference_frame.ib_is_in_xyb =
|
||||
dec_state_->shared->frame_header.save_before_color_transform;
|
||||
if (!dec_state_->shared->frame_header.save_before_color_transform) {
|
||||
const CodecMetadata* metadata =
|
||||
dec_state_->shared->frame_header.nonserialized_metadata;
|
||||
if (reference_frame.frame->xsize() < metadata->xsize() ||
|
||||
reference_frame.frame->ysize() < metadata->ysize()) {
|
||||
return JXL_FAILURE(
|
||||
"trying to save a reference frame that is too small: %" PRIuS
|
||||
"x%" PRIuS
|
||||
" "
|
||||
"instead of %" PRIuS "x%" PRIuS,
|
||||
reference_frame.frame->xsize(), reference_frame.frame->ysize(),
|
||||
metadata->xsize(), metadata->ysize());
|
||||
reference_frame.frame = &reference_frame.storage;
|
||||
reference_frame.ib_is_in_xyb =
|
||||
dec_state_->shared->frame_header.save_before_color_transform;
|
||||
if (!dec_state_->shared->frame_header.save_before_color_transform) {
|
||||
const CodecMetadata* metadata =
|
||||
dec_state_->shared->frame_header.nonserialized_metadata;
|
||||
if (reference_frame.frame->xsize() < metadata->xsize() ||
|
||||
reference_frame.frame->ysize() < metadata->ysize()) {
|
||||
return JXL_FAILURE(
|
||||
"trying to save a reference frame that is too small: %" PRIuS
|
||||
"x%" PRIuS
|
||||
" "
|
||||
"instead of %" PRIuS "x%" PRIuS,
|
||||
reference_frame.frame->xsize(), reference_frame.frame->ysize(),
|
||||
metadata->xsize(), metadata->ysize());
|
||||
}
|
||||
reference_frame.storage.ShrinkTo(metadata->xsize(), metadata->ysize());
|
||||
}
|
||||
reference_frame.storage.ShrinkTo(metadata->xsize(), metadata->ysize());
|
||||
}
|
||||
}
|
||||
|
||||
if (frame_header_.nonserialized_is_preview) {
|
||||
// Fix possible larger image size (multiple of kBlockDim)
|
||||
// TODO(lode): verify if and when that happens.
|
||||
|
@ -1033,7 +1291,7 @@ Status FrameDecoder::FinalizeFrame() {
|
|||
}
|
||||
}
|
||||
|
||||
if (render_spotcolors_) {
|
||||
if (render_spotcolors_ && !dec_state_->render_pipeline) {
|
||||
for (size_t i = 0; i < decoded_->extra_channels().size(); i++) {
|
||||
// Don't use Find() because there may be multiple spot color channels.
|
||||
const ExtraChannelInfo& eci = decoded_->metadata()->extra_channel_info[i];
|
||||
|
@ -1063,12 +1321,20 @@ Status FrameDecoder::FinalizeFrame() {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (dec_state_->shared->frame_header.dc_level != 0) {
|
||||
if (dec_state_->shared->frame_header.dc_level != 0 &&
|
||||
!dec_state_->render_pipeline) {
|
||||
dec_state_->shared_storage
|
||||
.dc_frames[dec_state_->shared->frame_header.dc_level - 1] =
|
||||
std::move(*decoded_->color());
|
||||
decoded_->RemoveColor();
|
||||
}
|
||||
if (dec_state_->render_pipeline && frame_header_.CanBeReferenced()) {
|
||||
auto& info = dec_state_->shared_storage
|
||||
.reference_frames[frame_header_.save_as_reference];
|
||||
info.storage = std::move(dec_state_->frame_storage_for_referencing);
|
||||
info.ib_is_in_xyb = frame_header_.save_before_color_transform;
|
||||
info.frame = &info.storage;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,8 +54,11 @@ class FrameDecoder {
|
|||
public:
|
||||
// All parameters must outlive the FrameDecoder.
|
||||
FrameDecoder(PassesDecoderState* dec_state, const CodecMetadata& metadata,
|
||||
ThreadPool* pool)
|
||||
: dec_state_(dec_state), pool_(pool), frame_header_(&metadata) {}
|
||||
ThreadPool* pool, bool use_slow_rendering_pipeline)
|
||||
: dec_state_(dec_state),
|
||||
pool_(pool),
|
||||
frame_header_(&metadata),
|
||||
use_slow_rendering_pipeline_(use_slow_rendering_pipeline) {}
|
||||
|
||||
// `constraints` must outlive the FrameDecoder if not null, or stay alive
|
||||
// until the next call to SetFrameSizeLimits.
|
||||
|
@ -134,6 +137,15 @@ class FrameDecoder {
|
|||
// Returns whether a DC image has been decoded, accessible at low resolution
|
||||
// at passes.shared_storage.dc_storage
|
||||
bool HasDecodedDC() const { return finalized_dc_; }
|
||||
bool HasDecodedAll() const { return NumSections() == num_sections_done_; }
|
||||
|
||||
// If enabled, ProcessSections will stop and return true when the DC
|
||||
// sections have been processed, instead of starting the AC sections. This
|
||||
// will only occur if supported (that is, flushing will produce a valid
|
||||
// 1/8th*1/8th resolution image). The return value of true then does not mean
|
||||
// all sections have been processed, use HasDecodedDC and HasDecodedAll
|
||||
// to check the true finished state.
|
||||
void SetPauseAtProgressive() { pause_at_progressive_ = true; }
|
||||
|
||||
// Sets the buffer to which uint8 sRGB pixels will be decoded. This is not
|
||||
// supported for all images. If it succeeds, HasRGBBuffer() will return true.
|
||||
|
@ -196,11 +208,14 @@ class FrameDecoder {
|
|||
Status ProcessDCGlobal(BitReader* br);
|
||||
Status ProcessDCGroup(size_t dc_group_id, BitReader* br);
|
||||
void FinalizeDC();
|
||||
void AllocateOutput();
|
||||
Status AllocateOutput();
|
||||
void PreparePipeline();
|
||||
Status ProcessACGlobal(BitReader* br);
|
||||
Status ProcessACGroup(size_t ac_group_id, BitReader* JXL_RESTRICT* br,
|
||||
size_t num_passes, size_t thread, bool force_draw,
|
||||
bool dc_only);
|
||||
void MarkSections(const SectionInfo* sections, size_t num,
|
||||
SectionStatus* section_status);
|
||||
|
||||
// Allocates storage for parallel decoding using up to `num_threads` threads
|
||||
// of up to `num_tasks` tasks. The value of `thread` passed to
|
||||
|
@ -214,6 +229,9 @@ class FrameDecoder {
|
|||
}
|
||||
dec_state_->EnsureStorage(storage_size);
|
||||
use_task_id_ = num_threads > num_tasks;
|
||||
if (dec_state_->render_pipeline) {
|
||||
dec_state_->render_pipeline->PrepareForThreads(storage_size);
|
||||
}
|
||||
}
|
||||
|
||||
size_t GetStorageLocation(size_t thread, size_t task) {
|
||||
|
@ -262,7 +280,9 @@ class FrameDecoder {
|
|||
std::vector<uint8_t> decoded_dc_groups_;
|
||||
bool decoded_dc_global_;
|
||||
bool decoded_ac_global_;
|
||||
bool HasEverything() const;
|
||||
bool finalized_dc_ = true;
|
||||
size_t num_sections_done_ = 0;
|
||||
bool is_finalized_ = true;
|
||||
size_t num_renders_ = 0;
|
||||
bool allocated_ = false;
|
||||
|
@ -275,6 +295,11 @@ class FrameDecoder {
|
|||
// Whether or not the task id should be used for storage indexing, instead of
|
||||
// the thread id.
|
||||
bool use_task_id_ = false;
|
||||
|
||||
// Testing setting: whether or not to use the slow rendering pipeline.
|
||||
bool use_slow_rendering_pipeline_;
|
||||
|
||||
bool pause_at_progressive_ = false;
|
||||
};
|
||||
|
||||
} // namespace jxl
|
||||
|
|
|
@ -169,8 +169,9 @@ void DequantBlock(const AcStrategy& acs, float inv_global_scale, int quant,
|
|||
Status DecodeGroupImpl(GetBlock* JXL_RESTRICT get_block,
|
||||
GroupDecCache* JXL_RESTRICT group_dec_cache,
|
||||
PassesDecoderState* JXL_RESTRICT dec_state,
|
||||
size_t thread, size_t group_idx, ImageBundle* decoded,
|
||||
DrawMode draw) {
|
||||
size_t thread, size_t group_idx,
|
||||
RenderPipelineInput* render_pipeline_input,
|
||||
ImageBundle* decoded, DrawMode draw) {
|
||||
// TODO(veluca): investigate cache usage in this function.
|
||||
PROFILER_FUNC;
|
||||
constexpr size_t kGroupDataXBorder = PassesDecoderState::kGroupDataXBorder;
|
||||
|
@ -191,9 +192,17 @@ Status DecodeGroupImpl(GetBlock* JXL_RESTRICT get_block,
|
|||
const YCbCrChromaSubsampling& cs =
|
||||
dec_state->shared->frame_header.chroma_subsampling;
|
||||
|
||||
const size_t idct_stride = dec_state->EagerFinalizeImageRect()
|
||||
? dec_state->group_data[thread].PixelsPerRow()
|
||||
: dec_state->decoded.PixelsPerRow();
|
||||
size_t idct_stride[3];
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
if (render_pipeline_input) {
|
||||
idct_stride[c] =
|
||||
render_pipeline_input->GetBuffer(c).first->PixelsPerRow();
|
||||
} else {
|
||||
idct_stride[c] = dec_state->EagerFinalizeImageRect()
|
||||
? dec_state->group_data[thread].PixelsPerRow()
|
||||
: dec_state->decoded.PixelsPerRow();
|
||||
}
|
||||
}
|
||||
|
||||
HWY_ALIGN int32_t scaled_qtable[64 * 3];
|
||||
|
||||
|
@ -278,7 +287,10 @@ Status DecodeGroupImpl(GetBlock* JXL_RESTRICT get_block,
|
|||
float* JXL_RESTRICT idct_row[3];
|
||||
int16_t* JXL_RESTRICT jpeg_row[3];
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
if (dec_state->EagerFinalizeImageRect()) {
|
||||
if (render_pipeline_input) {
|
||||
idct_row[c] = render_pipeline_input->GetBuffer(c).second.Row(
|
||||
render_pipeline_input->GetBuffer(c).first, sby[c] * kBlockDim);
|
||||
} else if (dec_state->EagerFinalizeImageRect()) {
|
||||
idct_row[c] = dec_state->group_data[thread].PlaneRow(
|
||||
c, sby[c] * kBlockDim + kGroupDataYBorder) +
|
||||
kGroupDataXBorder;
|
||||
|
@ -427,7 +439,7 @@ Status DecodeGroupImpl(GetBlock* JXL_RESTRICT get_block,
|
|||
// IDCT
|
||||
float* JXL_RESTRICT idct_pos = idct_row[c] + sbx[c] * kBlockDim;
|
||||
TransformToPixels(acs.Strategy(), block + c * size, idct_pos,
|
||||
idct_stride, group_dec_cache->scratch_space);
|
||||
idct_stride[c], group_dec_cache->scratch_space);
|
||||
}
|
||||
}
|
||||
bx += llf_x;
|
||||
|
@ -689,6 +701,7 @@ Status DecodeGroup(BitReader* JXL_RESTRICT* JXL_RESTRICT readers,
|
|||
size_t num_passes, size_t group_idx,
|
||||
PassesDecoderState* JXL_RESTRICT dec_state,
|
||||
GroupDecCache* JXL_RESTRICT group_dec_cache, size_t thread,
|
||||
RenderPipelineInput* render_pipeline_input,
|
||||
ImageBundle* JXL_RESTRICT decoded, size_t first_pass,
|
||||
bool force_draw, bool dc_only) {
|
||||
PROFILER_FUNC;
|
||||
|
@ -727,6 +740,10 @@ Status DecodeGroup(BitReader* JXL_RESTRICT* JXL_RESTRICT readers,
|
|||
PassesDecoderState::kGroupDataYBorder, dst_rect.xsize(),
|
||||
dst_rect.ysize());
|
||||
}
|
||||
if (render_pipeline_input) {
|
||||
dst_rect = render_pipeline_input->GetBuffer(c).second;
|
||||
upsampling_dst = render_pipeline_input->GetBuffer(c).first;
|
||||
}
|
||||
JXL_ASSERT(dst_rect.IsInside(*upsampling_dst));
|
||||
dec_state->upsamplers[2].UpsampleRect(
|
||||
dec_state->filter_input_storage[thread].Plane(c), copy_rect,
|
||||
|
@ -754,8 +771,8 @@ Status DecodeGroup(BitReader* JXL_RESTRICT* JXL_RESTRICT readers,
|
|||
group_dec_cache, dec_state, first_pass));
|
||||
|
||||
JXL_RETURN_IF_ERROR(HWY_DYNAMIC_DISPATCH(DecodeGroupImpl)(
|
||||
&get_block, group_dec_cache, dec_state, thread, group_idx, decoded,
|
||||
draw));
|
||||
&get_block, group_dec_cache, dec_state, thread, group_idx,
|
||||
render_pipeline_input, decoded, draw));
|
||||
|
||||
for (size_t pass = 0; pass < num_passes; pass++) {
|
||||
if (!get_block.decoders[pass].CheckANSFinalState()) {
|
||||
|
@ -781,7 +798,7 @@ Status DecodeGroupForRoundtrip(const std::vector<std::unique_ptr<ACImage>>& ac,
|
|||
|
||||
return HWY_DYNAMIC_DISPATCH(DecodeGroupImpl)(&get_block, group_dec_cache,
|
||||
dec_state, thread, group_idx,
|
||||
decoded, kDraw);
|
||||
nullptr, decoded, kDraw);
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
|
|
@ -32,6 +32,7 @@ Status DecodeGroup(BitReader* JXL_RESTRICT* JXL_RESTRICT readers,
|
|||
size_t num_passes, size_t group_idx,
|
||||
PassesDecoderState* JXL_RESTRICT dec_state,
|
||||
GroupDecCache* JXL_RESTRICT group_dec_cache, size_t thread,
|
||||
RenderPipelineInput* render_pipeline_input,
|
||||
ImageBundle* JXL_RESTRICT decoded, size_t first_pass,
|
||||
bool force_draw, bool dc_only);
|
||||
|
||||
|
|
|
@ -50,37 +50,27 @@ void MultiplySum(const size_t xsize,
|
|||
|
||||
void RgbFromSingle(const size_t xsize,
|
||||
const pixel_type* const JXL_RESTRICT row_in,
|
||||
const float factor, Image3F* decoded, size_t /*c*/, size_t y,
|
||||
Rect& rect) {
|
||||
JXL_DASSERT(xsize <= rect.xsize());
|
||||
const float factor, float* out_r, float* out_g,
|
||||
float* out_b) {
|
||||
const HWY_FULL(float) df;
|
||||
const Rebind<pixel_type, HWY_FULL(float)> di; // assumes pixel_type <= float
|
||||
|
||||
float* const JXL_RESTRICT row_out_r = rect.PlaneRow(decoded, 0, y);
|
||||
float* const JXL_RESTRICT row_out_g = rect.PlaneRow(decoded, 1, y);
|
||||
float* const JXL_RESTRICT row_out_b = rect.PlaneRow(decoded, 2, y);
|
||||
|
||||
const auto factor_v = Set(df, factor);
|
||||
for (size_t x = 0; x < xsize; x += Lanes(di)) {
|
||||
const auto in = Load(di, row_in + x);
|
||||
const auto out = ConvertTo(df, in) * factor_v;
|
||||
Store(out, df, row_out_r + x);
|
||||
Store(out, df, row_out_g + x);
|
||||
Store(out, df, row_out_b + x);
|
||||
Store(out, df, out_r + x);
|
||||
Store(out, df, out_g + x);
|
||||
Store(out, df, out_b + x);
|
||||
}
|
||||
}
|
||||
|
||||
// Same signature as RgbFromSingle so we can assign to the same pointer.
|
||||
void SingleFromSingle(const size_t xsize,
|
||||
const pixel_type* const JXL_RESTRICT row_in,
|
||||
const float factor, Image3F* decoded, size_t c, size_t y,
|
||||
Rect& rect) {
|
||||
JXL_DASSERT(xsize <= rect.xsize());
|
||||
const float factor, float* row_out) {
|
||||
const HWY_FULL(float) df;
|
||||
const Rebind<pixel_type, HWY_FULL(float)> di; // assumes pixel_type <= float
|
||||
|
||||
float* const JXL_RESTRICT row_out = rect.PlaneRow(decoded, c, y);
|
||||
|
||||
const auto factor_v = Set(df, factor);
|
||||
for (size_t x = 0; x < xsize; x += Lanes(di)) {
|
||||
const auto in = Load(di, row_in + x);
|
||||
|
@ -264,7 +254,8 @@ void ModularFrameDecoder::MaybeDropFullImage() {
|
|||
Status ModularFrameDecoder::DecodeGroup(
|
||||
const Rect& rect, BitReader* reader, int minShift, int maxShift,
|
||||
const ModularStreamId& stream, bool zerofill, PassesDecoderState* dec_state,
|
||||
ImageBundle* output, bool allow_truncated) {
|
||||
RenderPipelineInput* render_pipeline_input, ImageBundle* output,
|
||||
bool allow_truncated) {
|
||||
JXL_DASSERT(stream.kind == ModularStreamId::kModularDC ||
|
||||
stream.kind == ModularStreamId::kModularAC);
|
||||
const size_t xsize = rect.xsize();
|
||||
|
@ -304,13 +295,11 @@ Status ModularFrameDecoder::DecodeGroup(
|
|||
if (gi.channel.empty()) return true;
|
||||
ModularOptions options;
|
||||
if (!zerofill) {
|
||||
if (!ModularGenericDecompress(reader, gi, /*header=*/nullptr,
|
||||
stream.ID(frame_dim), &options,
|
||||
/*undo_transforms=*/true, &tree, &code,
|
||||
&context_map, allow_truncated) &&
|
||||
!allow_truncated) {
|
||||
return JXL_FAILURE("Failed to decode modular group");
|
||||
}
|
||||
auto status = ModularGenericDecompress(
|
||||
reader, gi, /*header=*/nullptr, stream.ID(frame_dim), &options,
|
||||
/*undo_transforms=*/true, &tree, &code, &context_map, allow_truncated);
|
||||
if (!allow_truncated) JXL_RETURN_IF_ERROR(status);
|
||||
if (status.IsFatalError()) return status;
|
||||
}
|
||||
// Undo global transforms that have been pushed to the group level
|
||||
if (!use_full_image) {
|
||||
|
@ -318,7 +307,8 @@ Status ModularFrameDecoder::DecodeGroup(
|
|||
JXL_RETURN_IF_ERROR(t.Inverse(gi, global_header.wp_header));
|
||||
}
|
||||
JXL_RETURN_IF_ERROR(ModularImageToDecodedRect(
|
||||
gi, dec_state, nullptr, output, rect.Crop(dec_state->decoded)));
|
||||
gi, dec_state, nullptr, render_pipeline_input, output,
|
||||
rect.Crop(dec_state->decoded), Rect(0, 0, gi.w, gi.h)));
|
||||
return true;
|
||||
}
|
||||
int gic = 0;
|
||||
|
@ -338,6 +328,7 @@ Status ModularFrameDecoder::DecodeGroup(
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Status ModularFrameDecoder::DecodeVarDCTDC(size_t group_id, BitReader* reader,
|
||||
PassesDecoderState* dec_state) {
|
||||
const Rect r = dec_state->shared->DCGroupRect(group_id);
|
||||
|
@ -460,16 +451,27 @@ Status ModularFrameDecoder::DecodeAcMetadata(size_t group_id, BitReader* reader,
|
|||
|
||||
Status ModularFrameDecoder::ModularImageToDecodedRect(
|
||||
Image& gi, PassesDecoderState* dec_state, jxl::ThreadPool* pool,
|
||||
ImageBundle* output, Rect rect) {
|
||||
RenderPipelineInput* render_pipeline_input, ImageBundle* output, Rect rect,
|
||||
Rect modular_rect) {
|
||||
auto& decoded = dec_state->decoded;
|
||||
const auto& frame_header = dec_state->shared->frame_header;
|
||||
const auto* metadata = frame_header.nonserialized_metadata;
|
||||
size_t xsize = rect.xsize();
|
||||
size_t ysize = rect.ysize();
|
||||
if (!xsize || !ysize) {
|
||||
return true;
|
||||
if (!render_pipeline_input) {
|
||||
size_t xsize = rect.xsize();
|
||||
size_t ysize = rect.ysize();
|
||||
if (!xsize || !ysize) {
|
||||
return true;
|
||||
}
|
||||
JXL_DASSERT(rect.IsInside(decoded));
|
||||
}
|
||||
JXL_DASSERT(rect.IsInside(decoded));
|
||||
JXL_CHECK(gi.transform.empty());
|
||||
|
||||
auto get_row = [&](Rect r, size_t c, size_t y) {
|
||||
return render_pipeline_input
|
||||
? render_pipeline_input->GetBuffer(c).second.Row(
|
||||
render_pipeline_input->GetBuffer(c).first, y)
|
||||
: r.PlaneRow(&decoded, c, y);
|
||||
};
|
||||
|
||||
size_t c = 0;
|
||||
if (do_color) {
|
||||
|
@ -496,60 +498,74 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
|
|||
if (ch_in.w == 0 || ch_in.h == 0) {
|
||||
return JXL_FAILURE("Empty image");
|
||||
}
|
||||
size_t xsize_shifted = DivCeil(xsize, 1 << ch_in.hshift);
|
||||
size_t ysize_shifted = DivCeil(ysize, 1 << ch_in.vshift);
|
||||
JXL_CHECK(ch_in.hshift <= 3 && ch_in.vshift <= 3);
|
||||
Rect r(rect.x0() >> ch_in.hshift, rect.y0() >> ch_in.vshift,
|
||||
rect.xsize() >> ch_in.hshift, rect.ysize() >> ch_in.vshift,
|
||||
DivCeil(decoded.xsize(), 1 << ch_in.hshift),
|
||||
DivCeil(decoded.ysize(), 1 << ch_in.vshift));
|
||||
if (r.ysize() != ch_in.h || r.xsize() != ch_in.w) {
|
||||
Rect mr(modular_rect.x0() >> ch_in.hshift,
|
||||
modular_rect.y0() >> ch_in.vshift,
|
||||
DivCeil(modular_rect.xsize(), 1 << ch_in.hshift),
|
||||
DivCeil(modular_rect.ysize(), 1 << ch_in.vshift));
|
||||
mr = mr.Crop(ch_in.plane);
|
||||
if (render_pipeline_input) {
|
||||
r = render_pipeline_input->GetBuffer(c).second;
|
||||
}
|
||||
size_t xsize_shifted = r.xsize();
|
||||
size_t ysize_shifted = r.ysize();
|
||||
if (r.ysize() != mr.ysize() || r.xsize() != mr.xsize()) {
|
||||
return JXL_FAILURE("Dimension mismatch: trying to fit a %" PRIuS
|
||||
"x%" PRIuS
|
||||
" modular channel into "
|
||||
"a %" PRIuS "x%" PRIuS " rect",
|
||||
ch_in.w, ch_in.h, r.xsize(), r.ysize());
|
||||
mr.xsize(), mr.ysize(), r.xsize(), r.ysize());
|
||||
}
|
||||
if (frame_header.color_transform == ColorTransform::kXYB && c == 2) {
|
||||
JXL_ASSERT(!fp);
|
||||
RunOnPool(
|
||||
pool, 0, ysize_shifted, jxl::ThreadPool::SkipInit(),
|
||||
[&](const int task, const int thread) {
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, ysize_shifted, ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /* thread */) {
|
||||
const size_t y = task;
|
||||
const pixel_type* const JXL_RESTRICT row_in = ch_in.Row(y);
|
||||
const pixel_type* const JXL_RESTRICT row_in =
|
||||
mr.Row(&ch_in.plane, y);
|
||||
const pixel_type* const JXL_RESTRICT row_in_Y =
|
||||
gi.channel[0].Row(y);
|
||||
float* const JXL_RESTRICT row_out = r.PlaneRow(&decoded, c, y);
|
||||
mr.Row(&gi.channel[0].plane, y);
|
||||
float* const JXL_RESTRICT row_out = get_row(r, c, y);
|
||||
HWY_DYNAMIC_DISPATCH(MultiplySum)
|
||||
(xsize_shifted, row_in, row_in_Y, factor, row_out);
|
||||
},
|
||||
"ModularIntToFloat");
|
||||
"ModularIntToFloat"));
|
||||
} else if (fp) {
|
||||
int bits = metadata->m.bit_depth.bits_per_sample;
|
||||
int exp_bits = metadata->m.bit_depth.exponent_bits_per_sample;
|
||||
RunOnPool(
|
||||
pool, 0, ysize_shifted, jxl::ThreadPool::SkipInit(),
|
||||
[&](const int task, const int thread) {
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, ysize_shifted, ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /* thread */) {
|
||||
const size_t y = task;
|
||||
const pixel_type* const JXL_RESTRICT row_in = ch_in.Row(y);
|
||||
float* const JXL_RESTRICT row_out = r.PlaneRow(&decoded, c, y);
|
||||
const pixel_type* const JXL_RESTRICT row_in =
|
||||
mr.Row(&ch_in.plane, y);
|
||||
float* const JXL_RESTRICT row_out = get_row(r, c, y);
|
||||
int_to_float(row_in, row_out, xsize_shifted, bits, exp_bits);
|
||||
},
|
||||
"ModularIntToFloat_losslessfloat");
|
||||
"ModularIntToFloat_losslessfloat"));
|
||||
} else {
|
||||
RunOnPool(
|
||||
pool, 0, ysize_shifted, jxl::ThreadPool::SkipInit(),
|
||||
[&](const int task, const int thread) {
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, ysize_shifted, ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /* thread */) {
|
||||
const size_t y = task;
|
||||
const pixel_type* const JXL_RESTRICT row_in = ch_in.Row(y);
|
||||
const pixel_type* const JXL_RESTRICT row_in =
|
||||
mr.Row(&ch_in.plane, y);
|
||||
if (rgb_from_gray) {
|
||||
HWY_DYNAMIC_DISPATCH(RgbFromSingle)
|
||||
(xsize_shifted, row_in, factor, &decoded, c, y, r);
|
||||
(xsize_shifted, row_in, factor, get_row(r, 0, y),
|
||||
get_row(r, 1, y), get_row(r, 2, y));
|
||||
} else {
|
||||
float* const JXL_RESTRICT row_out = get_row(r, c, y);
|
||||
HWY_DYNAMIC_DISPATCH(SingleFromSingle)
|
||||
(xsize_shifted, row_in, factor, &decoded, c, y, r);
|
||||
(xsize_shifted, row_in, factor, row_out);
|
||||
}
|
||||
},
|
||||
"ModularIntToFloat");
|
||||
"ModularIntToFloat"));
|
||||
}
|
||||
if (rgb_from_gray) {
|
||||
break;
|
||||
|
@ -559,7 +575,10 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
|
|||
c = 1;
|
||||
}
|
||||
}
|
||||
for (size_t ec = 0; ec < dec_state->extra_channels.size(); ec++, c++) {
|
||||
size_t num_extra_channels = render_pipeline_input
|
||||
? output->extra_channels().size()
|
||||
: dec_state->extra_channels.size();
|
||||
for (size_t ec = 0; ec < num_extra_channels; ec++, c++) {
|
||||
const ExtraChannelInfo& eci = output->metadata()->extra_channel_info[ec];
|
||||
int bits = eci.bit_depth.bits_per_sample;
|
||||
int exp_bits = eci.bit_depth.exponent_bits_per_sample;
|
||||
|
@ -571,19 +590,33 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
|
|||
const size_t ec_ysize = DivCeil(frame_dim.ysize_upsampled, ecups);
|
||||
JXL_ASSERT(c < gi.channel.size());
|
||||
Channel& ch_in = gi.channel[c];
|
||||
// For x0, y0 there's no need to do a DivCeil().
|
||||
JXL_DASSERT(rect.x0() % (1ul << ch_in.hshift) == 0);
|
||||
JXL_DASSERT(rect.y0() % (1ul << ch_in.vshift) == 0);
|
||||
if (!render_pipeline_input) {
|
||||
// For x0, y0 there's no need to do a DivCeil().
|
||||
JXL_DASSERT(rect.x0() % (1ul << ch_in.hshift) == 0);
|
||||
JXL_DASSERT(rect.y0() % (1ul << ch_in.vshift) == 0);
|
||||
}
|
||||
Rect r(rect.x0() >> ch_in.hshift, rect.y0() >> ch_in.vshift,
|
||||
DivCeil(rect.xsize(), 1lu << ch_in.hshift),
|
||||
DivCeil(rect.ysize(), 1lu << ch_in.vshift), ec_xsize, ec_ysize);
|
||||
|
||||
JXL_DASSERT(r.IsInside(dec_state->extra_channels[ec]));
|
||||
JXL_DASSERT(Rect(0, 0, r.xsize(), r.ysize()).IsInside(ch_in.plane));
|
||||
if (render_pipeline_input) {
|
||||
r = render_pipeline_input->GetBuffer(3 + ec).second;
|
||||
}
|
||||
Rect mr(modular_rect.x0() >> ch_in.hshift,
|
||||
modular_rect.y0() >> ch_in.vshift,
|
||||
DivCeil(modular_rect.xsize(), 1 << ch_in.hshift),
|
||||
DivCeil(modular_rect.ysize(), 1 << ch_in.vshift));
|
||||
mr = mr.Crop(ch_in.plane);
|
||||
|
||||
if (!render_pipeline_input) {
|
||||
JXL_DASSERT(r.IsInside(dec_state->extra_channels[ec]));
|
||||
}
|
||||
for (size_t y = 0; y < r.ysize(); ++y) {
|
||||
float* const JXL_RESTRICT row_out =
|
||||
r.Row(&dec_state->extra_channels[ec], y);
|
||||
const pixel_type* const JXL_RESTRICT row_in = ch_in.Row(y);
|
||||
render_pipeline_input
|
||||
? r.Row(render_pipeline_input->GetBuffer(3 + ec).first, y)
|
||||
: r.Row(&dec_state->extra_channels[ec], y);
|
||||
const pixel_type* const JXL_RESTRICT row_in = mr.Row(&ch_in.plane, y);
|
||||
if (fp) {
|
||||
int_to_float(row_in, row_out, r.xsize(), bits, exp_bits);
|
||||
} else {
|
||||
|
@ -592,7 +625,9 @@ Status ModularFrameDecoder::ModularImageToDecodedRect(
|
|||
}
|
||||
}
|
||||
}
|
||||
JXL_CHECK_IMAGE_INITIALIZED(dec_state->extra_channels[ec], r);
|
||||
if (!render_pipeline_input) {
|
||||
JXL_CHECK_IMAGE_INITIALIZED(dec_state->extra_channels[ec], r);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -618,8 +653,34 @@ Status ModularFrameDecoder::FinalizeDecoding(PassesDecoderState* dec_state,
|
|||
|
||||
auto& decoded = dec_state->decoded;
|
||||
|
||||
JXL_RETURN_IF_ERROR(
|
||||
ModularImageToDecodedRect(gi, dec_state, pool, output, Rect(decoded)));
|
||||
if (dec_state->render_pipeline) {
|
||||
std::atomic<bool> has_error{false};
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, dec_state->shared->frame_dim.num_groups,
|
||||
[&](size_t num_threads) {
|
||||
dec_state->render_pipeline->PrepareForThreads(num_threads);
|
||||
return true;
|
||||
},
|
||||
[&](const uint32_t group, size_t thread_id) {
|
||||
RenderPipelineInput input =
|
||||
dec_state->render_pipeline->GetInputBuffers(group, thread_id);
|
||||
if (!ModularImageToDecodedRect(gi, dec_state, nullptr, &input, output,
|
||||
Rect(),
|
||||
dec_state->shared->GroupRect(group))) {
|
||||
has_error = true;
|
||||
return;
|
||||
}
|
||||
input.Done();
|
||||
},
|
||||
"ModularToRect"));
|
||||
if (has_error) {
|
||||
return JXL_FAILURE("Error producing input to render pipeline");
|
||||
}
|
||||
} else {
|
||||
JXL_RETURN_IF_ERROR(ModularImageToDecodedRect(gi, dec_state, pool, nullptr,
|
||||
output, Rect(decoded),
|
||||
Rect(0, 0, gi.w, gi.h)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -91,8 +91,9 @@ class ModularFrameDecoder {
|
|||
bool allow_truncated_group);
|
||||
Status DecodeGroup(const Rect& rect, BitReader* reader, int minShift,
|
||||
int maxShift, const ModularStreamId& stream, bool zerofill,
|
||||
PassesDecoderState* dec_state, ImageBundle* output,
|
||||
bool allow_truncated);
|
||||
PassesDecoderState* dec_state,
|
||||
RenderPipelineInput* render_pipeline_input,
|
||||
ImageBundle* output, bool allow_truncated);
|
||||
// Decodes a VarDCT DC group (`group_id`) from the given `reader`.
|
||||
Status DecodeVarDCTDC(size_t group_id, BitReader* reader,
|
||||
PassesDecoderState* dec_state);
|
||||
|
@ -113,11 +114,14 @@ class ModularFrameDecoder {
|
|||
ImageBundle* output, bool inplace);
|
||||
bool have_dc() const { return have_something; }
|
||||
void MaybeDropFullImage();
|
||||
bool UsesFullImage() const { return use_full_image; }
|
||||
|
||||
private:
|
||||
Status ModularImageToDecodedRect(Image& gi, PassesDecoderState* dec_state,
|
||||
jxl::ThreadPool* pool, ImageBundle* output,
|
||||
Rect rect);
|
||||
jxl::ThreadPool* pool,
|
||||
RenderPipelineInput* render_pipeline_input,
|
||||
ImageBundle* output, Rect rect,
|
||||
Rect modular_rect);
|
||||
|
||||
Image full_image;
|
||||
std::vector<Transform> global_transform;
|
||||
|
|
|
@ -258,6 +258,15 @@ void RandomImage3(size_t seed, const Rect& rect, Image3F* JXL_RESTRICT noise) {
|
|||
RandomImage(&rng, rect, &noise->Plane(2));
|
||||
}
|
||||
|
||||
void Random3Planes(size_t seed, const std::pair<ImageF*, Rect>& plane0,
|
||||
const std::pair<ImageF*, Rect>& plane1,
|
||||
const std::pair<ImageF*, Rect>& plane2) {
|
||||
HWY_ALIGN Xorshift128Plus rng(seed);
|
||||
RandomImage(&rng, plane0.second, plane0.first);
|
||||
RandomImage(&rng, plane1.second, plane1.first);
|
||||
RandomImage(&rng, plane2.second, plane2.first);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jxl
|
||||
|
@ -279,6 +288,13 @@ void RandomImage3(size_t seed, const Rect& rect, Image3F* JXL_RESTRICT noise) {
|
|||
return HWY_DYNAMIC_DISPATCH(RandomImage3)(seed, rect, noise);
|
||||
}
|
||||
|
||||
HWY_EXPORT(Random3Planes);
|
||||
void Random3Planes(size_t seed, const std::pair<ImageF*, Rect>& plane0,
|
||||
const std::pair<ImageF*, Rect>& plane1,
|
||||
const std::pair<ImageF*, Rect>& plane2) {
|
||||
return HWY_DYNAMIC_DISPATCH(Random3Planes)(seed, plane0, plane1, plane2);
|
||||
}
|
||||
|
||||
void DecodeFloatParam(float precision, float* val, BitReader* br) {
|
||||
const int absval_quant = br->ReadFixedBits<10>();
|
||||
*val = absval_quant / precision;
|
||||
|
|
|
@ -28,6 +28,10 @@ void AddNoise(const NoiseParams& noise_params, const Rect& noise_rect,
|
|||
|
||||
void RandomImage3(size_t seed, const Rect& rect, Image3F* JXL_RESTRICT noise);
|
||||
|
||||
void Random3Planes(size_t seed, const std::pair<ImageF*, Rect>& plane0,
|
||||
const std::pair<ImageF*, Rect>& plane1,
|
||||
const std::pair<ImageF*, Rect>& plane2);
|
||||
|
||||
// Must only call if FrameHeader.flags.kNoise.
|
||||
Status DecodeNoise(BitReader* br, NoiseParams* noise_params);
|
||||
|
||||
|
|
|
@ -44,6 +44,10 @@ struct DecompressParams {
|
|||
bool allow_partial_files = false;
|
||||
// Allow even more progression.
|
||||
bool allow_more_progressive_steps = false;
|
||||
|
||||
// Internal test-only setting: whether or not to use the slow rendering
|
||||
// pipeline.
|
||||
bool use_slow_render_pipeline = false;
|
||||
};
|
||||
|
||||
} // namespace jxl
|
||||
|
|
|
@ -199,11 +199,50 @@ void PatchDictionary::ComputePatchCache() {
|
|||
}
|
||||
}
|
||||
|
||||
Status PatchDictionary::AddTo(Image3F* opsin, const Rect& opsin_rect,
|
||||
float* const* extra_channels,
|
||||
const Rect& image_rect) const {
|
||||
// Adds patches to a segment of `xsize` pixels, starting at `inout`, assumed
|
||||
// to be located at position (x0, y) in the frame.
|
||||
void PatchDictionary::AddOneRow(float* const* inout, size_t y, size_t x0,
|
||||
size_t xsize) const {
|
||||
if (patch_starts_.empty()) return;
|
||||
size_t num_ec = shared_->metadata->m.num_extra_channels;
|
||||
std::vector<const float*> fg_ptrs(3 + num_ec);
|
||||
if (y + 1 >= patch_starts_.size()) return;
|
||||
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 patch_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 >= x0 + xsize) continue;
|
||||
if (bx + patch_xsize < x0) continue;
|
||||
size_t patch_x0 = std::max(bx, x0);
|
||||
size_t patch_x1 = std::min(bx + patch_xsize, x0 + 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
PerformBlending(inout, fg_ptrs.data(), inout, patch_x0 - x0,
|
||||
patch_x1 - patch_x0, pos.blending[0],
|
||||
pos.blending.data() + 1,
|
||||
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 true;
|
||||
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);
|
||||
|
@ -238,13 +277,11 @@ Status PatchDictionary::AddTo(Image3F* opsin, const Rect& opsin_rect,
|
|||
pos.ref_pos.x0 + x0 - bx;
|
||||
bg_ptrs[3 + i] = extra_channels[i] + x0 - image_rect.x0();
|
||||
}
|
||||
JXL_RETURN_IF_ERROR(
|
||||
PerformBlending(bg_ptrs.data(), fg_ptrs.data(), bg_ptrs.data(),
|
||||
x1 - x0, pos.blending[0], pos.blending.data() + 1,
|
||||
shared_->metadata->m.extra_channel_info));
|
||||
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);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
|
|
|
@ -122,10 +122,14 @@ class PatchDictionary {
|
|||
ComputePatchCache();
|
||||
}
|
||||
|
||||
// Adds patches to a segment of `xsize` pixels, starting at `inout`, assumed
|
||||
// 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;
|
||||
|
||||
// Only adds patches that belong to the `image_rect` area of the decoded
|
||||
// image, writing them to the `opsin_rect` area of `opsin`.
|
||||
Status AddTo(Image3F* opsin, const Rect& opsin_rect,
|
||||
float* const* extra_channels, const Rect& image_rect) const;
|
||||
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
|
||||
// bit mask: bits 0-3 indicate reference frame 0-3.
|
||||
|
|
|
@ -59,16 +59,15 @@ void DoUndoXYBInPlace(Image3F* idct, const Rect& rect, Op op,
|
|||
const auto in_opsin_x = Load(d, row0 + x);
|
||||
const auto in_opsin_y = Load(d, row1 + x);
|
||||
const auto in_opsin_b = Load(d, row2 + x);
|
||||
JXL_COMPILER_FENCE;
|
||||
auto linear_r = Undefined(d);
|
||||
auto linear_g = Undefined(d);
|
||||
auto linear_b = Undefined(d);
|
||||
auto r = Undefined(d);
|
||||
auto g = Undefined(d);
|
||||
auto b = Undefined(d);
|
||||
XybToRgb(d, in_opsin_x, in_opsin_y, in_opsin_b,
|
||||
output_encoding_info.opsin_params, &linear_r, &linear_g,
|
||||
&linear_b);
|
||||
Store(op.Transform(d, linear_r), d, row0 + x);
|
||||
Store(op.Transform(d, linear_g), d, row1 + x);
|
||||
Store(op.Transform(d, linear_b), d, row2 + x);
|
||||
output_encoding_info.opsin_params, &r, &g, &b);
|
||||
op.Transform(d, &r, &g, &b);
|
||||
Store(r, d, row0 + x);
|
||||
Store(g, d, row1 + x);
|
||||
Store(b, d, row2 + x);
|
||||
}
|
||||
msan::PoisonMemory(row0 + xsize, sizeof(float) * (xsize_v - xsize));
|
||||
msan::PoisonMemory(row1 + xsize, sizeof(float) * (xsize_v - xsize));
|
||||
|
@ -76,6 +75,20 @@ void DoUndoXYBInPlace(Image3F* idct, const Rect& rect, Op op,
|
|||
}
|
||||
}
|
||||
|
||||
template <typename Op>
|
||||
struct PerChannelOp {
|
||||
template <typename... Args>
|
||||
explicit PerChannelOp(Args&&... args) : op(std::forward<Args>(args)...) {}
|
||||
template <typename D, typename T>
|
||||
void Transform(D d, T* r, T* g, T* b) {
|
||||
*r = op.Transform(d, *r);
|
||||
*g = op.Transform(d, *g);
|
||||
*b = op.Transform(d, *b);
|
||||
}
|
||||
|
||||
Op op;
|
||||
};
|
||||
|
||||
struct OpLinear {
|
||||
template <typename D, typename T>
|
||||
T Transform(D d, const T& linear) {
|
||||
|
@ -102,10 +115,34 @@ struct OpPq {
|
|||
};
|
||||
|
||||
struct OpHlg {
|
||||
template <typename D, typename T>
|
||||
T Transform(D d, const T& linear) {
|
||||
return TF_HLG().EncodedFromDisplay(d, linear);
|
||||
explicit OpHlg(const float luminances[3], const float intensity_target)
|
||||
: luminances(luminances) {
|
||||
if (295 <= intensity_target && intensity_target <= 305) {
|
||||
apply_inverse_ootf = false;
|
||||
return;
|
||||
}
|
||||
exponent =
|
||||
(1 / 1.2f) * std::pow(1.111f, -std::log2(intensity_target * 1e-3f)) - 1;
|
||||
}
|
||||
template <typename D, typename T>
|
||||
void Transform(D d, T* r, T* g, T* b) {
|
||||
if (apply_inverse_ootf) {
|
||||
const T luminance = Set(d, luminances[0]) * *r +
|
||||
Set(d, luminances[1]) * *g +
|
||||
Set(d, luminances[2]) * *b;
|
||||
const T ratio =
|
||||
Min(FastPowf(d, luminance, Set(d, exponent)), Set(d, 1e9));
|
||||
*r *= ratio;
|
||||
*g *= ratio;
|
||||
*b *= ratio;
|
||||
}
|
||||
*r = TF_HLG().EncodedFromDisplay(d, *r);
|
||||
*g = TF_HLG().EncodedFromDisplay(d, *g);
|
||||
*b = TF_HLG().EncodedFromDisplay(d, *b);
|
||||
}
|
||||
bool apply_inverse_ootf = true;
|
||||
const float* luminances;
|
||||
float exponent;
|
||||
};
|
||||
|
||||
struct Op709 {
|
||||
|
@ -116,6 +153,7 @@ struct Op709 {
|
|||
};
|
||||
|
||||
struct OpGamma {
|
||||
explicit OpGamma(const float inverse_gamma) : inverse_gamma(inverse_gamma) {}
|
||||
const float inverse_gamma;
|
||||
template <typename D, typename T>
|
||||
T Transform(D d, const T& linear) {
|
||||
|
@ -129,19 +167,24 @@ Status UndoXYBInPlace(Image3F* idct, const Rect& rect,
|
|||
PROFILER_ZONE("UndoXYB");
|
||||
|
||||
if (output_encoding_info.color_encoding.tf.IsLinear()) {
|
||||
DoUndoXYBInPlace(idct, rect, OpLinear(), output_encoding_info);
|
||||
DoUndoXYBInPlace(idct, rect, PerChannelOp<OpLinear>(),
|
||||
output_encoding_info);
|
||||
} else if (output_encoding_info.color_encoding.tf.IsSRGB()) {
|
||||
DoUndoXYBInPlace(idct, rect, OpRgb(), output_encoding_info);
|
||||
DoUndoXYBInPlace(idct, rect, PerChannelOp<OpRgb>(), output_encoding_info);
|
||||
} else if (output_encoding_info.color_encoding.tf.IsPQ()) {
|
||||
DoUndoXYBInPlace(idct, rect, OpPq(), output_encoding_info);
|
||||
DoUndoXYBInPlace(idct, rect, PerChannelOp<OpPq>(), output_encoding_info);
|
||||
} else if (output_encoding_info.color_encoding.tf.IsHLG()) {
|
||||
DoUndoXYBInPlace(idct, rect, OpHlg(), output_encoding_info);
|
||||
DoUndoXYBInPlace(idct, rect,
|
||||
OpHlg(output_encoding_info.luminances,
|
||||
output_encoding_info.intensity_target),
|
||||
output_encoding_info);
|
||||
} else if (output_encoding_info.color_encoding.tf.Is709()) {
|
||||
DoUndoXYBInPlace(idct, rect, Op709(), output_encoding_info);
|
||||
DoUndoXYBInPlace(idct, rect, PerChannelOp<Op709>(), output_encoding_info);
|
||||
} else if (output_encoding_info.color_encoding.tf.IsGamma() ||
|
||||
output_encoding_info.color_encoding.tf.IsDCI()) {
|
||||
OpGamma op{output_encoding_info.inverse_gamma};
|
||||
DoUndoXYBInPlace(idct, rect, op, output_encoding_info);
|
||||
DoUndoXYBInPlace(idct, rect,
|
||||
PerChannelOp<OpGamma>(output_encoding_info.inverse_gamma),
|
||||
output_encoding_info);
|
||||
} else {
|
||||
// This is a programming error.
|
||||
JXL_ABORT("Invalid target encoding");
|
||||
|
@ -418,13 +461,13 @@ HWY_EXPORT(DoYCbCrUpsampling);
|
|||
void UndoXYB(const Image3F& src, Image3F* dst,
|
||||
const OutputEncodingInfo& output_info, ThreadPool* pool) {
|
||||
CopyImageTo(src, dst);
|
||||
RunOnPool(
|
||||
pool, 0, src.ysize(), ThreadPool::SkipInit(),
|
||||
[&](int y, int /*thread*/) {
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, 0, src.ysize(), ThreadPool::NoInit,
|
||||
[&](const uint32_t y, size_t /*thread*/) {
|
||||
JXL_CHECK(HWY_DYNAMIC_DISPATCH(UndoXYBInPlace)(dst, Rect(*dst).Line(y),
|
||||
output_info));
|
||||
},
|
||||
"UndoXYB");
|
||||
"UndoXYB"));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -582,6 +625,10 @@ Status FinalizeImageRect(
|
|||
const std::vector<std::pair<ImageF*, Rect>>& extra_channels,
|
||||
PassesDecoderState* dec_state, size_t thread,
|
||||
ImageBundle* JXL_RESTRICT output_image, const Rect& frame_rect) {
|
||||
// Do nothing if using the rendering pipeline.
|
||||
if (dec_state->render_pipeline) {
|
||||
return true;
|
||||
}
|
||||
const ImageFeatures& image_features = dec_state->shared->image_features;
|
||||
const FrameHeader& frame_header = dec_state->shared->frame_header;
|
||||
const ImageMetadata& metadata = frame_header.nonserialized_metadata->m;
|
||||
|
@ -918,9 +965,9 @@ Status FinalizeImageRect(
|
|||
extra_channels_for_patches[i].first, available_y);
|
||||
}
|
||||
}
|
||||
JXL_RETURN_IF_ERROR(image_features.patches.AddTo(
|
||||
image_features.patches.AddTo(
|
||||
storage_for_if, rect_for_if_storage.Line(available_y),
|
||||
ec_ptrs_for_patches.data(), rect_for_if.Line(available_y)));
|
||||
ec_ptrs_for_patches.data(), rect_for_if.Line(available_y));
|
||||
image_features.splines.AddTo(storage_for_if,
|
||||
rect_for_if_storage.Line(available_y),
|
||||
rect_for_if.Line(available_y));
|
||||
|
@ -1107,6 +1154,9 @@ Status FinalizeImageRect(
|
|||
Status FinalizeFrameDecoding(ImageBundle* decoded,
|
||||
PassesDecoderState* dec_state, ThreadPool* pool,
|
||||
bool force_fir, bool skip_blending, bool move_ec) {
|
||||
if (dec_state->render_pipeline) {
|
||||
return true;
|
||||
}
|
||||
const FrameHeader& frame_header = dec_state->shared->frame_header;
|
||||
const FrameDimensions& frame_dim = dec_state->shared->frame_dim;
|
||||
|
||||
|
@ -1121,7 +1171,7 @@ Status FinalizeFrameDecoding(ImageBundle* decoded,
|
|||
rects_to_process.push_back(rect);
|
||||
}
|
||||
}
|
||||
const auto allocate_storage = [&](size_t num_threads) {
|
||||
const auto allocate_storage = [&](const size_t num_threads) {
|
||||
dec_state->EnsureStorage(num_threads);
|
||||
return true;
|
||||
};
|
||||
|
@ -1145,7 +1195,7 @@ Status FinalizeFrameDecoding(ImageBundle* decoded,
|
|||
}
|
||||
|
||||
std::atomic<bool> apply_features_ok{true};
|
||||
auto run_apply_features = [&](size_t rect_id, size_t thread) {
|
||||
auto run_apply_features = [&](const uint32_t rect_id, size_t thread) {
|
||||
size_t xstart = PassesDecoderState::kGroupDataXBorder;
|
||||
size_t ystart = PassesDecoderState::kGroupDataYBorder;
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
|
@ -1198,8 +1248,9 @@ Status FinalizeFrameDecoding(ImageBundle* decoded,
|
|||
}
|
||||
};
|
||||
|
||||
RunOnPool(pool, 0, rects_to_process.size(), allocate_storage,
|
||||
run_apply_features, "ApplyFeatures");
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(pool, 0, rects_to_process.size(),
|
||||
allocate_storage, run_apply_features,
|
||||
"ApplyFeatures"));
|
||||
|
||||
if (!apply_features_ok) {
|
||||
return JXL_FAILURE("FinalizeImageRect failed");
|
||||
|
@ -1254,8 +1305,8 @@ Status FinalizeFrameDecoding(ImageBundle* decoded,
|
|||
|
||||
std::atomic<bool> blending_ok{true};
|
||||
JXL_RETURN_IF_ERROR(RunOnPool(
|
||||
pool, 0, rects_to_process.size(), ThreadPool::SkipInit(),
|
||||
[&](size_t i, size_t /*thread*/) {
|
||||
pool, 0, rects_to_process.size(), ThreadPool::NoInit,
|
||||
[&](const uint32_t i, size_t /*thread*/) {
|
||||
const Rect& rect = rects_to_process[i];
|
||||
auto rect_blender = blender.PrepareRect(
|
||||
rect, *foreground.color(), foreground.extra_channels(), rect);
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JXL_DEC_RENDER_PIPELINE_H_
|
||||
#define LIB_JXL_DEC_RENDER_PIPELINE_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/jxl/filters.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// The first pixel in the input to RenderPipelineStage will be located at
|
||||
// this position. Pixels before this position may be accessed as padding.
|
||||
constexpr size_t kRenderPipelineXOffset = 16;
|
||||
|
||||
enum class RenderPipelineChannelMode {
|
||||
kIgnored = 0,
|
||||
kInPlace = 1,
|
||||
kInOut = 2,
|
||||
};
|
||||
|
||||
class RenderPipelineStage {
|
||||
public:
|
||||
// `input` points to `2*MaxPaddingY() + 1` pointers, each of which points to
|
||||
// `3+num_non_color_channels` pointer-to-row. So, `input[MaxPaddingY()][0]` is
|
||||
// the pointer to the center row of the first color channel.
|
||||
// `MaxPaddingY()` is the maximum value returned by `GetPaddingX()`;
|
||||
// typically, this is a constant.
|
||||
// `output` points to `1<<MaxShiftY()` pointers, each of which points to
|
||||
// `3+num_non_color_channels` pointer-to-row. So, `output[0][3]` is the
|
||||
// pointer to the top row of the first non-color channel.
|
||||
// `MaxShiftY()` is defined similarly to `MaxPaddingY()`.
|
||||
// `xsize` represents the total number of pixels to be processed in the input
|
||||
// row. `xpos` and `ypos` represent the position of the first pixel in the
|
||||
// center row in the input
|
||||
virtual void ProcessRow(float* JXL_RESTRICT** input,
|
||||
float* JXL_RESTRICT** output, size_t xsize,
|
||||
size_t xpos, size_t ypos) const = 0;
|
||||
virtual ~RenderPipelineStage() {}
|
||||
|
||||
// Amount of padding required by each channel in the various directions.
|
||||
// The value for c=0 indicates padding required for color channels, subsequent
|
||||
// values refer to padding for non-color channels, in order.
|
||||
virtual size_t GetPaddingX(size_t c) const = 0;
|
||||
virtual size_t GetPaddingY(size_t c) const = 0;
|
||||
|
||||
// Log2 of the number of columns/rows of output that this stage will produce
|
||||
// for the given channel.
|
||||
virtual size_t ShiftX(size_t c) const = 0;
|
||||
virtual size_t ShiftY(size_t c) const = 0;
|
||||
|
||||
// How each channel will be processed. If this method returns kIgnored or
|
||||
// kInPlace for a given channel, then the corresponding pointer-to-row values
|
||||
// in the output of ProcessRow will be null for that channel, and
|
||||
// `GetPaddingX`, `GetPaddingY`, `ShiftX` and `ShiftY` for that channel must
|
||||
// return 0.
|
||||
virtual RenderPipelineChannelMode GetChannelMode(size_t c) const = 0;
|
||||
};
|
||||
|
||||
class RenderPipeline {
|
||||
public:
|
||||
// Initial shifts for the channels (following the same convention as
|
||||
// RenderPipelineStage for naming the channels).
|
||||
void Init(const std::vector<std::pair<size_t, size_t>>& channel_shifts) {
|
||||
JXL_ABORT("Not implemented");
|
||||
}
|
||||
|
||||
// Adds a stage to the pipeline. The shifts for all the channels that are not
|
||||
// kIgnored by the stage must be identical at this point.
|
||||
void AddStage(std::unique_ptr<RenderPipelineStage> stage) {
|
||||
JXL_ABORT("Not implemented");
|
||||
}
|
||||
|
||||
// Finalizes setup of the pipeline. Shifts for all channels should be 0 at
|
||||
// this point.
|
||||
void Finalize() { JXL_ABORT("Not implemented"); }
|
||||
|
||||
// Allocates storage to run with `num` threads.
|
||||
void PrepareForThreads(size_t num) { JXL_ABORT("Not implemented"); }
|
||||
|
||||
// TBD: run the pipeline for a given input, on a given thread.
|
||||
// void Run(Image3F* color_data, ImageF* ec_data, const Rect& input_rect,
|
||||
// size_t thread, size_t xpos, size_t ypos) {}
|
||||
};
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_JXL_DEC_RENDER_PIPELINE_H_
|
|
@ -35,9 +35,9 @@ void OpsinToLinearInplace(Image3F* JXL_RESTRICT inout, ThreadPool* pool,
|
|||
JXL_CHECK_IMAGE_INITIALIZED(*inout, Rect(*inout));
|
||||
|
||||
const size_t xsize = inout->xsize(); // not padded
|
||||
RunOnPool(
|
||||
pool, 0, inout->ysize(), ThreadPool::SkipInit(),
|
||||
[&](const int task, const int thread) {
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, 0, inout->ysize(), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /* thread */) {
|
||||
const size_t y = task;
|
||||
|
||||
// Faster than adding via ByteOffset at end of loop.
|
||||
|
@ -51,7 +51,6 @@ void OpsinToLinearInplace(Image3F* JXL_RESTRICT inout, ThreadPool* pool,
|
|||
const auto in_opsin_x = Load(d, row0 + x);
|
||||
const auto in_opsin_y = Load(d, row1 + x);
|
||||
const auto in_opsin_b = Load(d, row2 + x);
|
||||
JXL_COMPILER_FENCE;
|
||||
auto linear_r = Undefined(d);
|
||||
auto linear_g = Undefined(d);
|
||||
auto linear_b = Undefined(d);
|
||||
|
@ -63,7 +62,7 @@ void OpsinToLinearInplace(Image3F* JXL_RESTRICT inout, ThreadPool* pool,
|
|||
Store(linear_b, d, row2 + x);
|
||||
}
|
||||
},
|
||||
"OpsinToLinear");
|
||||
"OpsinToLinear"));
|
||||
}
|
||||
|
||||
// Same, but not in-place.
|
||||
|
@ -75,9 +74,9 @@ void OpsinToLinear(const Image3F& opsin, const Rect& rect, ThreadPool* pool,
|
|||
JXL_ASSERT(SameSize(rect, *linear));
|
||||
JXL_CHECK_IMAGE_INITIALIZED(opsin, rect);
|
||||
|
||||
RunOnPool(
|
||||
pool, 0, static_cast<int>(rect.ysize()), ThreadPool::SkipInit(),
|
||||
[&](const int task, int /*thread*/) {
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, 0, static_cast<int>(rect.ysize()), ThreadPool::NoInit,
|
||||
[&](const uint32_t task, size_t /*thread*/) {
|
||||
const size_t y = static_cast<size_t>(task);
|
||||
|
||||
// Faster than adding via ByteOffset at end of loop.
|
||||
|
@ -94,7 +93,6 @@ void OpsinToLinear(const Image3F& opsin, const Rect& rect, ThreadPool* pool,
|
|||
const auto in_opsin_x = Load(d, row_opsin_0 + x);
|
||||
const auto in_opsin_y = Load(d, row_opsin_1 + x);
|
||||
const auto in_opsin_b = Load(d, row_opsin_2 + x);
|
||||
JXL_COMPILER_FENCE;
|
||||
auto linear_r = Undefined(d);
|
||||
auto linear_g = Undefined(d);
|
||||
auto linear_b = Undefined(d);
|
||||
|
@ -106,7 +104,7 @@ void OpsinToLinear(const Image3F& opsin, const Rect& rect, ThreadPool* pool,
|
|||
Store(linear_b, d, row_linear_2 + x);
|
||||
}
|
||||
},
|
||||
"OpsinToLinear(Rect)");
|
||||
"OpsinToLinear(Rect)"));
|
||||
JXL_CHECK_IMAGE_INITIALIZED(*linear, rect);
|
||||
}
|
||||
|
||||
|
@ -208,7 +206,7 @@ Status OutputEncodingInfo::Set(const CodecMetadata& metadata,
|
|||
const auto& im = metadata.transform_data.opsin_inverse_matrix;
|
||||
float inverse_matrix[9];
|
||||
memcpy(inverse_matrix, im.inverse_matrix, sizeof(inverse_matrix));
|
||||
float intensity_target = metadata.m.IntensityTarget();
|
||||
intensity_target = metadata.m.IntensityTarget();
|
||||
if (metadata.m.xyb_encoded) {
|
||||
const auto& orig_color_encoding = metadata.m.color_encoding;
|
||||
color_encoding = default_enc;
|
||||
|
@ -246,8 +244,8 @@ Status OutputEncodingInfo::Set(const CodecMetadata& metadata,
|
|||
srgb.GetPrimaries().g.x, srgb.GetPrimaries().g.y,
|
||||
srgb.GetPrimaries().b.x, srgb.GetPrimaries().b.y,
|
||||
srgb.GetWhitePoint().x, srgb.GetWhitePoint().y, srgb_to_xyzd50));
|
||||
float xyzd50_to_original[9];
|
||||
JXL_RETURN_IF_ERROR(PrimariesToXYZD50(
|
||||
float original_to_xyz[3][3];
|
||||
JXL_RETURN_IF_ERROR(PrimariesToXYZ(
|
||||
orig_color_encoding.GetPrimaries().r.x,
|
||||
orig_color_encoding.GetPrimaries().r.y,
|
||||
orig_color_encoding.GetPrimaries().g.x,
|
||||
|
@ -255,7 +253,15 @@ Status OutputEncodingInfo::Set(const CodecMetadata& metadata,
|
|||
orig_color_encoding.GetPrimaries().b.x,
|
||||
orig_color_encoding.GetPrimaries().b.y,
|
||||
orig_color_encoding.GetWhitePoint().x,
|
||||
orig_color_encoding.GetWhitePoint().y, xyzd50_to_original));
|
||||
orig_color_encoding.GetWhitePoint().y, &original_to_xyz[0][0]));
|
||||
memcpy(luminances, original_to_xyz[1], sizeof luminances);
|
||||
float adapt_to_d50[9];
|
||||
JXL_RETURN_IF_ERROR(AdaptToXYZD50(orig_color_encoding.GetWhitePoint().x,
|
||||
orig_color_encoding.GetWhitePoint().y,
|
||||
adapt_to_d50));
|
||||
float xyzd50_to_original[9];
|
||||
MatMul(adapt_to_d50, &original_to_xyz[0][0], 3, 3, 3,
|
||||
xyzd50_to_original);
|
||||
JXL_RETURN_IF_ERROR(Inv3x3Matrix(xyzd50_to_original));
|
||||
float srgb_to_original[9];
|
||||
MatMul(xyzd50_to_original, srgb_to_xyzd50, 3, 3, 3, srgb_to_original);
|
||||
|
|
|
@ -41,6 +41,11 @@ struct OutputEncodingInfo {
|
|||
Status Set(const CodecMetadata& metadata, const ColorEncoding& default_enc);
|
||||
bool all_default_opsin = true;
|
||||
bool color_encoding_is_original = false;
|
||||
// Luminances of color_encoding's primaries, used for the HLG inverse OOTF.
|
||||
// Default to sRGB's.
|
||||
float luminances[3] = {0.2126, 0.7152, 0.0722};
|
||||
// Also used for the HLG inverse OOTF.
|
||||
float intensity_target;
|
||||
};
|
||||
|
||||
// Converts `inout` (not padded) from opsin to linear sRGB in-place. Called from
|
||||
|
|
|
@ -20,28 +20,11 @@
|
|||
#include "lib/jxl/image_bundle.h"
|
||||
#include "lib/jxl/loop_filter.h"
|
||||
#include "lib/jxl/memory_manager_internal.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
#include "lib/jxl/toc.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// If set (by fuzzer) then some operations will fail, if those would require
|
||||
// allocating large objects. Actual memory usage might be two orders of
|
||||
// magnitude bigger.
|
||||
// TODO(eustas): this is a poor-mans replacement for memory-manager approach;
|
||||
// remove, once memory-manager actually works.
|
||||
size_t memory_limit_base_ = 0;
|
||||
size_t cpu_limit_base_ = 0;
|
||||
size_t used_cpu_base_ = 0;
|
||||
|
||||
bool CheckSizeLimit(size_t xsize, size_t ysize) {
|
||||
if (!memory_limit_base_) return true;
|
||||
if (xsize == 0 || ysize == 0) return true;
|
||||
size_t num_pixels = xsize * ysize;
|
||||
if (num_pixels / xsize != ysize) return false; // overflow
|
||||
if (num_pixels > memory_limit_base_) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks if a + b > size, taking possible integer overflow into account.
|
||||
bool OutOfBounds(size_t a, size_t b, size_t size) {
|
||||
size_t pos = a + b;
|
||||
|
@ -612,8 +595,29 @@ struct JxlDecoderStruct {
|
|||
// processed, so this check works.
|
||||
return stage != DecoderStage::kCodestreamFinished;
|
||||
}
|
||||
|
||||
// If set then some operations will fail, if those would require
|
||||
// allocating large objects. Actual memory usage might be two orders of
|
||||
// magnitude bigger.
|
||||
// TODO(eustas): remove once there is working API for memory / CPU limit.
|
||||
size_t memory_limit_base = 0;
|
||||
size_t cpu_limit_base = 0;
|
||||
size_t used_cpu_base = 0;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
bool CheckSizeLimit(JxlDecoder* dec, size_t xsize, size_t ysize) {
|
||||
if (!dec->memory_limit_base) return true;
|
||||
if (xsize == 0 || ysize == 0) return true;
|
||||
size_t num_pixels = xsize * ysize;
|
||||
if (num_pixels / xsize != ysize) return false; // overflow
|
||||
if (num_pixels > dec->memory_limit_base) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// TODO(zond): Make this depend on the data loaded into the decoder.
|
||||
JxlDecoderStatus JxlDecoderDefaultPixelFormat(const JxlDecoder* dec,
|
||||
JxlPixelFormat* format) {
|
||||
|
@ -726,6 +730,16 @@ JxlDecoder* JxlDecoderCreate(const JxlMemoryManager* memory_manager) {
|
|||
JxlDecoder* dec = new (alloc) JxlDecoder();
|
||||
dec->memory_manager = local_memory_manager;
|
||||
|
||||
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
if (!memory_manager) {
|
||||
dec->memory_limit_base = 1 << 21;
|
||||
// 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
|
||||
// or few larger ones.
|
||||
dec->cpu_limit_base = 5 * dec->memory_limit_base;
|
||||
}
|
||||
#endif
|
||||
|
||||
JxlDecoderReset(dec);
|
||||
|
||||
return dec;
|
||||
|
@ -824,6 +838,28 @@ JxlDecoderStatus JxlDecoderSetCoalescing(JxlDecoder* dec, JXL_BOOL coalescing) {
|
|||
return JXL_DEC_SUCCESS;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// helper function to get the dimensions of the current image buffer
|
||||
void GetCurrentDimensions(const JxlDecoder* dec, size_t& xsize, size_t& ysize,
|
||||
bool oriented) {
|
||||
if (dec->frame_header->nonserialized_is_preview) {
|
||||
xsize = dec->metadata.oriented_preview_xsize(dec->keep_orientation);
|
||||
ysize = dec->metadata.oriented_preview_ysize(dec->keep_orientation);
|
||||
return;
|
||||
}
|
||||
xsize = dec->metadata.oriented_xsize(dec->keep_orientation || !oriented);
|
||||
ysize = dec->metadata.oriented_ysize(dec->keep_orientation || !oriented);
|
||||
if (!dec->coalescing) {
|
||||
xsize = dec->frame_header->ToFrameDimensions().xsize;
|
||||
ysize = dec->frame_header->ToFrameDimensions().ysize;
|
||||
if (!dec->keep_orientation && oriented &&
|
||||
static_cast<int>(dec->metadata.m.GetOrientation()) > 4) {
|
||||
std::swap(xsize, ysize);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace jxl {
|
||||
namespace {
|
||||
|
||||
|
@ -900,7 +936,8 @@ JxlDecoderStatus JxlDecoderReadBasicInfo(JxlDecoder* dec, const uint8_t* in,
|
|||
dec->got_basic_info = true;
|
||||
dec->basic_info_size_hint = 0;
|
||||
|
||||
if (!CheckSizeLimit(dec->metadata.size.xsize(), dec->metadata.size.ysize())) {
|
||||
if (!CheckSizeLimit(dec, dec->metadata.size.xsize(),
|
||||
dec->metadata.size.ysize())) {
|
||||
return JXL_API_ERROR("image is too large");
|
||||
}
|
||||
|
||||
|
@ -945,7 +982,8 @@ JxlDecoderStatus JxlDecoderReadAllHeaders(JxlDecoder* dec, const uint8_t* in,
|
|||
dec->header_except_icc_bits = reader->TotalBitsConsumed();
|
||||
|
||||
if (dec->metadata.m.color_encoding.WantICC()) {
|
||||
jxl::Status status = dec->icc_reader.Init(reader.get(), memory_limit_base_);
|
||||
jxl::Status status =
|
||||
dec->icc_reader.Init(reader.get(), dec->memory_limit_base);
|
||||
// Always check AllReadsWithinBounds, not all the C++ decoder implementation
|
||||
// handles reader out of bounds correctly yet (e.g. context map). Not
|
||||
// checking AllReadsWithinBounds can cause reader->Close() to trigger an
|
||||
|
@ -992,15 +1030,9 @@ JxlDecoderStatus JxlDecoderReadAllHeaders(JxlDecoder* dec, const uint8_t* in,
|
|||
return JXL_DEC_SUCCESS;
|
||||
}
|
||||
|
||||
static size_t GetStride(const JxlDecoder* dec, const JxlPixelFormat& format,
|
||||
const jxl::ImageBundle* frame = nullptr) {
|
||||
size_t xsize = dec->metadata.xsize();
|
||||
if (!dec->keep_orientation && dec->metadata.m.orientation > 4) {
|
||||
xsize = dec->metadata.ysize();
|
||||
}
|
||||
if (frame) {
|
||||
xsize = dec->keep_orientation ? frame->xsize() : frame->oriented_xsize();
|
||||
}
|
||||
static size_t GetStride(const JxlDecoder* dec, const JxlPixelFormat& format) {
|
||||
size_t xsize, ysize;
|
||||
GetCurrentDimensions(dec, xsize, ysize, true);
|
||||
size_t stride = xsize * (BitsPerChannel(format.data_type) *
|
||||
format.num_channels / jxl::kBitsPerByte);
|
||||
if (format.align > 1) {
|
||||
|
@ -1022,7 +1054,7 @@ static JxlDecoderStatus ConvertImageInternal(
|
|||
JxlImageOutCallback out_callback, void* out_opaque) {
|
||||
// TODO(lode): handle mismatch of RGB/grayscale color profiles and pixel data
|
||||
// color/grayscale format
|
||||
const size_t stride = GetStride(dec, format, &frame);
|
||||
const size_t stride = GetStride(dec, format);
|
||||
|
||||
bool float_format = format.data_type == JXL_TYPE_FLOAT ||
|
||||
format.data_type == JXL_TYPE_FLOAT16;
|
||||
|
@ -1054,7 +1086,8 @@ static JxlDecoderStatus ConvertImageInternal(
|
|||
// Parses the FrameHeader and the total frame_size, given the initial bytes
|
||||
// of the frame up to and including the TOC.
|
||||
// TODO(lode): merge this with FrameDecoder
|
||||
JxlDecoderStatus ParseFrameHeader(jxl::FrameHeader* frame_header,
|
||||
JxlDecoderStatus ParseFrameHeader(JxlDecoder* dec,
|
||||
jxl::FrameHeader* frame_header,
|
||||
const uint8_t* in, size_t size, size_t pos,
|
||||
bool is_preview, size_t* frame_size,
|
||||
int* saved_as) {
|
||||
|
@ -1067,7 +1100,7 @@ JxlDecoderStatus ParseFrameHeader(jxl::FrameHeader* frame_header,
|
|||
frame_header->nonserialized_is_preview = is_preview;
|
||||
jxl::Status status = DecodeFrameHeader(reader.get(), frame_header);
|
||||
jxl::FrameDimensions frame_dim = frame_header->ToFrameDimensions();
|
||||
if (!CheckSizeLimit(frame_dim.xsize_upsampled_padded,
|
||||
if (!CheckSizeLimit(dec, frame_dim.xsize_upsampled_padded,
|
||||
frame_dim.ysize_upsampled_padded)) {
|
||||
return JXL_API_ERROR("frame is too large");
|
||||
}
|
||||
|
@ -1180,9 +1213,9 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
|
|||
size_t frame_size;
|
||||
size_t pos = dec->frame_start;
|
||||
dec->frame_header.reset(new FrameHeader(&dec->metadata));
|
||||
JxlDecoderStatus status = ParseFrameHeader(dec->frame_header.get(), in,
|
||||
size, pos, true, &frame_size,
|
||||
/*saved_as=*/nullptr);
|
||||
JxlDecoderStatus status = ParseFrameHeader(
|
||||
dec, dec->frame_header.get(), in, size, pos, true, &frame_size,
|
||||
/*saved_as=*/nullptr);
|
||||
if (status != JXL_DEC_SUCCESS) return status;
|
||||
if (OutOfBounds(pos, frame_size, size)) {
|
||||
return JXL_DEC_NEED_MORE_INPUT;
|
||||
|
@ -1255,7 +1288,7 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
|
|||
dec->frame_header.reset(new FrameHeader(&dec->metadata));
|
||||
int saved_as = 0;
|
||||
JxlDecoderStatus status =
|
||||
ParseFrameHeader(dec->frame_header.get(), in, size, pos,
|
||||
ParseFrameHeader(dec, dec->frame_header.get(), in, size, pos,
|
||||
/*is_preview=*/false, &dec->frame_size, &saved_as);
|
||||
if (status != JXL_DEC_SUCCESS) return status;
|
||||
|
||||
|
@ -1349,9 +1382,13 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
|
|||
}
|
||||
|
||||
dec->frame_dec.reset(new FrameDecoder(
|
||||
dec->passes_state.get(), dec->metadata, dec->thread_pool.get()));
|
||||
dec->passes_state.get(), dec->metadata, dec->thread_pool.get(),
|
||||
/*use_slow_rendering_pipeline=*/false));
|
||||
dec->frame_dec->SetRenderSpotcolors(dec->render_spotcolors);
|
||||
dec->frame_dec->SetCoalescing(dec->coalescing);
|
||||
if (dec->events_wanted & JXL_DEC_FRAME_PROGRESSION) {
|
||||
dec->frame_dec->SetPauseAtProgressive();
|
||||
}
|
||||
|
||||
// If JPEG reconstruction is wanted and possible, set the jpeg_data of
|
||||
// the ImageBundle.
|
||||
|
@ -1436,15 +1473,15 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
|
|||
}
|
||||
dec->sections->SetInput(in + pos, size - pos);
|
||||
|
||||
if (cpu_limit_base_ != 0) {
|
||||
if (dec->cpu_limit_base != 0) {
|
||||
FrameDimensions frame_dim = dec->frame_header->ToFrameDimensions();
|
||||
// No overflow, checked in ParseHeader.
|
||||
size_t num_pixels = frame_dim.xsize * frame_dim.ysize;
|
||||
if (used_cpu_base_ + num_pixels < used_cpu_base_) {
|
||||
if (dec->used_cpu_base + num_pixels < dec->used_cpu_base) {
|
||||
return JXL_API_ERROR("used too much CPU");
|
||||
}
|
||||
used_cpu_base_ += num_pixels;
|
||||
if (used_cpu_base_ > cpu_limit_base_) {
|
||||
dec->used_cpu_base += num_pixels;
|
||||
if (dec->used_cpu_base > dec->cpu_limit_base) {
|
||||
return JXL_API_ERROR("used too much CPU");
|
||||
}
|
||||
}
|
||||
|
@ -1461,8 +1498,17 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
|
|||
// TODO(lode): allow next_in to move forward if sections from the
|
||||
// beginning of the stream have been processed
|
||||
|
||||
if (status.code() == StatusCode::kNotEnoughBytes ||
|
||||
dec->sections->section_info.size() < dec->frame_dec->NumSections()) {
|
||||
bool all_sections_done = !!status && dec->frame_dec->HasDecodedAll();
|
||||
|
||||
bool got_dc_only =
|
||||
!!status && !all_sections_done && dec->frame_dec->HasDecodedDC();
|
||||
|
||||
if ((dec->events_wanted & JXL_DEC_FRAME_PROGRESSION) && got_dc_only) {
|
||||
dec->events_wanted &= ~JXL_DEC_FRAME_PROGRESSION;
|
||||
return JXL_DEC_FRAME_PROGRESSION;
|
||||
}
|
||||
|
||||
if (!all_sections_done) {
|
||||
// Not all sections have been processed yet
|
||||
return JXL_DEC_NEED_MORE_INPUT;
|
||||
}
|
||||
|
@ -1497,7 +1543,8 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec, const uint8_t* in,
|
|||
// Frame finished, restore the events_wanted with the per-frame events
|
||||
// from orig_events_wanted, in case there is a next frame.
|
||||
dec->events_wanted |=
|
||||
(dec->orig_events_wanted & (JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME));
|
||||
(dec->orig_events_wanted &
|
||||
(JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME | JXL_DEC_FRAME_PROGRESSION));
|
||||
|
||||
// If no output buffer was set, we merely return the JXL_DEC_FULL_IMAGE
|
||||
// status without outputting pixels.
|
||||
|
@ -2131,7 +2178,6 @@ JxlDecoderStatus JxlDecoderGetBasicInfo(const JxlDecoder* dec,
|
|||
|
||||
info->have_preview = meta.have_preview;
|
||||
info->have_animation = meta.have_animation;
|
||||
// TODO(janwas): intrinsic_size
|
||||
info->orientation = static_cast<JxlOrientation>(meta.orientation);
|
||||
|
||||
if (!dec->keep_orientation) {
|
||||
|
@ -2174,6 +2220,14 @@ JxlDecoderStatus JxlDecoderGetBasicInfo(const JxlDecoder* dec,
|
|||
info->animation.num_loops = dec->metadata.m.animation.num_loops;
|
||||
info->animation.have_timecodes = dec->metadata.m.animation.have_timecodes;
|
||||
}
|
||||
|
||||
if (meta.have_intrinsic_size) {
|
||||
info->intrinsic_xsize = dec->metadata.m.intrinsic_size.xsize();
|
||||
info->intrinsic_ysize = dec->metadata.m.intrinsic_size.ysize();
|
||||
} else {
|
||||
info->intrinsic_xsize = info->xsize;
|
||||
info->intrinsic_ysize = info->ysize;
|
||||
}
|
||||
}
|
||||
|
||||
return JXL_DEC_SUCCESS;
|
||||
|
@ -2349,21 +2403,6 @@ JxlDecoderStatus PrepareSizeCheck(const JxlDecoder* dec,
|
|||
return JXL_DEC_SUCCESS;
|
||||
}
|
||||
|
||||
// helper function to get the dimensions of the current image buffer
|
||||
void GetCurrentDimensions(const JxlDecoder* dec, size_t& xsize, size_t& ysize,
|
||||
bool oriented) {
|
||||
xsize = dec->metadata.oriented_xsize(dec->keep_orientation || !oriented);
|
||||
ysize = dec->metadata.oriented_ysize(dec->keep_orientation || !oriented);
|
||||
if (!dec->coalescing) {
|
||||
xsize = dec->frame_header->ToFrameDimensions().xsize;
|
||||
ysize = dec->frame_header->ToFrameDimensions().ysize;
|
||||
if (!dec->keep_orientation && oriented &&
|
||||
static_cast<int>(dec->metadata.m.GetOrientation()) > 4) {
|
||||
std::swap(xsize, ysize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
JxlDecoderStatus JxlDecoderFlushImage(JxlDecoder* dec) {
|
||||
|
@ -2419,10 +2458,11 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderPreviewOutBufferSize(
|
|||
|
||||
size_t row_size =
|
||||
jxl::DivCeil(xsize * format->num_channels * bits, jxl::kBitsPerByte);
|
||||
size_t last_row_size = row_size;
|
||||
if (format->align > 1) {
|
||||
row_size = jxl::DivCeil(row_size, format->align) * format->align;
|
||||
}
|
||||
*size = row_size * ysize;
|
||||
*size = row_size * (ysize - 1) + last_row_size;
|
||||
return JXL_DEC_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -2466,10 +2506,11 @@ JXL_EXPORT JxlDecoderStatus JxlDecoderDCOutBufferSize(
|
|||
|
||||
size_t row_size =
|
||||
jxl::DivCeil(xsize * format->num_channels * bits, jxl::kBitsPerByte);
|
||||
size_t last_row_size = row_size;
|
||||
if (format->align > 1) {
|
||||
row_size = jxl::DivCeil(row_size, format->align) * format->align;
|
||||
}
|
||||
*size = row_size * ysize;
|
||||
*size = row_size * (ysize - 1) + last_row_size;
|
||||
return JXL_DEC_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -2728,18 +2769,6 @@ JxlDecoderStatus JxlDecoderSetPreferredColorProfile(
|
|||
return JXL_DEC_SUCCESS;
|
||||
}
|
||||
|
||||
// This function is "package-private". It is only used by fuzzer to avoid
|
||||
// running cases that are too memory / CPU hungry. Limitations are applied
|
||||
// at mid-level API. In the future high-level API would also include the
|
||||
// means of limiting / throttling memory / CPU usage.
|
||||
void SetDecoderMemoryLimitBase_(size_t memory_limit_base) {
|
||||
memory_limit_base_ = memory_limit_base;
|
||||
// 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
|
||||
// or few larger ones.
|
||||
cpu_limit_base_ = 5 * memory_limit_base;
|
||||
}
|
||||
|
||||
JxlDecoderStatus JxlDecoderSetBoxBuffer(JxlDecoder* dec, uint8_t* data,
|
||||
size_t size) {
|
||||
if (dec->box_out_buffer_set) {
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -587,15 +587,15 @@ ImageF AdaptiveQuantizationMap(const float butteraugli_target,
|
|||
AdaptiveQuantizationImpl impl;
|
||||
impl.Init(xyb);
|
||||
*mask = ImageF(frame_dim.xsize_blocks, frame_dim.ysize_blocks);
|
||||
RunOnPool(
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, 0,
|
||||
DivCeil(frame_dim.xsize_blocks, kEncTileDimInBlocks) *
|
||||
DivCeil(frame_dim.ysize_blocks, kEncTileDimInBlocks),
|
||||
[&](size_t num_threads) {
|
||||
[&](const size_t num_threads) {
|
||||
impl.PrepareBuffers(num_threads);
|
||||
return true;
|
||||
},
|
||||
[&](const int tid, int thread) {
|
||||
[&](const uint32_t tid, const size_t thread) {
|
||||
size_t n_enc_tiles =
|
||||
DivCeil(frame_dim.xsize_blocks, kEncTileDimInBlocks);
|
||||
size_t tx = tid % n_enc_tiles;
|
||||
|
@ -609,7 +609,7 @@ ImageF AdaptiveQuantizationMap(const float butteraugli_target,
|
|||
Rect r(bx0, by0, bx1 - bx0, by1 - by0);
|
||||
impl.ComputeTile(butteraugli_target, scale, xyb, r, thread, mask);
|
||||
},
|
||||
"AQ DiffPrecompute");
|
||||
"AQ DiffPrecompute"));
|
||||
|
||||
return std::move(impl).aq_map;
|
||||
}
|
||||
|
@ -732,7 +732,8 @@ static const float kDcQuant = 1.12f;
|
|||
static const float kAcQuant = 0.825f;
|
||||
|
||||
void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin,
|
||||
PassesEncoderState* enc_state, ThreadPool* pool,
|
||||
PassesEncoderState* enc_state,
|
||||
const JxlCmsInterface& cms, ThreadPool* pool,
|
||||
AuxOut* aux_out) {
|
||||
const CompressParams& cparams = enc_state->cparams;
|
||||
Quantizer& quantizer = enc_state->shared.quantizer;
|
||||
|
@ -748,7 +749,7 @@ void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin,
|
|||
if (fabs(params.intensity_target - 255.0f) < 1e-3) {
|
||||
params.intensity_target = 80.0f;
|
||||
}
|
||||
JxlButteraugliComparator comparator(params);
|
||||
JxlButteraugliComparator comparator(params, cms);
|
||||
JXL_CHECK(comparator.SetReferenceImage(linear));
|
||||
bool lower_is_better =
|
||||
(comparator.GoodQualityScore() < comparator.BadQualityScore());
|
||||
|
@ -788,7 +789,7 @@ void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin,
|
|||
}
|
||||
}
|
||||
quantizer.SetQuantField(initial_quant_dc, quant_field, &raw_quant_field);
|
||||
ImageBundle linear = RoundtripImage(opsin, enc_state, pool);
|
||||
ImageBundle linear = RoundtripImage(opsin, enc_state, cms, pool);
|
||||
PROFILER_ZONE("enc Butteraugli");
|
||||
float score;
|
||||
ImageF diffmap;
|
||||
|
@ -898,7 +899,8 @@ void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin,
|
|||
|
||||
void FindBestQuantizationMaxError(const Image3F& opsin,
|
||||
PassesEncoderState* enc_state,
|
||||
ThreadPool* pool, AuxOut* aux_out) {
|
||||
const JxlCmsInterface& cms, ThreadPool* pool,
|
||||
AuxOut* aux_out) {
|
||||
// TODO(veluca): this only works if opsin is in XYB. The current encoder does
|
||||
// not have code paths that produce non-XYB opsin here.
|
||||
JXL_CHECK(enc_state->shared.frame_header.color_transform ==
|
||||
|
@ -923,7 +925,7 @@ void FindBestQuantizationMaxError(const Image3F& opsin,
|
|||
if (aux_out) {
|
||||
aux_out->DumpXybImage(("ops" + ToString(i)).c_str(), opsin);
|
||||
}
|
||||
ImageBundle decoded = RoundtripImage(opsin, enc_state, pool);
|
||||
ImageBundle decoded = RoundtripImage(opsin, enc_state, cms, pool);
|
||||
if (aux_out) {
|
||||
aux_out->DumpXybImage(("dec" + ToString(i)).c_str(), *decoded.color());
|
||||
}
|
||||
|
@ -1024,21 +1026,22 @@ ImageF InitialQuantField(const float butteraugli_target, const Image3F& opsin,
|
|||
}
|
||||
|
||||
void FindBestQuantizer(const ImageBundle* linear, const Image3F& opsin,
|
||||
PassesEncoderState* enc_state, ThreadPool* pool,
|
||||
PassesEncoderState* enc_state,
|
||||
const JxlCmsInterface& cms, ThreadPool* pool,
|
||||
AuxOut* aux_out, double rescale) {
|
||||
const CompressParams& cparams = enc_state->cparams;
|
||||
if (cparams.max_error_mode) {
|
||||
PROFILER_ZONE("enc find best maxerr");
|
||||
FindBestQuantizationMaxError(opsin, enc_state, pool, aux_out);
|
||||
FindBestQuantizationMaxError(opsin, enc_state, cms, pool, aux_out);
|
||||
} else if (cparams.speed_tier <= SpeedTier::kKitten) {
|
||||
// Normal encoding to a butteraugli score.
|
||||
PROFILER_ZONE("enc find best2");
|
||||
FindBestQuantization(*linear, opsin, enc_state, pool, aux_out);
|
||||
FindBestQuantization(*linear, opsin, enc_state, cms, pool, aux_out);
|
||||
}
|
||||
}
|
||||
|
||||
ImageBundle RoundtripImage(const Image3F& opsin, PassesEncoderState* enc_state,
|
||||
ThreadPool* pool) {
|
||||
const JxlCmsInterface& cms, ThreadPool* pool) {
|
||||
PROFILER_ZONE("enc roundtrip");
|
||||
std::unique_ptr<PassesDecoderState> dec_state =
|
||||
jxl::make_unique<PassesDecoderState>();
|
||||
|
@ -1058,10 +1061,10 @@ ImageBundle RoundtripImage(const Image3F& opsin, PassesEncoderState* enc_state,
|
|||
std::unique_ptr<ModularFrameEncoder> modular_frame_encoder =
|
||||
jxl::make_unique<ModularFrameEncoder>(enc_state->shared.frame_header,
|
||||
enc_state->cparams);
|
||||
InitializePassesEncoder(opsin, pool, enc_state, modular_frame_encoder.get(),
|
||||
nullptr);
|
||||
JXL_CHECK(InitializePassesEncoder(opsin, cms, pool, enc_state,
|
||||
modular_frame_encoder.get(), nullptr));
|
||||
JXL_CHECK(dec_state->Init());
|
||||
dec_state->InitForAC(pool);
|
||||
JXL_CHECK(dec_state->InitForAC(pool));
|
||||
|
||||
ImageBundle decoded(&enc_state->shared.metadata->m);
|
||||
decoded.origin = enc_state->shared.frame_header.frame_origin;
|
||||
|
@ -1088,12 +1091,13 @@ ImageBundle RoundtripImage(const Image3F& opsin, PassesEncoderState* enc_state,
|
|||
}
|
||||
|
||||
hwy::AlignedUniquePtr<GroupDecCache[]> group_dec_caches;
|
||||
const auto allocate_storage = [&](size_t num_threads) {
|
||||
const auto allocate_storage = [&](const size_t num_threads) {
|
||||
dec_state->EnsureStorage(num_threads);
|
||||
group_dec_caches = hwy::MakeUniqueAlignedArray<GroupDecCache>(num_threads);
|
||||
return true;
|
||||
};
|
||||
const auto process_group = [&](const int group_index, const int thread) {
|
||||
const auto process_group = [&](const uint32_t group_index,
|
||||
const size_t thread) {
|
||||
if (dec_state->shared->frame_header.loop_filter.epf_iters > 0) {
|
||||
ComputeSigma(dec_state->shared->BlockGroupRect(group_index),
|
||||
dec_state.get());
|
||||
|
@ -1102,7 +1106,8 @@ ImageBundle RoundtripImage(const Image3F& opsin, PassesEncoderState* enc_state,
|
|||
enc_state->coeffs, group_index, dec_state.get(),
|
||||
&group_dec_caches[thread], thread, &decoded, nullptr));
|
||||
};
|
||||
RunOnPool(pool, 0, num_groups, allocate_storage, process_group, "AQ loop");
|
||||
JXL_CHECK(RunOnPool(pool, 0, num_groups, allocate_storage, process_group,
|
||||
"AQ loop"));
|
||||
|
||||
// Fine to do a JXL_ASSERT instead of error handling, since this only happens
|
||||
// on the encoder side where we can't be fed with invalid data.
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче