зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1834208 - Update libjxl to 91760e31a40fb811867fbe9bda6ddf73c962389e r=saschanaz
Differential Revision: https://phabricator.services.mozilla.com/D178591
This commit is contained in:
Родитель
4967f36274
Коммит
2d81f310e8
|
@ -17,6 +17,7 @@ SOURCES += [
|
|||
"/third_party/jpeg-xl/lib/jxl/base/cache_aligned.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/base/data_parallel.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/base/padded_bytes.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/base/profiler.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/base/random.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/blending.cc",
|
||||
"/third_party/jpeg-xl/lib/jxl/chroma_from_luma.cc",
|
||||
|
|
|
@ -10,9 +10,9 @@ origin:
|
|||
|
||||
url: https://github.com/libjxl/libjxl
|
||||
|
||||
release: 73cb36f7129e1ce192992792778b146ed9b655a5 (2023-02-23T15:08:08Z).
|
||||
release: 91760e31a40fb811867fbe9bda6ddf73c962389e (2023-05-16T11:48:39Z).
|
||||
|
||||
revision: 73cb36f7129e1ce192992792778b146ed9b655a5
|
||||
revision: 91760e31a40fb811867fbe9bda6ddf73c962389e
|
||||
|
||||
license: Apache-2.0
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ gi-man
|
|||
Heiko Becker <heirecka@exherbo.org>
|
||||
Jim Robinson <jimbo2150@gmail.com>
|
||||
Jon Sneyers <jon@cloudinary.com>
|
||||
Joshua Root <jmr@macports.org>
|
||||
Kai Hollberg <Schweinepriester@users.noreply.github.com>
|
||||
Kleis Auke Wolthuizen <github@kleisauke.nl>
|
||||
L. E. Segovia
|
||||
|
@ -55,6 +56,7 @@ Pieter Wuille
|
|||
roland-rollo
|
||||
Samuel Leong <wvvwvvvvwvvw@gmail.com>
|
||||
Sandro <sandro.jaeckel@gmail.com>
|
||||
Sergey Fedorov <vital.had@gmail.com>
|
||||
Stephan T. Lavavej <stl@nuwen.net>
|
||||
Thomas Bonfort <thomas.bonfort@airbus.com>
|
||||
tmkk <tmkkmac@gmail.com>
|
||||
|
@ -62,3 +64,4 @@ Vincent Torri <vincent.torri@gmail.com>
|
|||
xiota
|
||||
Yonatan Nebenzhal <yonatan.nebenzhl@gmail.com>
|
||||
Ziemowit Zabawa <ziemek.zabawa@outlook.com>
|
||||
源文雨 <41315874+fumiama@users.noreply.github.com>
|
||||
|
|
|
@ -104,6 +104,10 @@ set(JPEGXL_ENABLE_JPEGLI true CACHE BOOL
|
|||
"Build jpegli library.")
|
||||
set(JPEGXL_ENABLE_JPEGLI_LIBJPEG true CACHE BOOL
|
||||
"Build libjpeg.so shared library based on jpegli.")
|
||||
set(JPEGLI_LIBJPEG_LIBRARY_VERSION "62.3.0" CACHE STRING
|
||||
"Library version of the libjpeg.so shared library that we build.")
|
||||
set(JPEGLI_LIBJPEG_LIBRARY_SOVERSION "62" CACHE STRING
|
||||
"Library so-version of the libjpeg.so shared library that we build.")
|
||||
set(JPEGXL_ENABLE_DOXYGEN true CACHE BOOL
|
||||
"Generate C API documentation using Doxygen.")
|
||||
set(JPEGXL_ENABLE_MANPAGES true CACHE BOOL
|
||||
|
@ -152,7 +156,8 @@ set(JPEGXL_DEP_LICENSE_DIR "" CACHE STRING
|
|||
"Directory where to search for system dependencies \"copyright\" files.")
|
||||
set(JPEGXL_FORCE_NEON false CACHE BOOL
|
||||
"Set flags to enable NEON in arm if not enabled by your toolchain.")
|
||||
|
||||
set(JPEGXL_TEST_TOOLS false CACHE BOOL
|
||||
"Run scripts that test the encoding / decoding tools.")
|
||||
|
||||
# Force system dependencies.
|
||||
set(JPEGXL_FORCE_SYSTEM_BROTLI false CACHE BOOL
|
||||
|
@ -265,73 +270,82 @@ if (CXX_NO_RTTI_SUPPORTED)
|
|||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
|
||||
endif()
|
||||
|
||||
# Internal flags for coverage builds:
|
||||
set(JPEGXL_COVERAGE_FLAGS)
|
||||
set(JPEGXL_COVERAGE_LINK_FLAGS)
|
||||
|
||||
if (MSVC)
|
||||
# TODO(janwas): add flags
|
||||
else ()
|
||||
|
||||
# Global compiler flags for all targets here and in subdirectories.
|
||||
add_definitions(
|
||||
# Avoid changing the binary based on the current time and date.
|
||||
-D__DATE__="redacted"
|
||||
-D__TIMESTAMP__="redacted"
|
||||
-D__TIME__="redacted"
|
||||
)
|
||||
|
||||
# Avoid log spam from fopen etc.
|
||||
if(MSVC)
|
||||
# TODO(janwas): add flags
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
else ()
|
||||
# Global compiler flags for all targets here and in subdirectories.
|
||||
add_definitions(
|
||||
# Avoid changing the binary based on the current time and date.
|
||||
-D__DATE__="redacted"
|
||||
-D__TIMESTAMP__="redacted"
|
||||
-D__TIME__="redacted"
|
||||
)
|
||||
|
||||
# TODO(eustas): JXL currently compiles, but does not pass tests...
|
||||
if (NOT JXL_HWY_DISABLED_TARGETS_FORCED AND NOT JPEGXL_ENABLE_SIZELESS_VECTORS)
|
||||
add_definitions(-DHWY_DISABLED_TARGETS=\(HWY_SVE|HWY_SVE2|HWY_SVE_256|HWY_SVE2_128|HWY_RVV\))
|
||||
message("Warning: HWY_SVE, HWY_SVE2, HWY_SVE_256, HWY_SVE2_128 and HWY_RVV CPU targets are disabled")
|
||||
endif()
|
||||
# TODO(eustas): JXL currently compiles, but does not pass tests...
|
||||
if (NOT JXL_HWY_DISABLED_TARGETS_FORCED AND NOT JPEGXL_ENABLE_SIZELESS_VECTORS)
|
||||
add_definitions(-DHWY_DISABLED_TARGETS=\(HWY_SVE|HWY_SVE2|HWY_SVE_256|HWY_SVE2_128|HWY_RVV\))
|
||||
message("Warning: HWY_SVE, HWY_SVE2, HWY_SVE_256, HWY_SVE2_128 and HWY_RVV CPU targets are disabled")
|
||||
endif()
|
||||
|
||||
# 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
|
||||
# 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
|
||||
|
||||
# Machine flags.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -funwind-tables")
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -mrelax-all")
|
||||
endif()
|
||||
if (CXX_CONSTRUCTOR_ALIASES_SUPPORTED)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -mconstructor-aliases")
|
||||
endif()
|
||||
# Machine flags.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -funwind-tables")
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -mrelax-all")
|
||||
endif()
|
||||
if (CXX_CONSTRUCTOR_ALIASES_SUPPORTED)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -mconstructor-aliases")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# Not supported by clang-cl, but frame pointers are default on Windows
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer")
|
||||
endif()
|
||||
if(WIN32)
|
||||
# Not supported by clang-cl, but frame pointers are default on Windows
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer")
|
||||
endif()
|
||||
|
||||
# CPU flags - remove once we have NEON dynamic dispatch
|
||||
# CPU flags - remove once we have NEON dynamic dispatch
|
||||
|
||||
# TODO(janwas): this also matches M1, but only ARMv7 is intended/needed.
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm")
|
||||
if(JPEGXL_FORCE_NEON)
|
||||
# GCC requires these flags, otherwise __ARM_NEON is undefined.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||
-mfpu=neon-vfpv4 -mfloat-abi=hard")
|
||||
endif()
|
||||
endif()
|
||||
# TODO(janwas): this also matches M1, but only ARMv7 is intended/needed.
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm")
|
||||
if(JPEGXL_FORCE_NEON)
|
||||
# GCC requires these flags, otherwise __ARM_NEON is undefined.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||
-mfpu=neon-vfpv4 -mfloat-abi=hard")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Force build with optimizations in release mode.
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
|
||||
# Force build with optimizations in release mode.
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
|
||||
|
||||
add_compile_options(
|
||||
# Ignore this to allow redefining __DATE__ and others.
|
||||
-Wno-builtin-macro-redefined
|
||||
add_compile_options(
|
||||
# Ignore this to allow redefining __DATE__ and others.
|
||||
-Wno-builtin-macro-redefined
|
||||
|
||||
# Global warning settings.
|
||||
-Wall
|
||||
)
|
||||
# Global warning settings.
|
||||
-Wall
|
||||
)
|
||||
|
||||
if (JPEGXL_WARNINGS_AS_ERRORS)
|
||||
add_compile_options(-Werror)
|
||||
endif ()
|
||||
if (JPEGXL_WARNINGS_AS_ERRORS)
|
||||
add_compile_options(-Werror)
|
||||
endif ()
|
||||
|
||||
if(JPEGXL_ENABLE_COVERAGE)
|
||||
set(JPEGXL_COVERAGE_FLAGS
|
||||
-g -O0 -fprofile-arcs -ftest-coverage
|
||||
-DJXL_ENABLE_ASSERT=0 -DJXL_ENABLE_CHECK=0
|
||||
)
|
||||
set(JPEGXL_COVERAGE_LINK_FLAGS
|
||||
--coverage
|
||||
)
|
||||
endif() # JPEGXL_ENABLE_COVERAGE
|
||||
endif () # !MSVC
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
@ -356,124 +370,124 @@ endif()
|
|||
add_subdirectory(lib)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
# Script to run tests over the source code in bash.
|
||||
find_program (BASH_PROGRAM bash)
|
||||
if(BASH_PROGRAM)
|
||||
add_test(
|
||||
NAME bash_test
|
||||
COMMAND ${BASH_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/bash_test.sh)
|
||||
endif()
|
||||
# Script to run tests over the source code in bash.
|
||||
find_program (BASH_PROGRAM bash)
|
||||
if(BASH_PROGRAM)
|
||||
add_test(
|
||||
NAME bash_test
|
||||
COMMAND ${BASH_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/bash_test.sh)
|
||||
endif()
|
||||
endif() # BUILD_TESTING
|
||||
|
||||
# Documentation generated by Doxygen
|
||||
if(JPEGXL_ENABLE_DOXYGEN)
|
||||
find_package(Doxygen)
|
||||
if(DOXYGEN_FOUND)
|
||||
set(DOXYGEN_GENERATE_HTML "YES")
|
||||
set(DOXYGEN_GENERATE_XML "YES")
|
||||
set(DOXYGEN_STRIP_FROM_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lib/include")
|
||||
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "README.md")
|
||||
if(JPEGXL_WARNINGS_AS_ERRORS)
|
||||
set(DOXYGEN_WARN_AS_ERROR "YES")
|
||||
endif()
|
||||
set(DOXYGEN_QUIET "YES")
|
||||
doxygen_add_docs(doc
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/doc/api.txt"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
COMMENT "Generating C API documentation")
|
||||
find_package(Doxygen)
|
||||
if(DOXYGEN_FOUND)
|
||||
set(DOXYGEN_GENERATE_HTML "YES")
|
||||
set(DOXYGEN_GENERATE_XML "YES")
|
||||
set(DOXYGEN_STRIP_FROM_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lib/include")
|
||||
set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "README.md")
|
||||
if(JPEGXL_WARNINGS_AS_ERRORS)
|
||||
set(DOXYGEN_WARN_AS_ERROR "YES")
|
||||
endif()
|
||||
set(DOXYGEN_QUIET "YES")
|
||||
doxygen_add_docs(doc
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/doc/api.txt"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
COMMENT "Generating C API documentation")
|
||||
|
||||
# Add sphinx doc build step for readthedocs.io (requires doxygen too).
|
||||
find_program(SPHINX_BUILD_PROGRAM sphinx-build)
|
||||
if(SPHINX_BUILD_PROGRAM)
|
||||
add_custom_command(
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rtd/nonexistent"
|
||||
COMMENT "Generating readthedocs.io output on ${CMAKE_CURRENT_BINARY_DIR}/rtd"
|
||||
COMMAND ${SPHINX_BUILD_PROGRAM} -q -W -b html -j auto
|
||||
${CMAKE_SOURCE_DIR}/doc/sphinx
|
||||
${CMAKE_CURRENT_BINARY_DIR}/rtd
|
||||
DEPENDS doc
|
||||
)
|
||||
# This command runs the documentation generation every time since the output
|
||||
# target file doesn't exist.
|
||||
add_custom_target(rtd-html
|
||||
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/rtd/nonexistent
|
||||
)
|
||||
else() # SPHINX_BUILD_PROGRAM\
|
||||
message(WARNING "sphinx-build not found, skipping rtd documentation")
|
||||
endif() # SPHINX_BUILD_PROGRAM
|
||||
# Add sphinx doc build step for readthedocs.io (requires doxygen too).
|
||||
find_program(SPHINX_BUILD_PROGRAM sphinx-build)
|
||||
if(SPHINX_BUILD_PROGRAM)
|
||||
add_custom_command(
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rtd/nonexistent"
|
||||
COMMENT "Generating readthedocs.io output on ${CMAKE_CURRENT_BINARY_DIR}/rtd"
|
||||
COMMAND ${SPHINX_BUILD_PROGRAM} -q -W -b html -j auto
|
||||
${CMAKE_SOURCE_DIR}/doc/sphinx
|
||||
${CMAKE_CURRENT_BINARY_DIR}/rtd
|
||||
DEPENDS doc
|
||||
)
|
||||
# This command runs the documentation generation every time since the output
|
||||
# target file doesn't exist.
|
||||
add_custom_target(rtd-html
|
||||
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/rtd/nonexistent
|
||||
)
|
||||
else() # SPHINX_BUILD_PROGRAM\
|
||||
message(WARNING "sphinx-build not found, skipping rtd documentation")
|
||||
endif() # SPHINX_BUILD_PROGRAM
|
||||
|
||||
else()
|
||||
# Create a "doc" target for compatibility since "doc" is not otherwise added to
|
||||
# the build when doxygen is not installed.
|
||||
add_custom_target(doc false
|
||||
COMMENT "Error: Can't generate doc since Doxygen not installed.")
|
||||
endif() # DOXYGEN_FOUND
|
||||
else()
|
||||
# Create a "doc" target for compatibility since "doc" is not otherwise added to
|
||||
# the build when doxygen is not installed.
|
||||
add_custom_target(doc false
|
||||
COMMENT "Error: Can't generate doc since Doxygen not installed.")
|
||||
endif() # DOXYGEN_FOUND
|
||||
endif() # JPEGXL_ENABLE_DOXYGEN
|
||||
|
||||
if(JPEGXL_ENABLE_MANPAGES)
|
||||
find_program(ASCIIDOC a2x)
|
||||
if(ASCIIDOC)
|
||||
file(STRINGS "${ASCIIDOC}" ASCIIDOC_SHEBANG LIMIT_COUNT 1)
|
||||
if(ASCIIDOC_SHEBANG MATCHES "/sh|/bash" OR MINGW)
|
||||
set(ASCIIDOC_PY_FOUND ON)
|
||||
# Run the program directly and set ASCIIDOC as empty.
|
||||
set(ASCIIDOC_PY "${ASCIIDOC}")
|
||||
set(ASCIIDOC "")
|
||||
elseif(ASCIIDOC_SHEBANG MATCHES "python2")
|
||||
find_package(Python2 COMPONENTS Interpreter)
|
||||
set(ASCIIDOC_PY_FOUND "${Python2_Interpreter_FOUND}")
|
||||
set(ASCIIDOC_PY Python2::Interpreter)
|
||||
elseif(ASCIIDOC_SHEBANG MATCHES "python3")
|
||||
find_package(Python3 COMPONENTS Interpreter)
|
||||
set(ASCIIDOC_PY_FOUND "${Python3_Interpreter_FOUND}")
|
||||
set(ASCIIDOC_PY Python3::Interpreter)
|
||||
else()
|
||||
find_package(Python COMPONENTS Interpreter QUIET)
|
||||
if(NOT Python_Interpreter_FOUND)
|
||||
find_program(ASCIIDOC_PY python)
|
||||
if(ASCIIDOC_PY)
|
||||
find_program(ASCIIDOC a2x)
|
||||
if(ASCIIDOC)
|
||||
file(STRINGS "${ASCIIDOC}" ASCIIDOC_SHEBANG LIMIT_COUNT 1)
|
||||
if(ASCIIDOC_SHEBANG MATCHES "/sh|/bash" OR MINGW)
|
||||
set(ASCIIDOC_PY_FOUND ON)
|
||||
# Run the program directly and set ASCIIDOC as empty.
|
||||
set(ASCIIDOC_PY "${ASCIIDOC}")
|
||||
set(ASCIIDOC "")
|
||||
elseif(ASCIIDOC_SHEBANG MATCHES "python2")
|
||||
find_package(Python2 COMPONENTS Interpreter)
|
||||
set(ASCIIDOC_PY_FOUND "${Python2_Interpreter_FOUND}")
|
||||
set(ASCIIDOC_PY Python2::Interpreter)
|
||||
elseif(ASCIIDOC_SHEBANG MATCHES "python3")
|
||||
find_package(Python3 COMPONENTS Interpreter)
|
||||
set(ASCIIDOC_PY_FOUND "${Python3_Interpreter_FOUND}")
|
||||
set(ASCIIDOC_PY Python3::Interpreter)
|
||||
else()
|
||||
find_package(Python COMPONENTS Interpreter QUIET)
|
||||
if(NOT Python_Interpreter_FOUND)
|
||||
find_program(ASCIIDOC_PY python)
|
||||
if(ASCIIDOC_PY)
|
||||
set(ASCIIDOC_PY_FOUND ON)
|
||||
endif()
|
||||
else()
|
||||
set(ASCIIDOC_PY_FOUND "${Python_Interpreter_FOUND}")
|
||||
set(ASCIIDOC_PY Python::Interpreter)
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
set(ASCIIDOC_PY_FOUND "${Python_Interpreter_FOUND}")
|
||||
set(ASCIIDOC_PY Python::Interpreter)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (ASCIIDOC_PY_FOUND)
|
||||
set(MANPAGE_FILES "")
|
||||
set(MANPAGES "")
|
||||
foreach(PAGE IN ITEMS cjxl djxl)
|
||||
# Invoking the Python interpreter ourselves instead of running the a2x binary
|
||||
# directly is necessary on MSYS2, otherwise it is run through cmd.exe which
|
||||
# does not recognize it.
|
||||
add_custom_command(
|
||||
OUTPUT "${PAGE}.1"
|
||||
COMMAND "${ASCIIDOC_PY}"
|
||||
ARGS ${ASCIIDOC}
|
||||
--format manpage --destination-dir="${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt"
|
||||
MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt")
|
||||
list(APPEND MANPAGE_FILES "${CMAKE_CURRENT_BINARY_DIR}/${PAGE}.1")
|
||||
list(APPEND MANPAGES "${PAGE}.1")
|
||||
endforeach()
|
||||
add_custom_target(manpages ALL DEPENDS ${MANPAGES})
|
||||
install(FILES ${MANPAGE_FILES} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
|
||||
endif() # ASCIIDOC_PY_FOUND
|
||||
else()
|
||||
message(WARNING "asciidoc was not found, the man pages will not be installed.")
|
||||
endif() # ASCIIDOC
|
||||
if (ASCIIDOC_PY_FOUND)
|
||||
set(MANPAGE_FILES "")
|
||||
set(MANPAGES "")
|
||||
foreach(PAGE IN ITEMS cjxl djxl)
|
||||
# Invoking the Python interpreter ourselves instead of running the a2x binary
|
||||
# directly is necessary on MSYS2, otherwise it is run through cmd.exe which
|
||||
# does not recognize it.
|
||||
add_custom_command(
|
||||
OUTPUT "${PAGE}.1"
|
||||
COMMAND "${ASCIIDOC_PY}"
|
||||
ARGS ${ASCIIDOC}
|
||||
--format manpage --destination-dir="${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt"
|
||||
MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt")
|
||||
list(APPEND MANPAGE_FILES "${CMAKE_CURRENT_BINARY_DIR}/${PAGE}.1")
|
||||
list(APPEND MANPAGES "${PAGE}.1")
|
||||
endforeach()
|
||||
add_custom_target(manpages ALL DEPENDS ${MANPAGES})
|
||||
install(FILES ${MANPAGE_FILES} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
|
||||
endif() # ASCIIDOC_PY_FOUND
|
||||
else()
|
||||
message(WARNING "asciidoc was not found, the man pages will not be installed.")
|
||||
endif() # ASCIIDOC
|
||||
endif() # JPEGXL_ENABLE_MANPAGES
|
||||
|
||||
# Example usage code.
|
||||
if (JPEGXL_ENABLE_EXAMPLES)
|
||||
include(examples/examples.cmake)
|
||||
include(examples/examples.cmake)
|
||||
endif ()
|
||||
|
||||
# Plugins for third-party software
|
||||
if (JPEGXL_ENABLE_PLUGINS)
|
||||
add_subdirectory(plugins)
|
||||
add_subdirectory(plugins)
|
||||
endif ()
|
||||
|
||||
# Binary tools
|
||||
|
|
|
@ -40,8 +40,9 @@ On MacOS, you can use [Homebrew](https://brew.sh/): `brew install jpeg-xl`.
|
|||
|
||||
[![libjxl packaging status](https://repology.org/badge/vertical-allrepos/libjxl.svg?exclude_unsupported=1&columns=3&exclude_sources=modules,site&header=libjxl%20packaging%20status)](https://repology.org/project/libjxl/versions)
|
||||
|
||||
|
||||
For Windows, binaries can be downloaded from the [releases page](https://github.com/libjxl/libjxl/releases/).
|
||||
From the [releases page](https://github.com/libjxl/libjxl/releases/) the following can be downloaded:
|
||||
- Windows binaries
|
||||
- Debian and Ubuntu .deb packages
|
||||
|
||||
Of course you can also [build libjxl from sources](BUILDING.md).
|
||||
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository")
|
||||
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository", "new_git_repository")
|
||||
|
||||
http_archive(
|
||||
name = "bazel_skylib",
|
||||
sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506",
|
||||
urls = [
|
||||
"https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
|
||||
"https://github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
|
||||
],
|
||||
sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506",
|
||||
)
|
||||
|
||||
load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
|
||||
|
||||
bazel_skylib_workspace()
|
||||
|
||||
local_repository(
|
||||
|
@ -24,13 +26,12 @@ local_repository(
|
|||
|
||||
new_local_repository(
|
||||
name = "googletest",
|
||||
path = "third_party/googletest",
|
||||
build_file = "third_party/googletest/BUILD.bazel",
|
||||
path = "third_party/googletest",
|
||||
)
|
||||
|
||||
new_local_repository(
|
||||
name = "skcms",
|
||||
path = "third_party/skcms",
|
||||
build_file_content = """
|
||||
cc_library(
|
||||
name = "skcms",
|
||||
|
@ -43,13 +44,11 @@ cc_library(
|
|||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
path = "third_party/skcms",
|
||||
)
|
||||
|
||||
# TODO(eustas): zconf.h might be deleted after CMake build; consider fetching
|
||||
# pristine sources with hash taken from `deps.sh`.
|
||||
new_local_repository(
|
||||
new_git_repository(
|
||||
name = "zlib",
|
||||
path = "third_party/zlib",
|
||||
build_file_content = """
|
||||
cc_library(
|
||||
name = "zlib",
|
||||
|
@ -86,11 +85,12 @@ cc_library(
|
|||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
remote = "https://github.com/madler/zlib",
|
||||
tag = "v1.2.13",
|
||||
)
|
||||
|
||||
new_local_repository(
|
||||
name = "png",
|
||||
path = "third_party/libpng",
|
||||
build_file_content = """
|
||||
genrule(
|
||||
name = "pnglibconf",
|
||||
|
@ -130,12 +130,11 @@ cc_library(
|
|||
deps = ["@zlib//:zlib"],
|
||||
)
|
||||
""",
|
||||
path = "third_party/libpng",
|
||||
)
|
||||
|
||||
new_git_repository(
|
||||
name = "libjpeg_turbo",
|
||||
remote = "https://github.com/libjpeg-turbo/libjpeg-turbo.git",
|
||||
tag = "2.1.4",
|
||||
build_file_content = """
|
||||
load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
|
||||
SUBSTITUTIONS = {
|
||||
|
@ -265,14 +264,13 @@ cc_library(
|
|||
includes = ["."],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
"""
|
||||
""",
|
||||
remote = "https://github.com/libjpeg-turbo/libjpeg-turbo.git",
|
||||
tag = "2.1.4",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "gif",
|
||||
url = "https://netcologne.dl.sourceforge.net/project/giflib/giflib-5.2.1.tar.gz",
|
||||
sha256 = "31da5562f44c5f15d63340a09a4fd62b48c45620cd302f77a6d9acf0077879bd",
|
||||
strip_prefix = "giflib-5.2.1",
|
||||
build_file_content = """
|
||||
cc_library(
|
||||
name = "gif",
|
||||
|
@ -285,5 +283,460 @@ cc_library(
|
|||
includes = ["."],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
"""
|
||||
""",
|
||||
sha256 = "31da5562f44c5f15d63340a09a4fd62b48c45620cd302f77a6d9acf0077879bd",
|
||||
strip_prefix = "giflib-5.2.1",
|
||||
url = "https://netcologne.dl.sourceforge.net/project/giflib/giflib-5.2.1.tar.gz",
|
||||
)
|
||||
|
||||
new_git_repository(
|
||||
name = "imath",
|
||||
build_file_content = """
|
||||
load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
|
||||
SUBSTITUTIONS = {
|
||||
"@IMATH_INTERNAL_NAMESPACE@": "Imath_3_1",
|
||||
"@IMATH_LIB_VERSION@": "3.1.4",
|
||||
"@IMATH_NAMESPACE_CUSTOM@": "0",
|
||||
"@IMATH_NAMESPACE@": "Imath",
|
||||
"@IMATH_PACKAGE_NAME@": "Imath 3.1.4",
|
||||
"@IMATH_VERSION_MAJOR@": "3",
|
||||
"@IMATH_VERSION_MINOR@": "1",
|
||||
"@IMATH_VERSION_PATCH@": "4",
|
||||
"@IMATH_VERSION@": "3.1.4",
|
||||
}
|
||||
YES_DEFINES = [
|
||||
"IMATH_HALF_USE_LOOKUP_TABLE", "IMATH_ENABLE_API_VISIBILITY",
|
||||
]
|
||||
NO_DEFINES = [
|
||||
"IMATH_HAVE_LARGE_STACK",
|
||||
]
|
||||
ONE_DEFINES = [
|
||||
"IMATH_USE_NOEXCEPT",
|
||||
]
|
||||
SUBSTITUTIONS.update({
|
||||
"#cmakedefine " + key : "#define " + key for key in YES_DEFINES
|
||||
})
|
||||
SUBSTITUTIONS.update({
|
||||
"#cmakedefine " + key : "// #define " + key for key in NO_DEFINES
|
||||
})
|
||||
SUBSTITUTIONS.update({
|
||||
"#cmakedefine01 " + key : "#define " + key + " 1" for key in ONE_DEFINES
|
||||
})
|
||||
expand_template(
|
||||
name = "expand_ImathConfig",
|
||||
template = "config/ImathConfig.h.in",
|
||||
out = "src/Imath/ImathConfig.h",
|
||||
substitutions = SUBSTITUTIONS,
|
||||
)
|
||||
cc_library(
|
||||
name = "Imath",
|
||||
srcs = [
|
||||
"src/Imath/ImathColorAlgo.cpp",
|
||||
":src/Imath/ImathConfig.h",
|
||||
"src/Imath/ImathFun.cpp",
|
||||
"src/Imath/ImathMatrixAlgo.cpp",
|
||||
"src/Imath/ImathRandom.cpp",
|
||||
"src/Imath/half.cpp",
|
||||
"src/Imath/toFloat.h",
|
||||
],
|
||||
hdrs = [
|
||||
"src/Imath/ImathBox.h",
|
||||
"src/Imath/ImathBoxAlgo.h",
|
||||
"src/Imath/ImathColor.h",
|
||||
"src/Imath/ImathColorAlgo.h",
|
||||
"src/Imath/ImathEuler.h",
|
||||
"src/Imath/ImathExport.h",
|
||||
"src/Imath/ImathForward.h",
|
||||
"src/Imath/ImathFrame.h",
|
||||
"src/Imath/ImathFrustum.h",
|
||||
"src/Imath/ImathFrustumTest.h",
|
||||
"src/Imath/ImathFun.h",
|
||||
"src/Imath/ImathGL.h",
|
||||
"src/Imath/ImathGLU.h",
|
||||
"src/Imath/ImathInt64.h",
|
||||
"src/Imath/ImathInterval.h",
|
||||
"src/Imath/ImathLine.h",
|
||||
"src/Imath/ImathLineAlgo.h",
|
||||
"src/Imath/ImathMath.h",
|
||||
"src/Imath/ImathMatrix.h",
|
||||
"src/Imath/ImathMatrixAlgo.h",
|
||||
"src/Imath/ImathNamespace.h",
|
||||
"src/Imath/ImathPlane.h",
|
||||
"src/Imath/ImathPlatform.h",
|
||||
"src/Imath/ImathQuat.h",
|
||||
"src/Imath/ImathRandom.h",
|
||||
"src/Imath/ImathRoots.h",
|
||||
"src/Imath/ImathShear.h",
|
||||
"src/Imath/ImathSphere.h",
|
||||
"src/Imath/ImathTypeTraits.h",
|
||||
"src/Imath/ImathVec.h",
|
||||
"src/Imath/ImathVecAlgo.h",
|
||||
"src/Imath/half.h",
|
||||
"src/Imath/halfFunction.h",
|
||||
"src/Imath/halfLimits.h",
|
||||
],
|
||||
includes = ["src/Imath"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
remote = "https://github.com/AcademySoftwareFoundation/imath",
|
||||
tag = "v3.1.5",
|
||||
)
|
||||
|
||||
new_git_repository(
|
||||
name = "openexr",
|
||||
build_file_content = """
|
||||
load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
|
||||
SUBSTITUTIONS = {
|
||||
"@IEX_INTERNAL_NAMESPACE@": "Iex_3_0",
|
||||
"@IEX_NAMESPACE_CUSTOM@": "0",
|
||||
"@IEX_NAMESPACE@": "Iex",
|
||||
"@ILMTHREAD_INTERNAL_NAMESPACE@": "IlmThread_3_0",
|
||||
"@ILMTHREAD_NAMESPACE_CUSTOM@": "0",
|
||||
"@ILMTHREAD_NAMESPACE@": "IlmThread",
|
||||
"@OPENEXR_IMF_NAMESPACE@": "Imf",
|
||||
"@OPENEXR_INTERNAL_IMF_NAMESPACE@": "Imf_3_0",
|
||||
"@OPENEXR_LIB_VERSION@": "3.0.4",
|
||||
"@OPENEXR_NAMESPACE_CUSTOM@": "0",
|
||||
"@OPENEXR_PACKAGE_NAME@": "OpenEXR 3.0.4",
|
||||
"@OPENEXR_VERSION_EXTRA@": "",
|
||||
"@OPENEXR_VERSION_MAJOR@": "3",
|
||||
"@OPENEXR_VERSION_MINOR@": "0",
|
||||
"@OPENEXR_VERSION_PATCH@": "4",
|
||||
"@OPENEXR_VERSION@": "3.0.4",
|
||||
}
|
||||
YES_DEFINES = [
|
||||
"OPENEXR_ENABLE_API_VISIBILITY", "OPENEXR_IMF_HAVE_COMPLETE_IOMANIP",
|
||||
"OPENEXR_HAVE_LARGE_STACK",
|
||||
]
|
||||
NO_DEFINES = [
|
||||
"HAVE_UCONTEXT_H", "IEX_HAVE_CONTROL_REGISTER_SUPPORT",
|
||||
"IEX_HAVE_SIGCONTEXT_CONTROL_REGISTER_SUPPORT", "OPENEXR_IMF_HAVE_DARWIN",
|
||||
"OPENEXR_IMF_HAVE_GCC_INLINE_ASM_AVX", "OPENEXR_IMF_HAVE_LINUX_PROCFS",
|
||||
"OPENEXR_IMF_HAVE_SYSCONF_NPROCESSORS_ONLN",
|
||||
]
|
||||
ONE_DEFINES = [
|
||||
"ILMTHREAD_THREADING_ENABLED",
|
||||
]
|
||||
ZERO_DEFINES = [
|
||||
"ILMTHREAD_HAVE_POSIX_SEMAPHORES",
|
||||
]
|
||||
SUBSTITUTIONS.update({
|
||||
"#cmakedefine " + key : "#define " + key for key in YES_DEFINES
|
||||
})
|
||||
SUBSTITUTIONS.update({
|
||||
"#cmakedefine " + key : "// #define " + key for key in NO_DEFINES
|
||||
})
|
||||
SUBSTITUTIONS.update({
|
||||
"#cmakedefine01 " + key : "#define " + key + " 1" for key in ONE_DEFINES
|
||||
})
|
||||
SUBSTITUTIONS.update({
|
||||
"#cmakedefine01 " + key : "#define " + key + " 0" for key in ZERO_DEFINES
|
||||
})
|
||||
[
|
||||
expand_template(
|
||||
name = "expand_" + item,
|
||||
template = "cmake/" + item + ".h.in",
|
||||
out = "src/lib/Iex/" + item + ".h",
|
||||
substitutions = SUBSTITUTIONS,
|
||||
) for item in ["IexConfig", "IexConfigInternal"]
|
||||
]
|
||||
[
|
||||
expand_template(
|
||||
name = "expand_" + item,
|
||||
template = "cmake/" + item + ".h.in",
|
||||
out = "src/lib/IlmThread/" + item + ".h",
|
||||
substitutions = SUBSTITUTIONS,
|
||||
) for item in ["IlmThreadConfig"]
|
||||
]
|
||||
[
|
||||
expand_template(
|
||||
name = "expand_" + item,
|
||||
template = "cmake/" + item + ".h.in",
|
||||
out = "src/lib/OpenEXR/" + item + ".h",
|
||||
substitutions = SUBSTITUTIONS,
|
||||
) for item in ["OpenEXRConfig", "OpenEXRConfigInternal"]
|
||||
]
|
||||
cc_library(
|
||||
name = "Iex",
|
||||
srcs = [
|
||||
"src/lib/Iex/IexBaseExc.cpp",
|
||||
"src/lib/Iex/IexMathFloatExc.cpp",
|
||||
"src/lib/Iex/IexMathFpu.cpp",
|
||||
"src/lib/Iex/IexThrowErrnoExc.cpp",
|
||||
],
|
||||
hdrs = [
|
||||
"src/lib/Iex/Iex.h",
|
||||
"src/lib/Iex/IexBaseExc.h",
|
||||
":src/lib/Iex/IexConfig.h",
|
||||
":src/lib/Iex/IexConfigInternal.h",
|
||||
"src/lib/Iex/IexErrnoExc.h",
|
||||
"src/lib/Iex/IexExport.h",
|
||||
"src/lib/Iex/IexForward.h",
|
||||
"src/lib/Iex/IexMacros.h",
|
||||
"src/lib/Iex/IexMathExc.h",
|
||||
"src/lib/Iex/IexMathFloatExc.h",
|
||||
"src/lib/Iex/IexMathFpu.h",
|
||||
"src/lib/Iex/IexMathIeeeExc.h",
|
||||
"src/lib/Iex/IexNamespace.h",
|
||||
"src/lib/Iex/IexThrowErrnoExc.h",
|
||||
":src/lib/OpenEXR/OpenEXRConfig.h",
|
||||
],
|
||||
includes = [
|
||||
"src/lib/Iex",
|
||||
"src/lib/OpenEXR",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "IlmThread",
|
||||
srcs = [
|
||||
"src/lib/IlmThread/IlmThread.cpp",
|
||||
"src/lib/IlmThread/IlmThreadPool.cpp",
|
||||
"src/lib/IlmThread/IlmThreadSemaphore.cpp",
|
||||
"src/lib/IlmThread/IlmThreadSemaphoreOSX.cpp",
|
||||
"src/lib/IlmThread/IlmThreadSemaphorePosix.cpp",
|
||||
"src/lib/IlmThread/IlmThreadSemaphorePosixCompat.cpp",
|
||||
"src/lib/IlmThread/IlmThreadSemaphoreWin32.cpp",
|
||||
],
|
||||
hdrs = [
|
||||
"src/lib/IlmThread/IlmThread.h",
|
||||
":src/lib/IlmThread/IlmThreadConfig.h",
|
||||
"src/lib/IlmThread/IlmThreadExport.h",
|
||||
"src/lib/IlmThread/IlmThreadForward.h",
|
||||
"src/lib/IlmThread/IlmThreadMutex.h",
|
||||
"src/lib/IlmThread/IlmThreadNamespace.h",
|
||||
"src/lib/IlmThread/IlmThreadPool.h",
|
||||
"src/lib/IlmThread/IlmThreadSemaphore.h",
|
||||
],
|
||||
includes = ["src/lib/IlmThread"],
|
||||
deps = [":Iex"],
|
||||
)
|
||||
cc_library(
|
||||
name = "OpenEXR",
|
||||
srcs = [
|
||||
"src/lib/OpenEXR/ImfAcesFile.cpp",
|
||||
"src/lib/OpenEXR/ImfAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfB44Compressor.cpp",
|
||||
"src/lib/OpenEXR/ImfBoxAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfCRgbaFile.cpp",
|
||||
"src/lib/OpenEXR/ImfChannelList.cpp",
|
||||
"src/lib/OpenEXR/ImfChannelListAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfChromaticities.cpp",
|
||||
"src/lib/OpenEXR/ImfChromaticitiesAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfCompositeDeepScanLine.cpp",
|
||||
"src/lib/OpenEXR/ImfCompressionAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfCompressor.cpp",
|
||||
"src/lib/OpenEXR/ImfConvert.cpp",
|
||||
"src/lib/OpenEXR/ImfDeepCompositing.cpp",
|
||||
"src/lib/OpenEXR/ImfDeepFrameBuffer.cpp",
|
||||
"src/lib/OpenEXR/ImfDeepImageStateAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfDeepScanLineInputFile.cpp",
|
||||
"src/lib/OpenEXR/ImfDeepScanLineInputPart.cpp",
|
||||
"src/lib/OpenEXR/ImfDeepScanLineOutputFile.cpp",
|
||||
"src/lib/OpenEXR/ImfDeepScanLineOutputPart.cpp",
|
||||
"src/lib/OpenEXR/ImfDeepTiledInputFile.cpp",
|
||||
"src/lib/OpenEXR/ImfDeepTiledInputPart.cpp",
|
||||
"src/lib/OpenEXR/ImfDeepTiledOutputFile.cpp",
|
||||
"src/lib/OpenEXR/ImfDeepTiledOutputPart.cpp",
|
||||
"src/lib/OpenEXR/ImfDoubleAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfDwaCompressor.cpp",
|
||||
"src/lib/OpenEXR/ImfEnvmap.cpp",
|
||||
"src/lib/OpenEXR/ImfEnvmapAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfFastHuf.cpp",
|
||||
"src/lib/OpenEXR/ImfFloatAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfFloatVectorAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfFrameBuffer.cpp",
|
||||
"src/lib/OpenEXR/ImfFramesPerSecond.cpp",
|
||||
"src/lib/OpenEXR/ImfGenericInputFile.cpp",
|
||||
"src/lib/OpenEXR/ImfGenericOutputFile.cpp",
|
||||
"src/lib/OpenEXR/ImfHeader.cpp",
|
||||
"src/lib/OpenEXR/ImfHuf.cpp",
|
||||
"src/lib/OpenEXR/ImfIDManifest.cpp",
|
||||
"src/lib/OpenEXR/ImfIDManifestAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfIO.cpp",
|
||||
"src/lib/OpenEXR/ImfInputFile.cpp",
|
||||
"src/lib/OpenEXR/ImfInputPart.cpp",
|
||||
"src/lib/OpenEXR/ImfInputPartData.cpp",
|
||||
"src/lib/OpenEXR/ImfIntAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfKeyCode.cpp",
|
||||
"src/lib/OpenEXR/ImfKeyCodeAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfLineOrderAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfLut.cpp",
|
||||
"src/lib/OpenEXR/ImfMatrixAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfMisc.cpp",
|
||||
"src/lib/OpenEXR/ImfMultiPartInputFile.cpp",
|
||||
"src/lib/OpenEXR/ImfMultiPartOutputFile.cpp",
|
||||
"src/lib/OpenEXR/ImfMultiView.cpp",
|
||||
"src/lib/OpenEXR/ImfOpaqueAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfOutputFile.cpp",
|
||||
"src/lib/OpenEXR/ImfOutputPart.cpp",
|
||||
"src/lib/OpenEXR/ImfOutputPartData.cpp",
|
||||
"src/lib/OpenEXR/ImfPartType.cpp",
|
||||
"src/lib/OpenEXR/ImfPizCompressor.cpp",
|
||||
"src/lib/OpenEXR/ImfPreviewImage.cpp",
|
||||
"src/lib/OpenEXR/ImfPreviewImageAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfPxr24Compressor.cpp",
|
||||
"src/lib/OpenEXR/ImfRational.cpp",
|
||||
"src/lib/OpenEXR/ImfRationalAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfRgbaFile.cpp",
|
||||
"src/lib/OpenEXR/ImfRgbaYca.cpp",
|
||||
"src/lib/OpenEXR/ImfRle.cpp",
|
||||
"src/lib/OpenEXR/ImfRleCompressor.cpp",
|
||||
"src/lib/OpenEXR/ImfScanLineInputFile.cpp",
|
||||
"src/lib/OpenEXR/ImfStandardAttributes.cpp",
|
||||
"src/lib/OpenEXR/ImfStdIO.cpp",
|
||||
"src/lib/OpenEXR/ImfStringAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfStringVectorAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfSystemSpecific.cpp",
|
||||
"src/lib/OpenEXR/ImfTestFile.cpp",
|
||||
"src/lib/OpenEXR/ImfThreading.cpp",
|
||||
"src/lib/OpenEXR/ImfTileDescriptionAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfTileOffsets.cpp",
|
||||
"src/lib/OpenEXR/ImfTiledInputFile.cpp",
|
||||
"src/lib/OpenEXR/ImfTiledInputPart.cpp",
|
||||
"src/lib/OpenEXR/ImfTiledMisc.cpp",
|
||||
"src/lib/OpenEXR/ImfTiledOutputFile.cpp",
|
||||
"src/lib/OpenEXR/ImfTiledOutputPart.cpp",
|
||||
"src/lib/OpenEXR/ImfTiledRgbaFile.cpp",
|
||||
"src/lib/OpenEXR/ImfTimeCode.cpp",
|
||||
"src/lib/OpenEXR/ImfTimeCodeAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfVecAttribute.cpp",
|
||||
"src/lib/OpenEXR/ImfVersion.cpp",
|
||||
"src/lib/OpenEXR/ImfWav.cpp",
|
||||
"src/lib/OpenEXR/ImfZip.cpp",
|
||||
"src/lib/OpenEXR/ImfZipCompressor.cpp",
|
||||
"src/lib/OpenEXR/b44ExpLogTable.h",
|
||||
"src/lib/OpenEXR/dwaLookups.h",
|
||||
],
|
||||
hdrs = [
|
||||
":src/lib/Iex/IexConfig.h",
|
||||
":src/lib/Iex/IexConfigInternal.h",
|
||||
":src/lib/IlmThread/IlmThreadConfig.h",
|
||||
"src/lib/OpenEXR/ImfAcesFile.h",
|
||||
"src/lib/OpenEXR/ImfArray.h",
|
||||
"src/lib/OpenEXR/ImfAttribute.h",
|
||||
"src/lib/OpenEXR/ImfAutoArray.h",
|
||||
"src/lib/OpenEXR/ImfB44Compressor.h",
|
||||
"src/lib/OpenEXR/ImfBoxAttribute.h",
|
||||
"src/lib/OpenEXR/ImfCRgbaFile.h",
|
||||
"src/lib/OpenEXR/ImfChannelList.h",
|
||||
"src/lib/OpenEXR/ImfChannelListAttribute.h",
|
||||
"src/lib/OpenEXR/ImfCheckedArithmetic.h",
|
||||
"src/lib/OpenEXR/ImfChromaticities.h",
|
||||
"src/lib/OpenEXR/ImfChromaticitiesAttribute.h",
|
||||
"src/lib/OpenEXR/ImfCompositeDeepScanLine.h",
|
||||
"src/lib/OpenEXR/ImfCompression.h",
|
||||
"src/lib/OpenEXR/ImfCompressionAttribute.h",
|
||||
"src/lib/OpenEXR/ImfCompressor.h",
|
||||
"src/lib/OpenEXR/ImfConvert.h",
|
||||
"src/lib/OpenEXR/ImfDeepCompositing.h",
|
||||
"src/lib/OpenEXR/ImfDeepFrameBuffer.h",
|
||||
"src/lib/OpenEXR/ImfDeepImageState.h",
|
||||
"src/lib/OpenEXR/ImfDeepImageStateAttribute.h",
|
||||
"src/lib/OpenEXR/ImfDeepScanLineInputFile.h",
|
||||
"src/lib/OpenEXR/ImfDeepScanLineInputPart.h",
|
||||
"src/lib/OpenEXR/ImfDeepScanLineOutputFile.h",
|
||||
"src/lib/OpenEXR/ImfDeepScanLineOutputPart.h",
|
||||
"src/lib/OpenEXR/ImfDeepTiledInputFile.h",
|
||||
"src/lib/OpenEXR/ImfDeepTiledInputPart.h",
|
||||
"src/lib/OpenEXR/ImfDeepTiledOutputFile.h",
|
||||
"src/lib/OpenEXR/ImfDeepTiledOutputPart.h",
|
||||
"src/lib/OpenEXR/ImfDoubleAttribute.h",
|
||||
"src/lib/OpenEXR/ImfDwaCompressor.h",
|
||||
"src/lib/OpenEXR/ImfDwaCompressorSimd.h",
|
||||
"src/lib/OpenEXR/ImfEnvmap.h",
|
||||
"src/lib/OpenEXR/ImfEnvmapAttribute.h",
|
||||
"src/lib/OpenEXR/ImfExport.h",
|
||||
"src/lib/OpenEXR/ImfFastHuf.h",
|
||||
"src/lib/OpenEXR/ImfFloatAttribute.h",
|
||||
"src/lib/OpenEXR/ImfFloatVectorAttribute.h",
|
||||
"src/lib/OpenEXR/ImfForward.h",
|
||||
"src/lib/OpenEXR/ImfFrameBuffer.h",
|
||||
"src/lib/OpenEXR/ImfFramesPerSecond.h",
|
||||
"src/lib/OpenEXR/ImfGenericInputFile.h",
|
||||
"src/lib/OpenEXR/ImfGenericOutputFile.h",
|
||||
"src/lib/OpenEXR/ImfHeader.h",
|
||||
"src/lib/OpenEXR/ImfHuf.h",
|
||||
"src/lib/OpenEXR/ImfIDManifest.h",
|
||||
"src/lib/OpenEXR/ImfIDManifestAttribute.h",
|
||||
"src/lib/OpenEXR/ImfIO.h",
|
||||
"src/lib/OpenEXR/ImfInputFile.h",
|
||||
"src/lib/OpenEXR/ImfInputPart.h",
|
||||
"src/lib/OpenEXR/ImfInputPartData.h",
|
||||
"src/lib/OpenEXR/ImfInputStreamMutex.h",
|
||||
"src/lib/OpenEXR/ImfInt64.h",
|
||||
"src/lib/OpenEXR/ImfIntAttribute.h",
|
||||
"src/lib/OpenEXR/ImfKeyCode.h",
|
||||
"src/lib/OpenEXR/ImfKeyCodeAttribute.h",
|
||||
"src/lib/OpenEXR/ImfLineOrder.h",
|
||||
"src/lib/OpenEXR/ImfLineOrderAttribute.h",
|
||||
"src/lib/OpenEXR/ImfLut.h",
|
||||
"src/lib/OpenEXR/ImfMatrixAttribute.h",
|
||||
"src/lib/OpenEXR/ImfMisc.h",
|
||||
"src/lib/OpenEXR/ImfMultiPartInputFile.h",
|
||||
"src/lib/OpenEXR/ImfMultiPartOutputFile.h",
|
||||
"src/lib/OpenEXR/ImfMultiView.h",
|
||||
"src/lib/OpenEXR/ImfName.h",
|
||||
"src/lib/OpenEXR/ImfNamespace.h",
|
||||
"src/lib/OpenEXR/ImfOpaqueAttribute.h",
|
||||
"src/lib/OpenEXR/ImfOptimizedPixelReading.h",
|
||||
"src/lib/OpenEXR/ImfOutputFile.h",
|
||||
"src/lib/OpenEXR/ImfOutputPart.h",
|
||||
"src/lib/OpenEXR/ImfOutputPartData.h",
|
||||
"src/lib/OpenEXR/ImfOutputStreamMutex.h",
|
||||
"src/lib/OpenEXR/ImfPartHelper.h",
|
||||
"src/lib/OpenEXR/ImfPartType.h",
|
||||
"src/lib/OpenEXR/ImfPixelType.h",
|
||||
"src/lib/OpenEXR/ImfPizCompressor.h",
|
||||
"src/lib/OpenEXR/ImfPreviewImage.h",
|
||||
"src/lib/OpenEXR/ImfPreviewImageAttribute.h",
|
||||
"src/lib/OpenEXR/ImfPxr24Compressor.h",
|
||||
"src/lib/OpenEXR/ImfRational.h",
|
||||
"src/lib/OpenEXR/ImfRationalAttribute.h",
|
||||
"src/lib/OpenEXR/ImfRgba.h",
|
||||
"src/lib/OpenEXR/ImfRgbaFile.h",
|
||||
"src/lib/OpenEXR/ImfRgbaYca.h",
|
||||
"src/lib/OpenEXR/ImfRle.h",
|
||||
"src/lib/OpenEXR/ImfRleCompressor.h",
|
||||
"src/lib/OpenEXR/ImfScanLineInputFile.h",
|
||||
"src/lib/OpenEXR/ImfSimd.h",
|
||||
"src/lib/OpenEXR/ImfStandardAttributes.h",
|
||||
"src/lib/OpenEXR/ImfStdIO.h",
|
||||
"src/lib/OpenEXR/ImfStringAttribute.h",
|
||||
"src/lib/OpenEXR/ImfStringVectorAttribute.h",
|
||||
"src/lib/OpenEXR/ImfSystemSpecific.h",
|
||||
"src/lib/OpenEXR/ImfTestFile.h",
|
||||
"src/lib/OpenEXR/ImfThreading.h",
|
||||
"src/lib/OpenEXR/ImfTileDescription.h",
|
||||
"src/lib/OpenEXR/ImfTileDescriptionAttribute.h",
|
||||
"src/lib/OpenEXR/ImfTileOffsets.h",
|
||||
"src/lib/OpenEXR/ImfTiledInputFile.h",
|
||||
"src/lib/OpenEXR/ImfTiledInputPart.h",
|
||||
"src/lib/OpenEXR/ImfTiledMisc.h",
|
||||
"src/lib/OpenEXR/ImfTiledOutputFile.h",
|
||||
"src/lib/OpenEXR/ImfTiledOutputPart.h",
|
||||
"src/lib/OpenEXR/ImfTiledRgbaFile.h",
|
||||
"src/lib/OpenEXR/ImfTimeCode.h",
|
||||
"src/lib/OpenEXR/ImfTimeCodeAttribute.h",
|
||||
"src/lib/OpenEXR/ImfVecAttribute.h",
|
||||
"src/lib/OpenEXR/ImfVersion.h",
|
||||
"src/lib/OpenEXR/ImfWav.h",
|
||||
"src/lib/OpenEXR/ImfXdr.h",
|
||||
"src/lib/OpenEXR/ImfZip.h",
|
||||
"src/lib/OpenEXR/ImfZipCompressor.h",
|
||||
":src/lib/OpenEXR/OpenEXRConfig.h",
|
||||
":src/lib/OpenEXR/OpenEXRConfigInternal.h",
|
||||
],
|
||||
includes = ["src/lib/OpenEXR"],
|
||||
deps = [
|
||||
":IlmThread",
|
||||
"@imath//:Imath",
|
||||
"@zlib//:zlib",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
remote = "https://github.com/AcademySoftwareFoundation/openexr",
|
||||
tag = "v3.1.5",
|
||||
)
|
||||
|
|
|
@ -21,6 +21,7 @@ CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH:-}
|
|||
CMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER:-}
|
||||
CMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER:-}
|
||||
CMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM:-}
|
||||
SKIP_BUILD="${SKIP_BUILD:-0}"
|
||||
SKIP_TEST="${SKIP_TEST:-0}"
|
||||
TARGETS="${TARGETS:-all doc}"
|
||||
TEST_SELECTOR="${TEST_SELECTOR:-}"
|
||||
|
@ -459,6 +460,9 @@ cmake_configure() {
|
|||
}
|
||||
|
||||
cmake_build_and_test() {
|
||||
if [[ "${SKIP_BUILD}" -eq "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
# gtest_discover_tests() runs the test binaries to discover the list of tests
|
||||
# at build time, which fails under qemu.
|
||||
ASAN_OPTIONS=detect_leaks=0 cmake --build "${BUILD_DIR}" -- $TARGETS
|
||||
|
@ -1192,11 +1196,11 @@ cmd_fuzz() {
|
|||
)
|
||||
}
|
||||
|
||||
# Runs the linter (clang-format) on the pending CLs.
|
||||
# Runs the linters (clang-format, build_cleaner, buildirier) on the pending CLs.
|
||||
cmd_lint() {
|
||||
merge_request_commits
|
||||
{ set +x; } 2>/dev/null
|
||||
local versions=(${1:-6.0 7 8 9 10 11})
|
||||
local versions=(${1:-16 15 14 13 12 11 10 9 8 7 6.0})
|
||||
local clang_format_bins=("${versions[@]/#/clang-format-}" clang-format)
|
||||
local tmpdir=$(mktemp -d)
|
||||
CLEANUP_FILES+=("${tmpdir}")
|
||||
|
@ -1210,6 +1214,22 @@ cmd_lint() {
|
|||
echo "Run \`tools/scripts/build_cleaner.py --update\` to apply them" >&2
|
||||
fi
|
||||
|
||||
# It is ok, if buildifier is not installed.
|
||||
if which buildifier >/dev/null; then
|
||||
local buildifier_patch="${tmpdir}/buildifier.patch"
|
||||
local bazel_files=`git -C ${MYDIR} ls-files | grep -E "/BUILD$|WORKSPACE|.bzl$"`
|
||||
set -x
|
||||
buildifier -d ${bazel_files} >"${buildifier_patch}"|| true
|
||||
{ set +x; } 2>/dev/null
|
||||
if [ -s "${buildifier_patch}" ]; then
|
||||
ret=1
|
||||
echo 'buildifier have found some problems in Bazel build files:' >&2
|
||||
"${COLORDIFF_BIN}" <"${buildifier_patch}"
|
||||
echo 'To fix them run (from the base directory):' >&2
|
||||
echo ' buildifier `git ls-files | grep -E "/BUILD$|WORKSPACE|.bzl$"`' >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
local installed=()
|
||||
local clang_patch
|
||||
local clang_format
|
||||
|
@ -1495,6 +1515,7 @@ You can pass some optional environment variables as well:
|
|||
- FUZZER_MAX_TIME: "fuzz" command fuzzer running timeout in seconds.
|
||||
- LINT_OUTPUT: Path to the output patch from the "lint" command.
|
||||
- SKIP_CPUSET=1: Skip modifying the cpuset in the arm_benchmark.
|
||||
- SKIP_BUILD=1: Skip the build stage, cmake configure only.
|
||||
- SKIP_TEST=1: Skip the test stage.
|
||||
- STORE_IMAGES=0: Makes the benchmark discard the computed images.
|
||||
- TEST_STACK_LIMIT: Stack size limit (ulimit -s) during tests, in KiB.
|
||||
|
|
|
@ -8,11 +8,14 @@ include /usr/share/dpkg/pkg-info.mk
|
|||
override_dh_auto_configure:
|
||||
# TODO(deymo): Remove the DCMAKE_BUILD_TYPE once builds without NDEBUG
|
||||
# are as useful as Release builds.
|
||||
# TODO(szabadka) Re-enable jpegli after tests are fixed on Ubuntu 20.04,
|
||||
# and debian:buster
|
||||
dh_auto_configure -- \
|
||||
-DJPEGXL_VERSION=$(DEB_VERSION) \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DJPEGXL_FORCE_SYSTEM_GTEST=ON \
|
||||
-DJPEGXL_FORCE_SYSTEM_BROTLI=ON \
|
||||
-DJPEGXL_FORCE_SYSTEM_HWY=ON \
|
||||
-DJPEGXL_ENABLE_JPEGLI=OFF \
|
||||
-DJPEGXL_ENABLE_JPEGLI_LIBJPEG=OFF \
|
||||
-DJPEGXL_ENABLE_PLUGINS=ON
|
||||
-DJPEGXL_ENABLE_PLUGINS=ON
|
||||
|
|
|
@ -14,7 +14,7 @@ MYDIR=$(dirname $(realpath "$0"))
|
|||
# Git revisions we use for the given submodules. Update these whenever you
|
||||
# update a git submodule.
|
||||
THIRD_PARTY_BROTLI="36533a866ed1ca4b75cf049f4521e4ec5fe24727"
|
||||
THIRD_PARTY_HIGHWAY="58746ca5b9f9444a2a3549704602ecc6239f8f41"
|
||||
THIRD_PARTY_HIGHWAY="46e365d6770f5d7a4240d8ac9d8e928a520478ea"
|
||||
THIRD_PARTY_SKCMS="b25b07b4b07990811de121c0356155b2ba0f4318"
|
||||
THIRD_PARTY_SJPEG="868ab558fad70fcbe8863ba4e85179eeb81cc840"
|
||||
THIRD_PARTY_ZLIB="cacf7f1d4e3d44d871b605da3b647f07d718623f"
|
||||
|
|
|
@ -31,7 +31,6 @@ RUN set -ex; \
|
|||
libgoogle-perftools4 \
|
||||
libopenexr22 \
|
||||
libpng16-16 \
|
||||
libqt5x11extras5 \
|
||||
libsdl2-2.0-0 \
|
||||
parallel; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
|
|
|
@ -214,10 +214,8 @@ install_pkgs() {
|
|||
libgif7:"${ubarch}"
|
||||
libjpeg-dev:"${ubarch}"
|
||||
libpng-dev:"${ubarch}"
|
||||
libqt5x11extras5-dev:"${ubarch}"
|
||||
|
||||
libstdc++-8-dev:"${ubarch}"
|
||||
qtbase5-dev:"${ubarch}"
|
||||
|
||||
# For OpenEXR:
|
||||
libilmbase12:"${ubarch}"
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
// available at once). The example outputs the pixels and color information to a
|
||||
// floating point image and an ICC profile on disk.
|
||||
|
||||
#include <jxl/decode.h>
|
||||
#include <jxl/decode_cxx.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
@ -14,9 +16,6 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/decode.h"
|
||||
#include "jxl/decode_cxx.h"
|
||||
|
||||
bool DecodeJpegXlExif(const uint8_t* jxl, size_t size,
|
||||
std::vector<uint8_t>* exif) {
|
||||
auto dec = JxlDecoderMake(nullptr);
|
||||
|
|
|
@ -7,7 +7,15 @@
|
|||
// available at once). The example outputs the pixels and color information to a
|
||||
// floating point image and an ICC profile on disk.
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <jxl/decode.h>
|
||||
#include <jxl/decode_cxx.h>
|
||||
#include <jxl/resizable_parallel_runner.h>
|
||||
#include <jxl/resizable_parallel_runner_cxx.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
@ -15,11 +23,6 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/decode.h"
|
||||
#include "jxl/decode_cxx.h"
|
||||
#include "jxl/resizable_parallel_runner.h"
|
||||
#include "jxl/resizable_parallel_runner_cxx.h"
|
||||
|
||||
/** Decodes JPEG XL image to floating point pixels and ICC Profile. Pixel are
|
||||
* stored as floating point, as interleaved RGBA (4 floating point values per
|
||||
* pixel), line per line from top to bottom. Pixel values have nominal range
|
||||
|
|
|
@ -6,7 +6,15 @@
|
|||
// This C++ example decodes a JPEG XL image progressively (input bytes are
|
||||
// passed in chunks). The example outputs the intermediate steps to PAM files.
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <jxl/decode.h>
|
||||
#include <jxl/decode_cxx.h>
|
||||
#include <jxl/resizable_parallel_runner.h>
|
||||
#include <jxl/resizable_parallel_runner_cxx.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
@ -14,11 +22,6 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/decode.h"
|
||||
#include "jxl/decode_cxx.h"
|
||||
#include "jxl/resizable_parallel_runner.h"
|
||||
#include "jxl/resizable_parallel_runner_cxx.h"
|
||||
|
||||
bool WritePAM(const char* filename, const uint8_t* buffer, size_t w, size_t h) {
|
||||
FILE* fp = fopen(filename, "wb");
|
||||
if (!fp) {
|
||||
|
@ -30,7 +33,11 @@ bool WritePAM(const char* filename, const uint8_t* buffer, size_t w, size_t h) {
|
|||
"\nDEPTH 4\nMAXVAL 255\nTUPLTYPE "
|
||||
"RGB_ALPHA\nENDHDR\n",
|
||||
static_cast<uint64_t>(w), static_cast<uint64_t>(h));
|
||||
fwrite(buffer, 1, w * h * 4, fp);
|
||||
size_t num_bytes = w * h * 4;
|
||||
if (fwrite(buffer, 1, num_bytes, fp) != num_bytes) {
|
||||
fclose(fp);
|
||||
return false;
|
||||
};
|
||||
if (fclose(fp) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
// This example encodes a file containing a floating point image to another
|
||||
// file containing JPEG XL image with a single frame.
|
||||
|
||||
#include <jxl/encode.h>
|
||||
#include <jxl/encode_cxx.h>
|
||||
#include <jxl/thread_parallel_runner.h>
|
||||
#include <jxl/thread_parallel_runner_cxx.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
|
@ -13,11 +17,6 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/encode.h"
|
||||
#include "jxl/encode_cxx.h"
|
||||
#include "jxl/thread_parallel_runner.h"
|
||||
#include "jxl/thread_parallel_runner_cxx.h"
|
||||
|
||||
/**
|
||||
* Reads from .pfm file (Portable FloatMap)
|
||||
*
|
||||
|
@ -229,6 +228,7 @@ bool WriteFile(const std::vector<uint8_t>& bytes, const char* filename) {
|
|||
if (fwrite(bytes.data(), sizeof(uint8_t), bytes.size(), file) !=
|
||||
bytes.size()) {
|
||||
fprintf(stderr, "Could not write bytes to %s\n", filename);
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
if (fclose(file) != 0) {
|
||||
|
|
|
@ -1,58 +1,76 @@
|
|||
package(default_visibility = ['//:__subpackages__'])
|
||||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
load('jxl_lists.bzl',
|
||||
'libjxl_codec_apng_sources',
|
||||
'libjxl_codec_gif_sources',
|
||||
'libjxl_codec_jpegli_sources',
|
||||
'libjxl_codec_jpg_sources',
|
||||
'libjxl_codec_jxl_sources',
|
||||
'libjxl_codec_npy_sources',
|
||||
'libjxl_codec_pgx_sources',
|
||||
'libjxl_codec_pnm_sources',
|
||||
'libjxl_dec_box_sources',
|
||||
'libjxl_dec_jpeg_sources',
|
||||
'libjxl_dec_sources',
|
||||
'libjxl_enc_sources',
|
||||
'libjxl_extras_for_tools_sources',
|
||||
'libjxl_extras_sources',
|
||||
'libjxl_jpegli_sources',
|
||||
'libjxl_jpegli_testlib_files',
|
||||
'libjxl_jpegli_tests',
|
||||
'libjxl_major_version',
|
||||
'libjxl_minor_version',
|
||||
'libjxl_patch_version',
|
||||
'libjxl_public_headers',
|
||||
'libjxl_testlib_files',
|
||||
'libjxl_tests',
|
||||
'libjxl_threads_public_headers',
|
||||
'libjxl_threads_sources',
|
||||
#'libjxl_codec_exr_sources',
|
||||
# Load sources/headers/tests lists.
|
||||
load(
|
||||
"jxl_lists.bzl",
|
||||
"libjxl_base_sources",
|
||||
"libjxl_codec_apng_sources",
|
||||
"libjxl_codec_exr_sources",
|
||||
"libjxl_codec_gif_sources",
|
||||
"libjxl_codec_jpegli_sources",
|
||||
"libjxl_codec_jpg_sources",
|
||||
"libjxl_codec_jxl_sources",
|
||||
"libjxl_codec_npy_sources",
|
||||
"libjxl_codec_pgx_sources",
|
||||
"libjxl_codec_pnm_sources",
|
||||
"libjxl_dec_box_sources",
|
||||
"libjxl_dec_jpeg_sources",
|
||||
"libjxl_dec_sources",
|
||||
"libjxl_enc_sources",
|
||||
"libjxl_extras_for_tools_sources",
|
||||
"libjxl_extras_sources",
|
||||
#'libjxl_gbench_sources',
|
||||
#'libjxl_profiler_sources',
|
||||
"libjxl_jpegli_sources",
|
||||
"libjxl_jpegli_testlib_files",
|
||||
"libjxl_jpegli_tests",
|
||||
"libjxl_major_version",
|
||||
"libjxl_minor_version",
|
||||
"libjxl_patch_version",
|
||||
"libjxl_public_headers",
|
||||
"libjxl_testlib_files",
|
||||
"libjxl_tests",
|
||||
"libjxl_threads_public_headers",
|
||||
"libjxl_threads_sources",
|
||||
)
|
||||
load(
|
||||
"jxl_vars.bzl",
|
||||
"libjxl_deps_brotli",
|
||||
"libjxl_deps_exr",
|
||||
"libjxl_deps_gif",
|
||||
"libjxl_deps_gtest",
|
||||
"libjxl_deps_hwy",
|
||||
"libjxl_deps_hwy_nanobenchmark",
|
||||
"libjxl_deps_hwy_test_util",
|
||||
"libjxl_deps_jpeg",
|
||||
"libjxl_deps_jxl_box",
|
||||
"libjxl_deps_png",
|
||||
"libjxl_deps_runfiles",
|
||||
"libjxl_deps_skcms",
|
||||
"libjxl_deps_testdata",
|
||||
"libjxl_root_package",
|
||||
"libjxl_test_shards",
|
||||
"libjxl_test_timeouts",
|
||||
)
|
||||
load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
|
||||
|
||||
DEFAULT_VISIBILITY = ["//:__subpackages__"]
|
||||
|
||||
DEFAULT_COMPATIBILITY = []
|
||||
|
||||
INCLUDES_DIR = "include"
|
||||
|
||||
package(
|
||||
default_visibility = ["//:__subpackages__"],
|
||||
)
|
||||
|
||||
load('jxl_vars.bzl',
|
||||
'libjxl_deps_brotli',
|
||||
'libjxl_deps_gif',
|
||||
'libjxl_deps_gtest',
|
||||
'libjxl_deps_hwy_nanobenchmark',
|
||||
'libjxl_deps_hwy_test_util',
|
||||
'libjxl_deps_hwy',
|
||||
'libjxl_deps_jpeg',
|
||||
'libjxl_deps_jxl_box',
|
||||
'libjxl_deps_png',
|
||||
'libjxl_deps_runfiles',
|
||||
'libjxl_deps_skcms',
|
||||
'libjxl_deps_testdata',
|
||||
'libjxl_root_package',
|
||||
'libjxl_test_shards',
|
||||
'libjxl_test_timeouts',
|
||||
)
|
||||
licenses(["notice"])
|
||||
|
||||
load('@bazel_skylib//rules:expand_template.bzl', 'expand_template')
|
||||
exports_files(["LICENSE"])
|
||||
|
||||
|
||||
EXPORT_TEMPLATE = '''
|
||||
EXPORT_TEMPLATE = """
|
||||
#ifndef @_EXPORT_H
|
||||
#define @_EXPORT_H
|
||||
|
||||
|
@ -64,132 +82,175 @@ EXPORT_TEMPLATE = '''
|
|||
#endif
|
||||
|
||||
#endif
|
||||
'''
|
||||
"""
|
||||
|
||||
JXL_EXPORT_H = INCLUDES_DIR + "/jxl/jxl_export.h"
|
||||
|
||||
JXL_EXPORT_H = 'include/jxl/jxl_export.h'
|
||||
genrule(
|
||||
name = 'create_jxl_export',
|
||||
name = "create_jxl_export",
|
||||
outs = [JXL_EXPORT_H],
|
||||
cmd = 'echo \'' + EXPORT_TEMPLATE.replace('@', 'JXL') + '\' > $@',
|
||||
cmd = "echo '" + EXPORT_TEMPLATE.replace("@", "JXL") + "' > $@",
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
)
|
||||
|
||||
JXL_THREADS_EXPORT_H = 'include/jxl/jxl_threads_export.h'
|
||||
JXL_THREADS_EXPORT_H = INCLUDES_DIR + "/jxl/jxl_threads_export.h"
|
||||
|
||||
genrule(
|
||||
name = 'create_jxl_threads_export',
|
||||
name = "create_jxl_threads_export",
|
||||
outs = [JXL_THREADS_EXPORT_H],
|
||||
cmd = 'echo \'' + EXPORT_TEMPLATE.replace('@', 'JXL_THREADS') + '\' > $@',
|
||||
cmd = "echo '" + EXPORT_TEMPLATE.replace("@", "JXL_THREADS") + "' > $@",
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
)
|
||||
|
||||
JXL_VERSION_H = 'include/jxl/version.h'
|
||||
JXL_VERSION_H = INCLUDES_DIR + "/jxl/version.h"
|
||||
|
||||
# TODO(eustas): extract version from CMake file
|
||||
expand_template(
|
||||
name = 'expand_jxl_version',
|
||||
template = 'jxl/version.h.in',
|
||||
name = "expand_jxl_version",
|
||||
out = JXL_VERSION_H,
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
substitutions = {
|
||||
'@JPEGXL_MAJOR_VERSION@': str(libjxl_major_version),
|
||||
'@JPEGXL_MINOR_VERSION@': str(libjxl_minor_version),
|
||||
'@JPEGXL_PATCH_VERSION@': str(libjxl_patch_version),
|
||||
"@JPEGXL_MAJOR_VERSION@": str(libjxl_major_version),
|
||||
"@JPEGXL_MINOR_VERSION@": str(libjxl_minor_version),
|
||||
"@JPEGXL_PATCH_VERSION@": str(libjxl_patch_version),
|
||||
},
|
||||
template = "jxl/version.h.in",
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = 'jpegxl_version',
|
||||
name = "jxl_version",
|
||||
hdrs = [JXL_VERSION_H],
|
||||
strip_include_prefix = 'include',
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
strip_include_prefix = INCLUDES_DIR,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = 'jpegxl_private',
|
||||
srcs = libjxl_dec_sources + libjxl_dec_box_sources + libjxl_dec_jpeg_sources + libjxl_enc_sources,
|
||||
name = "includes",
|
||||
hdrs = libjxl_public_headers + [JXL_EXPORT_H],
|
||||
strip_include_prefix = 'include',
|
||||
defines = ['JPEGXL_ENABLE_SKCMS=1'],
|
||||
deps = [
|
||||
':jpegxl_version',
|
||||
] + libjxl_deps_brotli + libjxl_deps_hwy + libjxl_deps_skcms
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
strip_include_prefix = INCLUDES_DIR,
|
||||
deps = [":jxl_version"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = 'jpegxl_threads',
|
||||
name = "base",
|
||||
srcs = [path for path in libjxl_base_sources if path.endswith(".cc")],
|
||||
hdrs = [path for path in libjxl_base_sources if path.endswith(".h")],
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
deps = [
|
||||
":includes",
|
||||
] + libjxl_deps_hwy,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "jpegxl",
|
||||
srcs = libjxl_dec_sources + libjxl_dec_box_sources + libjxl_dec_jpeg_sources + libjxl_enc_sources,
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
defines = ["JPEGXL_ENABLE_SKCMS=1"],
|
||||
deps = [
|
||||
":base",
|
||||
":includes",
|
||||
] + libjxl_deps_brotli + libjxl_deps_hwy + libjxl_deps_skcms,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "jpegxl_private",
|
||||
hdrs = [
|
||||
path
|
||||
for path in libjxl_dec_sources + libjxl_dec_box_sources + libjxl_dec_jpeg_sources + libjxl_enc_sources
|
||||
if path.endswith(".h") and not path.endswith("-inl.h")
|
||||
],
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
deps = [":jpegxl"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "jpegxl_threads",
|
||||
srcs = libjxl_threads_sources,
|
||||
hdrs = libjxl_threads_public_headers + [JXL_THREADS_EXPORT_H],
|
||||
strip_include_prefix = 'include',
|
||||
# TODO(eustas): used only for [memory_manager.h, profiler.h, parallel_runner.h]
|
||||
deps = [':jpegxl_private'],
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
strip_include_prefix = INCLUDES_DIR,
|
||||
deps = [
|
||||
":base",
|
||||
":includes",
|
||||
],
|
||||
)
|
||||
|
||||
CODEC_FILES = libjxl_codec_apng_sources + libjxl_codec_gif_sources + libjxl_codec_jpegli_sources + libjxl_codec_jpg_sources + libjxl_codec_jxl_sources + libjxl_codec_npy_sources + libjxl_codec_pgx_sources + libjxl_codec_pnm_sources
|
||||
CODEC_SRCS = [path for path in CODEC_FILES if path.endswith('.cc')]
|
||||
CODEC_HDRS = [path for path in CODEC_FILES if path.endswith('.h')]
|
||||
CODEC_FILES = libjxl_codec_apng_sources + libjxl_codec_exr_sources + libjxl_codec_gif_sources + libjxl_codec_jpegli_sources + libjxl_codec_jpg_sources + libjxl_codec_jxl_sources + libjxl_codec_npy_sources + libjxl_codec_pgx_sources + libjxl_codec_pnm_sources
|
||||
|
||||
CODEC_SRCS = [path for path in CODEC_FILES if path.endswith(".cc")]
|
||||
|
||||
CODEC_HDRS = [path for path in CODEC_FILES if path.endswith(".h")]
|
||||
|
||||
cc_library(
|
||||
name = 'jpegli',
|
||||
name = "jpegli",
|
||||
srcs = libjxl_jpegli_sources,
|
||||
hdrs = [
|
||||
'jpegli/common_internal.h', # TODO(eustas): should not be here
|
||||
"jpegli/common_internal.h", # TODO(eustas): should not be here
|
||||
],
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
deps = [
|
||||
':jpegxl_private',
|
||||
":jpegxl_private",
|
||||
] + libjxl_deps_hwy + libjxl_deps_jpeg,
|
||||
)
|
||||
|
||||
# TODO(eustas): build codecs separately?
|
||||
cc_library(
|
||||
name = 'jpegxl_extras',
|
||||
name = "jpegxl_extras",
|
||||
srcs = libjxl_extras_sources + libjxl_extras_for_tools_sources + CODEC_SRCS,
|
||||
hdrs = CODEC_HDRS,
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
defines = [
|
||||
'JPEGXL_ENABLE_APNG=1',
|
||||
'JPEGXL_ENABLE_EXR=0', # TODO(eustas): add
|
||||
'JPEGXL_ENABLE_GIF=1',
|
||||
'JPEGXL_ENABLE_JPEG=1',
|
||||
'JPEGXL_ENABLE_JPEGLI=1',
|
||||
"JPEGXL_ENABLE_APNG=1",
|
||||
"JPEGXL_ENABLE_EXR=1",
|
||||
"JPEGXL_ENABLE_GIF=1",
|
||||
"JPEGXL_ENABLE_JPEG=1",
|
||||
"JPEGXL_ENABLE_JPEGLI=1",
|
||||
],
|
||||
deps = [
|
||||
':jpegli',
|
||||
':jpegxl_private',
|
||||
':jpegxl_threads',
|
||||
':jpegxl_version'
|
||||
] + libjxl_deps_gif + libjxl_deps_jpeg + libjxl_deps_png
|
||||
":jpegli",
|
||||
":jpegxl_private",
|
||||
":jpegxl_threads",
|
||||
":jxl_version",
|
||||
] + libjxl_deps_exr + libjxl_deps_gif + libjxl_deps_jpeg + libjxl_deps_png,
|
||||
)
|
||||
|
||||
TESTLIB_FILES = libjxl_testlib_files + libjxl_jpegli_testlib_files
|
||||
|
||||
cc_library(
|
||||
name = 'test_utils',
|
||||
name = "test_utils",
|
||||
testonly = 1,
|
||||
srcs = [src for src in TESTLIB_FILES if not src.endswith('.h')],
|
||||
hdrs = [src for src in TESTLIB_FILES if src.endswith('.h')],
|
||||
srcs = [path for path in TESTLIB_FILES if not path.endswith(".h")],
|
||||
hdrs = [path for path in TESTLIB_FILES if path.endswith(".h")],
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
defines = [
|
||||
'JPEGXL_ROOT_PACKAGE=\'"' + libjxl_root_package + '"\'',
|
||||
],
|
||||
deps = [
|
||||
':jpegxl_extras',
|
||||
':jpegxl_private',
|
||||
':jpegli',
|
||||
] + libjxl_deps_runfiles
|
||||
":jpegli",
|
||||
":jpegxl_extras",
|
||||
":jpegxl_private",
|
||||
] + libjxl_deps_runfiles,
|
||||
)
|
||||
|
||||
TESTS = [src.partition('.')[0] for src in libjxl_tests + libjxl_jpegli_tests]
|
||||
TESTS = [path.partition(".")[0] for path in libjxl_tests + libjxl_jpegli_tests]
|
||||
|
||||
[
|
||||
cc_test(
|
||||
name = test,
|
||||
timeout = libjxl_test_timeouts.get(test, "moderate"),
|
||||
srcs = [
|
||||
test + '.cc',
|
||||
'jxl/testing.h',
|
||||
'jpegli/testing.h',
|
||||
test + ".cc",
|
||||
"jpegli/testing.h",
|
||||
"jxl/testing.h",
|
||||
],
|
||||
data = ['//:testdata'],
|
||||
timeout = libjxl_test_timeouts.get(test, 'moderate'),
|
||||
data = ["//:testdata"],
|
||||
shard_count = libjxl_test_shards.get(test, 1),
|
||||
deps = [
|
||||
':jpegxl_extras',
|
||||
':jpegxl_private',
|
||||
':jpegxl_threads', # Some tests use the public threads API.
|
||||
':test_utils',
|
||||
] + libjxl_deps_gtest + libjxl_deps_hwy_test_util + libjxl_deps_hwy_nanobenchmark + libjxl_deps_jxl_box
|
||||
) for test in TESTS
|
||||
":jpegxl_extras",
|
||||
":jpegxl_private",
|
||||
":jpegxl_threads",
|
||||
":test_utils",
|
||||
] + libjxl_deps_gtest + libjxl_deps_hwy_test_util + libjxl_deps_hwy_nanobenchmark + libjxl_deps_jxl_box,
|
||||
)
|
||||
for test in TESTS
|
||||
]
|
||||
|
|
|
@ -17,119 +17,111 @@ set(JPEGXL_LIBRARY_VERSION
|
|||
# versioning allows 0.y.z to have incompatible changes in minor versions.
|
||||
set(JPEGXL_SO_MINOR_VERSION 9)
|
||||
if (JPEGXL_MAJOR_VERSION EQUAL 0)
|
||||
set(JPEGXL_LIBRARY_SOVERSION
|
||||
"${JPEGXL_MAJOR_VERSION}.${JPEGXL_SO_MINOR_VERSION}")
|
||||
set(JPEGXL_LIBRARY_SOVERSION
|
||||
"${JPEGXL_MAJOR_VERSION}.${JPEGXL_SO_MINOR_VERSION}")
|
||||
else()
|
||||
set(JPEGXL_LIBRARY_SOVERSION "${JPEGXL_MAJOR_VERSION}")
|
||||
set(JPEGXL_LIBRARY_SOVERSION "${JPEGXL_MAJOR_VERSION}")
|
||||
endif()
|
||||
|
||||
|
||||
# List of warning and feature flags for our library and tests.
|
||||
if (MSVC)
|
||||
set(JPEGXL_INTERNAL_FLAGS
|
||||
# TODO(janwas): add flags
|
||||
)
|
||||
set(JPEGXL_INTERNAL_FLAGS
|
||||
# TODO(janwas): add flags
|
||||
)
|
||||
else ()
|
||||
set(JPEGXL_INTERNAL_FLAGS
|
||||
# F_FLAGS
|
||||
-fmerge-all-constants
|
||||
-fno-builtin-fwrite
|
||||
-fno-builtin-fread
|
||||
set(JPEGXL_INTERNAL_FLAGS
|
||||
# F_FLAGS
|
||||
-fmerge-all-constants
|
||||
-fno-builtin-fwrite
|
||||
-fno-builtin-fread
|
||||
|
||||
# WARN_FLAGS
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wc++11-compat
|
||||
-Warray-bounds
|
||||
-Wformat-security
|
||||
-Wimplicit-fallthrough
|
||||
-Wno-register # Needed by public headers in lcms
|
||||
-Wno-unused-function
|
||||
-Wno-unused-parameter
|
||||
-Wnon-virtual-dtor
|
||||
-Woverloaded-virtual
|
||||
-Wvla
|
||||
)
|
||||
|
||||
# Warning flags supported by clang.
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-Wdeprecated-increment-bool
|
||||
# TODO(deymo): Add -Wextra-semi once we update third_party/highway.
|
||||
# -Wextra-semi
|
||||
-Wfloat-overflow-conversion
|
||||
-Wfloat-zero-conversion
|
||||
-Wfor-loop-analysis
|
||||
-Wgnu-redeclared-enum
|
||||
-Winfinite-recursion
|
||||
-Wliteral-conversion
|
||||
-Wno-c++98-compat
|
||||
-Wno-unused-command-line-argument
|
||||
-Wprivate-header
|
||||
-Wself-assign
|
||||
-Wstring-conversion
|
||||
-Wtautological-overlap-compare
|
||||
-Wthread-safety-analysis
|
||||
-Wundefined-func-template
|
||||
-Wunreachable-code
|
||||
-Wunused-comparison
|
||||
)
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 5.0)
|
||||
list(APPEND HWY_FLAGS -Wc++2a-extensions)
|
||||
endif()
|
||||
endif() # Clang
|
||||
|
||||
if (WIN32)
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-Wno-cast-align
|
||||
-Wno-double-promotion
|
||||
-Wno-float-equal
|
||||
-Wno-format-nonliteral
|
||||
-Wno-shadow
|
||||
-Wno-sign-conversion
|
||||
-Wno-zero-as-null-pointer-constant
|
||||
# WARN_FLAGS
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wc++11-compat
|
||||
-Warray-bounds
|
||||
-Wformat-security
|
||||
-Wimplicit-fallthrough
|
||||
-Wno-register # Needed by public headers in lcms
|
||||
-Wno-unused-function
|
||||
-Wno-unused-parameter
|
||||
-Wnon-virtual-dtor
|
||||
-Woverloaded-virtual
|
||||
-Wvla
|
||||
)
|
||||
|
||||
# Warning flags supported by clang.
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-Wno-used-but-marked-unused
|
||||
-Wno-unused-template
|
||||
-Wno-unused-member-function
|
||||
-Wno-shadow-field-in-constructor
|
||||
-Wno-language-extension-token
|
||||
-Wno-global-constructors
|
||||
-Wno-c++98-compat-pedantic
|
||||
-Wdeprecated-increment-bool
|
||||
# TODO(deymo): Add -Wextra-semi once we update third_party/highway.
|
||||
# -Wextra-semi
|
||||
-Wfloat-overflow-conversion
|
||||
-Wfloat-zero-conversion
|
||||
-Wfor-loop-analysis
|
||||
-Wgnu-redeclared-enum
|
||||
-Winfinite-recursion
|
||||
-Wliteral-conversion
|
||||
-Wno-c++98-compat
|
||||
-Wno-unused-command-line-argument
|
||||
-Wprivate-header
|
||||
-Wself-assign
|
||||
-Wstring-conversion
|
||||
-Wtautological-overlap-compare
|
||||
-Wthread-safety-analysis
|
||||
-Wundefined-func-template
|
||||
-Wunreachable-code
|
||||
-Wunused-comparison
|
||||
)
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 5.0)
|
||||
list(APPEND HWY_FLAGS -Wc++2a-extensions)
|
||||
endif()
|
||||
endif() # Clang
|
||||
else() # WIN32
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-fsized-deallocation
|
||||
-fno-exceptions
|
||||
|
||||
# Language flags
|
||||
-fmath-errno
|
||||
)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
if (WIN32)
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-fnew-alignment=8
|
||||
-fno-cxx-exceptions
|
||||
-fno-slp-vectorize
|
||||
-fno-vectorize
|
||||
|
||||
-disable-free
|
||||
-disable-llvm-verifier
|
||||
-Wno-cast-align
|
||||
-Wno-double-promotion
|
||||
-Wno-float-equal
|
||||
-Wno-format-nonliteral
|
||||
-Wno-shadow
|
||||
-Wno-sign-conversion
|
||||
-Wno-zero-as-null-pointer-constant
|
||||
)
|
||||
endif() # Clang
|
||||
endif() # WIN32
|
||||
|
||||
# Internal flags for coverage builds:
|
||||
if(JPEGXL_ENABLE_COVERAGE)
|
||||
set(JPEGXL_COVERAGE_FLAGS
|
||||
-g -O0 -fprofile-arcs -ftest-coverage
|
||||
-DJXL_ENABLE_ASSERT=0 -DJXL_ENABLE_CHECK=0
|
||||
)
|
||||
endif() # JPEGXL_ENABLE_COVERAGE
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-Wno-used-but-marked-unused
|
||||
-Wno-unused-template
|
||||
-Wno-unused-member-function
|
||||
-Wno-shadow-field-in-constructor
|
||||
-Wno-language-extension-token
|
||||
-Wno-global-constructors
|
||||
-Wno-c++98-compat-pedantic
|
||||
)
|
||||
endif() # Clang
|
||||
else() # WIN32
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-fsized-deallocation
|
||||
-fno-exceptions
|
||||
|
||||
# Language flags
|
||||
-fmath-errno
|
||||
)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-fnew-alignment=8
|
||||
-fno-cxx-exceptions
|
||||
-fno-slp-vectorize
|
||||
-fno-vectorize
|
||||
|
||||
-disable-free
|
||||
-disable-llvm-verifier
|
||||
)
|
||||
endif() # Clang
|
||||
endif() # WIN32
|
||||
endif() #!MSVC
|
||||
|
||||
# strips the -static suffix from all the elements in LIST
|
||||
|
@ -162,9 +154,6 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/jxl
|
|||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/jxl
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
|
||||
# Profiler for libjxl
|
||||
include(jxl_profiler.cmake)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
cmake_policy(SET CMP0057 NEW) # https://gitlab.kitware.com/cmake/cmake/issues/18198
|
||||
include(GoogleTest)
|
||||
|
|
|
@ -11,6 +11,18 @@ function(jxl_discover_tests TESTNAME)
|
|||
endif ()
|
||||
endfunction()
|
||||
|
||||
function(jxl_link_libraries DST SRC)
|
||||
if (CMAKE_VERSION VERSION_LESS "3.13.5")
|
||||
target_include_directories(${DST} SYSTEM PUBLIC
|
||||
$<BUILD_INTERFACE:$<TARGET_PROPERTY:${SRC},INTERFACE_SYSTEM_INCLUDE_DIRECTORIES>>
|
||||
)
|
||||
add_dependencies(${DST} ${SRC})
|
||||
else()
|
||||
target_link_libraries(${DST} PUBLIC ${SRC})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
|
||||
if (CMAKE_VERSION VERSION_LESS "3.12.4")
|
||||
set(JXL_HWY_INCLUDE_DIRS "$<BUILD_INTERFACE:$<TARGET_PROPERTY:hwy,INTERFACE_INCLUDE_DIRECTORIES>>")
|
||||
else()
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
|
||||
#include "lib/extras/codec.h"
|
||||
|
||||
#include "jxl/decode.h"
|
||||
#include "jxl/types.h"
|
||||
#include <jxl/decode.h>
|
||||
#include <jxl/types.h>
|
||||
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
@ -38,12 +39,12 @@ constexpr size_t kMinBytes = 9;
|
|||
|
||||
Status SetFromBytes(const Span<const uint8_t> bytes,
|
||||
const extras::ColorHints& color_hints, CodecInOut* io,
|
||||
ThreadPool* pool, extras::Codec* orig_codec) {
|
||||
ThreadPool* pool, const SizeConstraints* constraints,
|
||||
extras::Codec* orig_codec) {
|
||||
if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes");
|
||||
|
||||
extras::PackedPixelFile ppf;
|
||||
if (extras::DecodeBytes(bytes, color_hints, io->constraints, &ppf,
|
||||
orig_codec)) {
|
||||
if (extras::DecodeBytes(bytes, color_hints, &ppf, constraints, orig_codec)) {
|
||||
return ConvertPackedPixelFileToCodecInOut(ppf, pool, io);
|
||||
}
|
||||
return JXL_FAILURE("Codecs failed to decode");
|
||||
|
@ -51,11 +52,12 @@ Status SetFromBytes(const Span<const uint8_t> bytes,
|
|||
|
||||
Status SetFromFile(const std::string& pathname,
|
||||
const extras::ColorHints& color_hints, CodecInOut* io,
|
||||
ThreadPool* pool, extras::Codec* orig_codec) {
|
||||
ThreadPool* pool, const SizeConstraints* constraints,
|
||||
extras::Codec* orig_codec) {
|
||||
std::vector<uint8_t> encoded;
|
||||
JXL_RETURN_IF_ERROR(ReadFile(pathname, &encoded));
|
||||
JXL_RETURN_IF_ERROR(SetFromBytes(Span<const uint8_t>(encoded), color_hints,
|
||||
io, pool, orig_codec));
|
||||
io, pool, constraints, orig_codec));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -132,6 +134,7 @@ Status Encode(const CodecInOut& io, const extras::Codec codec,
|
|||
ConvertCodecInOutToPackedPixelFile(io, format, c_desired, pool, &ppf));
|
||||
ppf.info.bits_per_sample = bits_per_sample;
|
||||
if (format.data_type == JXL_TYPE_FLOAT) {
|
||||
ppf.info.bits_per_sample = 32;
|
||||
ppf.info.exponent_bits_per_sample = 8;
|
||||
}
|
||||
extras::EncodedImage encoded_image;
|
||||
|
|
|
@ -26,23 +26,29 @@
|
|||
|
||||
namespace jxl {
|
||||
|
||||
struct SizeConstraints;
|
||||
|
||||
// Decodes "bytes" and sets io->metadata.m.
|
||||
// color_space_hint may specify the color space, otherwise, defaults to sRGB.
|
||||
Status SetFromBytes(Span<const uint8_t> bytes,
|
||||
const extras::ColorHints& color_hints, CodecInOut* io,
|
||||
ThreadPool* pool = nullptr,
|
||||
const SizeConstraints* constraints = nullptr,
|
||||
extras::Codec* orig_codec = nullptr);
|
||||
// Helper function to use no color_space_hint.
|
||||
JXL_INLINE Status SetFromBytes(const Span<const uint8_t> bytes, CodecInOut* io,
|
||||
ThreadPool* pool = nullptr,
|
||||
const SizeConstraints* constraints = nullptr,
|
||||
extras::Codec* orig_codec = nullptr) {
|
||||
return SetFromBytes(bytes, extras::ColorHints(), io, pool, orig_codec);
|
||||
return SetFromBytes(bytes, extras::ColorHints(), io, pool, constraints,
|
||||
orig_codec);
|
||||
}
|
||||
|
||||
// Reads from file and calls SetFromBytes.
|
||||
Status SetFromFile(const std::string& pathname,
|
||||
const extras::ColorHints& color_hints, CodecInOut* io,
|
||||
ThreadPool* pool = nullptr,
|
||||
const SizeConstraints* constraints = nullptr,
|
||||
extras::Codec* orig_codec = nullptr);
|
||||
|
||||
// Replaces "bytes" with an encoding of pixels transformed from c_current
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
#include "lib/extras/enc/encode.h"
|
||||
#include "lib/extras/packed_image_convert.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_butteraugli_comparator.h"
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
|
@ -31,6 +30,9 @@
|
|||
#include "lib/jxl/testing.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
using test::ThreadPoolForTests;
|
||||
|
||||
namespace extras {
|
||||
namespace {
|
||||
|
||||
|
@ -275,7 +277,7 @@ void TestRoundTrip(const TestImageParams& params, ThreadPool* pool) {
|
|||
params.is_gray ? "Gra_D65_Rel_SRG" : "RGB_D65_SRG_Rel_SRG");
|
||||
}
|
||||
ASSERT_TRUE(DecodeBytes(Span<const uint8_t>(encoded.bitstreams[0]),
|
||||
color_hints, SizeConstraints(), &ppf_out));
|
||||
color_hints, &ppf_out));
|
||||
if (params.codec == Codec::kPNG && ppf_out.icc.empty()) {
|
||||
// Decoding a PNG may drop the ICC profile if there's a valid cICP chunk.
|
||||
// Rendering intent is not preserved in this case.
|
||||
|
@ -315,7 +317,7 @@ void TestRoundTrip(const TestImageParams& params, ThreadPool* pool) {
|
|||
}
|
||||
|
||||
TEST(CodecTest, TestRoundTrip) {
|
||||
ThreadPoolInternal pool(12);
|
||||
ThreadPoolForTests pool(12);
|
||||
|
||||
TestImageParams params;
|
||||
params.xsize = 7;
|
||||
|
@ -345,7 +347,7 @@ TEST(CodecTest, TestRoundTrip) {
|
|||
}
|
||||
|
||||
TEST(CodecTest, LosslessPNMRoundtrip) {
|
||||
ThreadPoolInternal pool(12);
|
||||
ThreadPoolForTests pool(12);
|
||||
|
||||
static const char* kChannels[] = {"", "g", "ga", "rgb", "rgba"};
|
||||
static const char* kExtension[] = {"", ".pgm", ".pam", ".ppm", ".pam"};
|
||||
|
@ -363,7 +365,7 @@ TEST(CodecTest, LosslessPNMRoundtrip) {
|
|||
color_hints.Add("color_space",
|
||||
channels < 3 ? "Gra_D65_Rel_SRG" : "RGB_D65_SRG_Rel_SRG");
|
||||
ASSERT_TRUE(DecodeBytes(Span<const uint8_t>(orig.data(), orig.size()),
|
||||
color_hints, SizeConstraints(), &ppf));
|
||||
color_hints, &ppf));
|
||||
|
||||
EncodedImage encoded;
|
||||
auto encoder = Encoder::FromExtension(extension);
|
||||
|
@ -381,8 +383,7 @@ void DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
|
|||
CodecInOut& io,
|
||||
const ColorHints& color_hints = ColorHints()) {
|
||||
const PaddedBytes orig = jxl::test::ReadTestData(pathname);
|
||||
JXL_CHECK(
|
||||
SetFromBytes(Span<const uint8_t>(orig), color_hints, &io, pool, nullptr));
|
||||
JXL_CHECK(SetFromBytes(Span<const uint8_t>(orig), color_hints, &io, pool));
|
||||
const ImageBundle& ib1 = io.Main();
|
||||
|
||||
// Encode/Decode again to make sure Encode carries through all metadata.
|
||||
|
@ -391,8 +392,8 @@ void DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
|
|||
io.metadata.m.bit_depth.bits_per_sample, &encoded, pool));
|
||||
|
||||
CodecInOut io2;
|
||||
JXL_CHECK(SetFromBytes(Span<const uint8_t>(encoded), color_hints, &io2, pool,
|
||||
nullptr));
|
||||
JXL_CHECK(
|
||||
SetFromBytes(Span<const uint8_t>(encoded), color_hints, &io2, pool));
|
||||
const ImageBundle& ib2 = io2.Main();
|
||||
EXPECT_EQ(Description(ib1.metadata()->color_encoding),
|
||||
Description(ib2.metadata()->color_encoding));
|
||||
|
@ -422,7 +423,7 @@ void DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
|
|||
|
||||
#if 0
|
||||
TEST(CodecTest, TestMetadataSRGB) {
|
||||
ThreadPoolInternal pool(12);
|
||||
ThreadPoolForTests pool(12);
|
||||
|
||||
const char* paths[] = {"external/raw.pixls/DJI-FC6310-16bit_srgb8_v4_krita.png",
|
||||
"external/raw.pixls/Google-Pixel2XL-16bit_srgb8_v4_krita.png",
|
||||
|
@ -450,7 +451,7 @@ TEST(CodecTest, TestMetadataSRGB) {
|
|||
}
|
||||
|
||||
TEST(CodecTest, TestMetadataLinear) {
|
||||
ThreadPoolInternal pool(12);
|
||||
ThreadPoolForTests pool(12);
|
||||
|
||||
const char* paths[3] = {
|
||||
"external/raw.pixls/Google-Pixel2XL-16bit_acescg_g1_v4_krita.png",
|
||||
|
@ -483,7 +484,7 @@ TEST(CodecTest, TestMetadataLinear) {
|
|||
}
|
||||
|
||||
TEST(CodecTest, TestMetadataICC) {
|
||||
ThreadPoolInternal pool(12);
|
||||
ThreadPoolForTests pool(12);
|
||||
|
||||
const char* paths[] = {
|
||||
"external/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png",
|
||||
|
@ -510,7 +511,7 @@ TEST(CodecTest, TestMetadataICC) {
|
|||
}
|
||||
|
||||
TEST(CodecTest, Testexternal/pngsuite) {
|
||||
ThreadPoolInternal pool(12);
|
||||
ThreadPoolForTests pool(12);
|
||||
|
||||
// Ensure we can load PNG with text, japanese UTF-8, compressed text.
|
||||
CodecInOut tmp1;
|
||||
|
@ -556,7 +557,7 @@ void VerifyWideGamutMetadata(const std::string& relative_pathname,
|
|||
}
|
||||
|
||||
TEST(CodecTest, TestWideGamut) {
|
||||
ThreadPoolInternal pool(12);
|
||||
ThreadPoolForTests pool(12);
|
||||
// VerifyWideGamutMetadata("external/wide-gamut-tests/P3-sRGB-color-bars.png",
|
||||
// Primaries::kP3, &pool);
|
||||
VerifyWideGamutMetadata("external/wide-gamut-tests/P3-sRGB-color-ring.png",
|
||||
|
@ -614,7 +615,7 @@ TEST(CodecTest, EncodeToPNG) {
|
|||
"external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
|
||||
PackedPixelFile ppf;
|
||||
ASSERT_TRUE(extras::DecodeBytes(Span<const uint8_t>(original_png),
|
||||
ColorHints(), SizeConstraints(), &ppf));
|
||||
ColorHints(), &ppf));
|
||||
|
||||
const JxlPixelFormat& format = ppf.frames.front().color.format;
|
||||
ASSERT_THAT(
|
||||
|
@ -630,7 +631,7 @@ TEST(CodecTest, EncodeToPNG) {
|
|||
PackedPixelFile decoded_ppf;
|
||||
ASSERT_TRUE(
|
||||
extras::DecodeBytes(Span<const uint8_t>(encoded_png.bitstreams.front()),
|
||||
ColorHints(), SizeConstraints(), &decoded_ppf));
|
||||
ColorHints(), &decoded_ppf));
|
||||
|
||||
ASSERT_EQ(decoded_ppf.info.bits_per_sample, ppf.info.bits_per_sample);
|
||||
ASSERT_EQ(decoded_ppf.frames.size(), 1);
|
||||
|
|
|
@ -36,6 +36,8 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#include <jxl/codestream_header.h>
|
||||
#include <jxl/encode.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
@ -43,15 +45,13 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/codestream_header.h"
|
||||
#include "jxl/encode.h"
|
||||
#include "lib/extras/size_constraints.h"
|
||||
#include "lib/jxl/base/byte_order.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
#include "lib/jxl/base/scope_guard.h"
|
||||
#include "lib/jxl/common.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
#include "lib/jxl/size_constraints.h"
|
||||
#include "png.h" /* original (unpatched) libpng is ok */
|
||||
|
||||
namespace jxl {
|
||||
|
@ -491,6 +491,12 @@ int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr,
|
|||
std::vector<std::vector<uint8_t>>& chunksInfo) {
|
||||
unsigned char header[8] = {137, 80, 78, 71, 13, 10, 26, 10};
|
||||
|
||||
// Cleanup prior decoder, if any.
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
// Just in case. Not all versions on libpng wipe-out the pointers.
|
||||
png_ptr = nullptr;
|
||||
info_ptr = nullptr;
|
||||
|
||||
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;
|
||||
|
@ -554,9 +560,8 @@ int processing_finish(png_structp png_ptr, png_infop info_ptr,
|
|||
} // namespace
|
||||
|
||||
Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
const ColorHints& color_hints, PackedPixelFile* ppf,
|
||||
const SizeConstraints* constraints) {
|
||||
Reader r;
|
||||
unsigned int id, j, w, h, w0, h0, x0, y0;
|
||||
unsigned int delay_num, delay_den, dop, bop, rowbytes, imagesize;
|
||||
|
@ -699,7 +704,8 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
|||
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);
|
||||
int png_bit_depth = png_get_bit_depth(png_ptr, info_ptr);
|
||||
ppf->info.bits_per_sample = png_bit_depth;
|
||||
png_color_8p sigbits = NULL;
|
||||
png_get_sBIT(png_ptr, info_ptr, &sigbits);
|
||||
if (colortype & 1) {
|
||||
|
@ -735,7 +741,7 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
|||
: JXL_COLOR_SPACE_RGB);
|
||||
ppf->info.xsize = w;
|
||||
ppf->info.ysize = h;
|
||||
JXL_RETURN_IF_ERROR(VerifyDimensions(&constraints, w, h));
|
||||
JXL_RETURN_IF_ERROR(VerifyDimensions(constraints, w, h));
|
||||
num_channels =
|
||||
ppf->info.num_color_channels + (ppf->info.alpha_bits ? 1 : 0);
|
||||
format = {
|
||||
|
@ -745,6 +751,9 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
|||
/*endianness=*/JXL_BIG_ENDIAN,
|
||||
/*align=*/0,
|
||||
};
|
||||
if (png_bit_depth > 8 && format.data_type == JXL_TYPE_UINT8) {
|
||||
png_set_strip_16(png_ptr);
|
||||
}
|
||||
bytes_per_pixel =
|
||||
num_channels * (format.data_type == JXL_TYPE_UINT16 ? 2 : 1);
|
||||
rowbytes = w * bytes_per_pixel;
|
||||
|
|
|
@ -16,15 +16,17 @@
|
|||
#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 {
|
||||
|
||||
struct SizeConstraints;
|
||||
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `ppf`.
|
||||
Status DecodeImageAPNG(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf);
|
||||
PackedPixelFile* ppf,
|
||||
const SizeConstraints* constraints = nullptr);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
#ifndef LIB_EXTRAS_COLOR_DESCRIPTION_H_
|
||||
#define LIB_EXTRAS_COLOR_DESCRIPTION_H_
|
||||
|
||||
#include <jxl/color_encoding.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "jxl/color_encoding.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
|
||||
#include "jxl/encode.h"
|
||||
#include <jxl/encode.h>
|
||||
|
||||
#include "lib/extras/dec/color_description.h"
|
||||
#include "lib/jxl/base/file_io.h"
|
||||
|
||||
|
@ -51,7 +52,6 @@ Status ApplyColorHints(const ColorHints& color_hints,
|
|||
}));
|
||||
|
||||
if (!got_color_space) {
|
||||
JXL_WARNING("No color_space/icc_pathname given, assuming sRGB");
|
||||
ppf->color_encoding.color_space =
|
||||
is_gray ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
|
||||
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
|
||||
|
|
|
@ -79,9 +79,8 @@ Codec CodecFromExtension(std::string extension,
|
|||
}
|
||||
|
||||
Status DecodeBytes(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
extras::PackedPixelFile* ppf, Codec* orig_codec) {
|
||||
const ColorHints& color_hints, extras::PackedPixelFile* ppf,
|
||||
const SizeConstraints* constraints, Codec* orig_codec) {
|
||||
if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes");
|
||||
|
||||
*ppf = extras::PackedPixelFile();
|
||||
|
@ -92,28 +91,28 @@ Status DecodeBytes(const Span<const uint8_t> bytes,
|
|||
|
||||
const auto choose_codec = [&]() -> Codec {
|
||||
#if JPEGXL_ENABLE_APNG
|
||||
if (DecodeImageAPNG(bytes, color_hints, constraints, ppf)) {
|
||||
if (DecodeImageAPNG(bytes, color_hints, ppf, constraints)) {
|
||||
return Codec::kPNG;
|
||||
}
|
||||
#endif
|
||||
if (DecodeImagePGX(bytes, color_hints, constraints, ppf)) {
|
||||
if (DecodeImagePGX(bytes, color_hints, ppf, constraints)) {
|
||||
return Codec::kPGX;
|
||||
}
|
||||
if (DecodeImagePNM(bytes, color_hints, constraints, ppf)) {
|
||||
if (DecodeImagePNM(bytes, color_hints, ppf, constraints)) {
|
||||
return Codec::kPNM;
|
||||
}
|
||||
#if JPEGXL_ENABLE_GIF
|
||||
if (DecodeImageGIF(bytes, color_hints, constraints, ppf)) {
|
||||
if (DecodeImageGIF(bytes, color_hints, ppf, constraints)) {
|
||||
return Codec::kGIF;
|
||||
}
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
if (DecodeImageJPG(bytes, color_hints, constraints, ppf)) {
|
||||
if (DecodeImageJPG(bytes, color_hints, ppf, constraints)) {
|
||||
return Codec::kJPG;
|
||||
}
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_EXR
|
||||
if (DecodeImageEXR(bytes, color_hints, constraints, ppf)) {
|
||||
if (DecodeImageEXR(bytes, color_hints, ppf, constraints)) {
|
||||
return Codec::kEXR;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -17,9 +17,11 @@
|
|||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
struct SizeConstraints;
|
||||
|
||||
namespace extras {
|
||||
|
||||
// Codecs supported by CodecInOut::Encode.
|
||||
|
@ -43,8 +45,9 @@ Codec CodecFromExtension(std::string extension,
|
|||
// Decodes "bytes" info *ppf.
|
||||
// color_space_hint may specify the color space, otherwise, defaults to sRGB.
|
||||
Status DecodeBytes(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
extras::PackedPixelFile* ppf, Codec* orig_codec = nullptr);
|
||||
extras::PackedPixelFile* ppf,
|
||||
const SizeConstraints* constraints = nullptr,
|
||||
Codec* orig_codec = nullptr);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
|
|
@ -62,8 +62,8 @@ class InMemoryIStream : public OpenEXR::IStream {
|
|||
} // namespace
|
||||
|
||||
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
PackedPixelFile* ppf,
|
||||
const SizeConstraints* constraints) {
|
||||
InMemoryIStream is(bytes);
|
||||
|
||||
#ifdef __EXCEPTIONS
|
||||
|
|
|
@ -14,14 +14,17 @@
|
|||
#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 {
|
||||
|
||||
struct SizeConstraints;
|
||||
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `ppf`. color_hints are ignored.
|
||||
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, PackedPixelFile* ppf);
|
||||
PackedPixelFile* ppf,
|
||||
const SizeConstraints* constraints = nullptr);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
|
|
@ -6,16 +6,16 @@
|
|||
#include "lib/extras/dec/gif.h"
|
||||
|
||||
#include <gif_lib.h>
|
||||
#include <jxl/codestream_header.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/codestream_header.h"
|
||||
#include "lib/extras/size_constraints.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
#include "lib/jxl/size_constraints.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
@ -57,8 +57,8 @@ void ensure_have_alpha(PackedFrame* frame) {
|
|||
} // namespace
|
||||
|
||||
Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
PackedPixelFile* ppf,
|
||||
const SizeConstraints* constraints) {
|
||||
int error = GIF_OK;
|
||||
ReadState state = {bytes};
|
||||
const auto ReadFromSpan = [](GifFileType* const gif, GifByteType* const bytes,
|
||||
|
@ -97,20 +97,20 @@ Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
|||
sizeof(*gif->SavedImages) * gif->ImageCount);
|
||||
|
||||
JXL_RETURN_IF_ERROR(
|
||||
VerifyDimensions<uint32_t>(&constraints, gif->SWidth, gif->SHeight));
|
||||
VerifyDimensions<uint32_t>(constraints, gif->SWidth, gif->SHeight));
|
||||
uint64_t total_pixel_count =
|
||||
static_cast<uint64_t>(gif->SWidth) * gif->SHeight;
|
||||
for (int i = 0; i < gif->ImageCount; ++i) {
|
||||
const SavedImage& image = gif->SavedImages[i];
|
||||
uint32_t w = image.ImageDesc.Width;
|
||||
uint32_t h = image.ImageDesc.Height;
|
||||
JXL_RETURN_IF_ERROR(VerifyDimensions<uint32_t>(&constraints, w, h));
|
||||
JXL_RETURN_IF_ERROR(VerifyDimensions<uint32_t>(constraints, w, h));
|
||||
uint64_t pixel_count = static_cast<uint64_t>(w) * h;
|
||||
if (total_pixel_count + pixel_count < total_pixel_count) {
|
||||
return JXL_FAILURE("Image too big");
|
||||
}
|
||||
total_pixel_count += pixel_count;
|
||||
if (total_pixel_count > constraints.dec_max_pixels) {
|
||||
if (constraints && (total_pixel_count > constraints->dec_max_pixels)) {
|
||||
return JXL_FAILURE("Image too big");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,14 +15,17 @@
|
|||
#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 {
|
||||
|
||||
struct SizeConstraints;
|
||||
|
||||
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);
|
||||
PackedPixelFile* ppf,
|
||||
const SizeConstraints* constraints = nullptr);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
|
|
@ -82,6 +82,30 @@ JpegliDataType ConvertDataType(JxlDataType type) {
|
|||
}
|
||||
}
|
||||
|
||||
JpegliEndianness ConvertEndianness(JxlEndianness type) {
|
||||
switch (type) {
|
||||
case JXL_NATIVE_ENDIAN:
|
||||
return JPEGLI_NATIVE_ENDIAN;
|
||||
case JXL_BIG_ENDIAN:
|
||||
return JPEGLI_BIG_ENDIAN;
|
||||
case JXL_LITTLE_ENDIAN:
|
||||
return JPEGLI_LITTLE_ENDIAN;
|
||||
default:
|
||||
return JPEGLI_NATIVE_ENDIAN;
|
||||
}
|
||||
}
|
||||
|
||||
JxlColorSpace ConvertColorSpace(J_COLOR_SPACE colorspace) {
|
||||
switch (colorspace) {
|
||||
case JCS_GRAYSCALE:
|
||||
return JXL_COLOR_SPACE_GRAY;
|
||||
case JCS_RGB:
|
||||
return JXL_COLOR_SPACE_RGB;
|
||||
default:
|
||||
return JXL_COLOR_SPACE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
void MyErrorExit(j_common_ptr cinfo) {
|
||||
jmp_buf* env = static_cast<jmp_buf*>(cinfo->client_data);
|
||||
(*cinfo->err->output_message)(cinfo);
|
||||
|
@ -98,10 +122,23 @@ void MyOutputMessage(j_common_ptr cinfo) {
|
|||
#endif
|
||||
}
|
||||
|
||||
void UnmapColors(uint8_t* row, size_t xsize, int components,
|
||||
JSAMPARRAY colormap, size_t num_colors) {
|
||||
JXL_CHECK(colormap != nullptr);
|
||||
std::vector<uint8_t> tmp(xsize * components);
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
JXL_CHECK(row[x] < num_colors);
|
||||
for (int c = 0; c < components; ++c) {
|
||||
tmp[x * components + c] = colormap[c][row[x]];
|
||||
}
|
||||
}
|
||||
memcpy(row, tmp.data(), tmp.size());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeJpeg(const std::vector<uint8_t>& compressed,
|
||||
JxlDataType output_data_type, ThreadPool* pool,
|
||||
const JpegDecompressParams& dparams, ThreadPool* pool,
|
||||
PackedPixelFile* ppf) {
|
||||
// Don't do anything for non-JPEG files (no need to report an error)
|
||||
if (!IsJPG(compressed)) return false;
|
||||
|
@ -112,11 +149,8 @@ Status DecodeJpeg(const std::vector<uint8_t>& compressed,
|
|||
// the call to setjmp().
|
||||
std::unique_ptr<JSAMPLE[]> row;
|
||||
|
||||
jpeg_decompress_struct cinfo;
|
||||
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;
|
||||
|
@ -149,13 +183,16 @@ Status DecodeJpeg(const std::vector<uint8_t>& compressed,
|
|||
if (nbcomp != 1 && nbcomp != 3) {
|
||||
return failure("unsupported number of components in JPEG");
|
||||
}
|
||||
if (dparams.force_rgb) {
|
||||
cinfo.out_color_space = JCS_RGB;
|
||||
} else if (dparams.force_grayscale) {
|
||||
cinfo.out_color_space = JCS_GRAYSCALE;
|
||||
}
|
||||
if (!ReadICCProfile(&cinfo, &ppf->icc)) {
|
||||
ppf->icc.clear();
|
||||
// Default to SRGB
|
||||
// Actually, (cinfo.output_components == nbcomp) will be checked after
|
||||
// `jpegli_start_decompress`.
|
||||
ppf->color_encoding.color_space =
|
||||
(nbcomp == 1) ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
|
||||
ConvertColorSpace(cinfo.out_color_space);
|
||||
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;
|
||||
|
@ -165,39 +202,50 @@ Status DecodeJpeg(const std::vector<uint8_t>& compressed,
|
|||
|
||||
ppf->info.xsize = cinfo.image_width;
|
||||
ppf->info.ysize = cinfo.image_height;
|
||||
if (output_data_type == JXL_TYPE_UINT8) {
|
||||
if (dparams.output_data_type == JXL_TYPE_UINT8) {
|
||||
ppf->info.bits_per_sample = 8;
|
||||
} else if (output_data_type == JXL_TYPE_UINT16) {
|
||||
ppf->info.exponent_bits_per_sample = 0;
|
||||
} else if (dparams.output_data_type == JXL_TYPE_UINT16) {
|
||||
ppf->info.bits_per_sample = 16;
|
||||
ppf->info.exponent_bits_per_sample = 0;
|
||||
} else if (dparams.output_data_type == JXL_TYPE_FLOAT) {
|
||||
ppf->info.bits_per_sample = 32;
|
||||
ppf->info.exponent_bits_per_sample = 8;
|
||||
} else {
|
||||
return failure("unsupported data type");
|
||||
}
|
||||
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;
|
||||
|
||||
jpegli_set_output_format(&cinfo, ConvertDataType(output_data_type),
|
||||
JPEGLI_NATIVE_ENDIAN);
|
||||
jpegli_start_decompress(&cinfo);
|
||||
JXL_ASSERT(cinfo.output_components == nbcomp);
|
||||
jpegli_set_output_format(&cinfo, ConvertDataType(dparams.output_data_type),
|
||||
ConvertEndianness(dparams.output_endianness));
|
||||
|
||||
if (dparams.num_colors > 0) {
|
||||
cinfo.quantize_colors = TRUE;
|
||||
cinfo.desired_number_of_colors = dparams.num_colors;
|
||||
cinfo.two_pass_quantize = dparams.two_pass_quant;
|
||||
cinfo.dither_mode = (J_DITHER_MODE)dparams.dither_mode;
|
||||
}
|
||||
|
||||
jpegli_start_decompress(&cinfo);
|
||||
|
||||
ppf->info.num_color_channels = cinfo.out_color_components;
|
||||
const JxlPixelFormat format{
|
||||
/*num_channels=*/static_cast<uint32_t>(nbcomp),
|
||||
output_data_type,
|
||||
/*endianness=*/JXL_NATIVE_ENDIAN,
|
||||
/*num_channels=*/static_cast<uint32_t>(cinfo.out_color_components),
|
||||
dparams.output_data_type,
|
||||
dparams.output_endianness,
|
||||
/*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 <=
|
||||
JXL_ASSERT(sizeof(JSAMPLE) * cinfo.out_color_components *
|
||||
cinfo.image_width <=
|
||||
frame.color.stride);
|
||||
|
||||
for (size_t y = 0; y < cinfo.image_height; ++y) {
|
||||
|
@ -205,16 +253,18 @@ Status DecodeJpeg(const std::vector<uint8_t>& compressed,
|
|||
static_cast<uint8_t*>(frame.color.pixels()) +
|
||||
frame.color.stride * y)};
|
||||
jpegli_read_scanlines(&cinfo, rows, 1);
|
||||
msan::UnpoisonMemory(rows[0], sizeof(JSAMPLE) * cinfo.output_components *
|
||||
cinfo.image_width);
|
||||
if (dparams.num_colors > 0) {
|
||||
UnmapColors(rows[0], cinfo.output_width, cinfo.out_color_components,
|
||||
cinfo.colormap, cinfo.actual_number_of_colors);
|
||||
}
|
||||
}
|
||||
|
||||
jpegli_finish_decompress(&cinfo);
|
||||
jpegli_destroy_decompress(&cinfo);
|
||||
return true;
|
||||
};
|
||||
|
||||
return try_catch_block();
|
||||
bool success = try_catch_block();
|
||||
jpegli_destroy_decompress(&cinfo);
|
||||
return success;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
|
|
|
@ -8,11 +8,11 @@
|
|||
|
||||
// Decodes JPG pixels and metadata in memory using the libjpegli library.
|
||||
|
||||
#include <jxl/types.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/types.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
@ -20,8 +20,19 @@
|
|||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
struct JpegDecompressParams {
|
||||
JxlDataType output_data_type = JXL_TYPE_UINT8;
|
||||
JxlEndianness output_endianness = JXL_NATIVE_ENDIAN;
|
||||
bool force_rgb = false;
|
||||
bool force_grayscale = false;
|
||||
int num_colors = 0;
|
||||
bool two_pass_quant = true;
|
||||
// 0 = none, 1 = ordered, 2 = Floyd-Steinberg
|
||||
int dither_mode = 2;
|
||||
};
|
||||
|
||||
Status DecodeJpeg(const std::vector<uint8_t>& compressed,
|
||||
JxlDataType output_data_type, ThreadPool* pool,
|
||||
const JpegDecompressParams& dparams, ThreadPool* pool,
|
||||
PackedPixelFile* ppf);
|
||||
|
||||
} // namespace extras
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/extras/size_constraints.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
#include "lib/jxl/size_constraints.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
@ -161,12 +161,25 @@ void MyOutputMessage(j_common_ptr cinfo) {
|
|||
#endif
|
||||
}
|
||||
|
||||
void UnmapColors(uint8_t* row, size_t xsize, int components,
|
||||
JSAMPARRAY colormap, size_t num_colors) {
|
||||
JXL_CHECK(colormap != nullptr);
|
||||
std::vector<uint8_t> tmp(xsize * components);
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
JXL_CHECK(row[x] < num_colors);
|
||||
for (int c = 0; c < components; ++c) {
|
||||
tmp[x * components + c] = colormap[c][row[x]];
|
||||
}
|
||||
}
|
||||
memcpy(row, tmp.data(), tmp.size());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeImageJPG(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
const ColorHints& color_hints, PackedPixelFile* ppf,
|
||||
const SizeConstraints* constraints,
|
||||
const JPGDecompressParams* dparams) {
|
||||
// Don't do anything for non-JPEG files (no need to report an error)
|
||||
if (!IsJPG(bytes)) return false;
|
||||
|
||||
|
@ -205,8 +218,7 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes,
|
|||
if (read_header_result == JPEG_SUSPENDED) {
|
||||
return failure("truncated JPEG input");
|
||||
}
|
||||
if (!VerifyDimensions(&constraints, cinfo.image_width,
|
||||
cinfo.image_height)) {
|
||||
if (!VerifyDimensions(constraints, cinfo.image_width, cinfo.image_height)) {
|
||||
return failure("image too big");
|
||||
}
|
||||
// Might cause CPU-zip bomb.
|
||||
|
@ -250,8 +262,15 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes,
|
|||
ppf->info.num_color_channels = nbcomp;
|
||||
ppf->info.orientation = JXL_ORIENT_IDENTITY;
|
||||
|
||||
if (dparams && dparams->num_colors > 0) {
|
||||
cinfo.quantize_colors = TRUE;
|
||||
cinfo.desired_number_of_colors = dparams->num_colors;
|
||||
cinfo.two_pass_quantize = dparams->two_pass_quant;
|
||||
cinfo.dither_mode = (J_DITHER_MODE)dparams->dither_mode;
|
||||
}
|
||||
|
||||
jpeg_start_decompress(&cinfo);
|
||||
JXL_ASSERT(cinfo.output_components == nbcomp);
|
||||
JXL_ASSERT(cinfo.out_color_components == nbcomp);
|
||||
JxlDataType data_type =
|
||||
ppf->info.bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16;
|
||||
|
||||
|
@ -265,9 +284,19 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes,
|
|||
// 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 <=
|
||||
JXL_ASSERT(sizeof(JSAMPLE) * cinfo.out_color_components *
|
||||
cinfo.image_width <=
|
||||
frame.color.stride);
|
||||
|
||||
if (cinfo.quantize_colors) {
|
||||
jxl::msan::UnpoisonMemory(cinfo.colormap, cinfo.out_color_components *
|
||||
sizeof(cinfo.colormap[0]));
|
||||
for (int c = 0; c < cinfo.out_color_components; ++c) {
|
||||
jxl::msan::UnpoisonMemory(
|
||||
cinfo.colormap[c],
|
||||
cinfo.actual_number_of_colors * sizeof(cinfo.colormap[c][0]));
|
||||
}
|
||||
}
|
||||
for (size_t y = 0; y < cinfo.image_height; ++y) {
|
||||
JSAMPROW rows[] = {reinterpret_cast<JSAMPLE*>(
|
||||
static_cast<uint8_t*>(frame.color.pixels()) +
|
||||
|
@ -275,6 +304,10 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes,
|
|||
jpeg_read_scanlines(&cinfo, rows, 1);
|
||||
msan::UnpoisonMemory(rows[0], sizeof(JSAMPLE) * cinfo.output_components *
|
||||
cinfo.image_width);
|
||||
if (dparams && dparams->num_colors > 0) {
|
||||
UnmapColors(rows[0], cinfo.output_width, cinfo.out_color_components,
|
||||
cinfo.colormap, cinfo.actual_number_of_colors);
|
||||
}
|
||||
}
|
||||
|
||||
jpeg_finish_decompress(&cinfo);
|
||||
|
|
|
@ -16,16 +16,27 @@
|
|||
#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 {
|
||||
|
||||
struct SizeConstraints;
|
||||
|
||||
namespace extras {
|
||||
|
||||
struct JPGDecompressParams {
|
||||
int num_colors = 0;
|
||||
bool two_pass_quant = false;
|
||||
// 0 = none, 1 = ordered, 2 = Floyd-Steinberg
|
||||
int dither_mode = 0;
|
||||
};
|
||||
|
||||
// Decodes `bytes` into `ppf`. color_hints are ignored.
|
||||
// `elapsed_deinterleave`, if non-null, will be set to the time (in seconds)
|
||||
// that it took to deinterleave the raw JSAMPLEs to planar floats.
|
||||
Status DecodeImageJPG(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, PackedPixelFile* ppf);
|
||||
PackedPixelFile* ppf,
|
||||
const SizeConstraints* constraints = nullptr,
|
||||
const JPGDecompressParams* dparams = nullptr);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
|
|
@ -5,9 +5,10 @@
|
|||
|
||||
#include "lib/extras/dec/jxl.h"
|
||||
|
||||
#include "jxl/decode.h"
|
||||
#include "jxl/decode_cxx.h"
|
||||
#include "jxl/types.h"
|
||||
#include <jxl/decode.h>
|
||||
#include <jxl/decode_cxx.h>
|
||||
#include <jxl/types.h>
|
||||
|
||||
#include "lib/extras/dec/color_description.h"
|
||||
#include "lib/extras/enc/encode.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
|
||||
// Decodes JPEG XL images in memory.
|
||||
|
||||
#include <jxl/parallel_runner.h>
|
||||
#include <jxl/types.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/parallel_runner.h"
|
||||
#include "jxl/types.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
|
||||
namespace jxl {
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
#include "lib/extras/size_constraints.h"
|
||||
#include "lib/jxl/base/bits.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/size_constraints.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
@ -146,15 +146,14 @@ class Parser {
|
|||
} // namespace
|
||||
|
||||
Status DecodeImagePGX(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
const ColorHints& color_hints, PackedPixelFile* ppf,
|
||||
const SizeConstraints* constraints) {
|
||||
Parser parser(bytes);
|
||||
HeaderPGX header = {};
|
||||
const uint8_t* pos;
|
||||
if (!parser.ParseHeader(&header, &pos)) return false;
|
||||
JXL_RETURN_IF_ERROR(
|
||||
VerifyDimensions(&constraints, header.xsize, header.ysize));
|
||||
VerifyDimensions(constraints, header.xsize, header.ysize));
|
||||
if (header.bits_per_sample == 0 || header.bits_per_sample > 32) {
|
||||
return JXL_FAILURE("PGX: bits_per_sample invalid");
|
||||
}
|
||||
|
|
|
@ -17,14 +17,17 @@
|
|||
#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 {
|
||||
|
||||
struct SizeConstraints;
|
||||
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `ppf`.
|
||||
Status DecodeImagePGX(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, PackedPixelFile* ppf);
|
||||
PackedPixelFile* ppf,
|
||||
const SizeConstraints* constraints = nullptr);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
|
|
@ -24,8 +24,7 @@ TEST(CodecPGXTest, Test8bits) {
|
|||
PackedPixelFile ppf;
|
||||
ThreadPool* pool = nullptr;
|
||||
|
||||
EXPECT_TRUE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(),
|
||||
SizeConstraints(), &ppf));
|
||||
EXPECT_TRUE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(), &ppf));
|
||||
CodecInOut io;
|
||||
EXPECT_TRUE(ConvertPackedPixelFileToCodecInOut(ppf, pool, &io));
|
||||
|
||||
|
@ -52,8 +51,7 @@ TEST(CodecPGXTest, Test16bits) {
|
|||
PackedPixelFile ppf;
|
||||
ThreadPool* pool = nullptr;
|
||||
|
||||
EXPECT_TRUE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(),
|
||||
SizeConstraints(), &ppf));
|
||||
EXPECT_TRUE(DecodeImagePGX(MakeSpan(pgx.c_str()), ColorHints(), &ppf));
|
||||
CodecInOut io;
|
||||
EXPECT_TRUE(ConvertPackedPixelFileToCodecInOut(ppf, pool, &io));
|
||||
|
||||
|
|
|
@ -8,10 +8,12 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "lib/extras/size_constraints.h"
|
||||
#include "lib/jxl/base/bits.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/size_constraints.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
@ -325,15 +327,14 @@ Span<const uint8_t> MakeSpan(const char* str) {
|
|||
} // namespace
|
||||
|
||||
Status DecodeImagePNM(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
const ColorHints& color_hints, PackedPixelFile* ppf,
|
||||
const SizeConstraints* constraints) {
|
||||
Parser parser(bytes);
|
||||
HeaderPNM header = {};
|
||||
const uint8_t* pos = nullptr;
|
||||
if (!parser.ParseHeader(&header, &pos)) return false;
|
||||
JXL_RETURN_IF_ERROR(
|
||||
VerifyDimensions(&constraints, header.xsize, header.ysize));
|
||||
VerifyDimensions(constraints, header.xsize, header.ysize));
|
||||
|
||||
if (header.bits_per_sample == 0 || header.bits_per_sample > 32) {
|
||||
return JXL_FAILURE("PNM: bits_per_sample invalid");
|
||||
|
|
|
@ -20,15 +20,18 @@
|
|||
#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 {
|
||||
|
||||
struct SizeConstraints;
|
||||
|
||||
namespace extras {
|
||||
|
||||
// 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);
|
||||
PackedPixelFile* ppf,
|
||||
const SizeConstraints* constraints = nullptr);
|
||||
|
||||
void TestCodecPNM();
|
||||
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
#include <ImfIO.h>
|
||||
#include <ImfRgbaFile.h>
|
||||
#include <ImfStandardAttributes.h>
|
||||
#include <jxl/codestream_header.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/codestream_header.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/byte_order.h"
|
||||
|
||||
|
@ -110,7 +110,7 @@ Status EncodeImageEXR(const PackedImage& image, const JxlBasicInfo& info,
|
|||
chromaticities.white =
|
||||
Imath::V2f(c_enc.white_point_xy[0], c_enc.white_point_xy[1]);
|
||||
OpenEXR::addChromaticities(header, chromaticities);
|
||||
OpenEXR::addWhiteLuminance(header, 255.0f);
|
||||
OpenEXR::addWhiteLuminance(header, info.intensity_target);
|
||||
|
||||
auto loadFloat =
|
||||
format.endianness == JXL_BIG_ENDIAN ? LoadBEFloat : LoadLEFloat;
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
|
||||
#include "lib/extras/enc/jpegli.h"
|
||||
|
||||
#include <jxl/codestream_header.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "jxl/codestream_header.h"
|
||||
#include "lib/extras/enc/encode.h"
|
||||
#include "lib/jpegli/encode.h"
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
|
@ -105,6 +105,106 @@ Status WriteAppData(j_compress_ptr cinfo,
|
|||
return true;
|
||||
}
|
||||
|
||||
static constexpr int kICCMarker = 0xe2;
|
||||
constexpr unsigned char kICCSignature[12] = {
|
||||
0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00};
|
||||
static constexpr uint8_t kUnknownTf = 2;
|
||||
static constexpr unsigned char kCICPTagSignature[4] = {0x63, 0x69, 0x63, 0x70};
|
||||
static constexpr size_t kCICPTagSize = 12;
|
||||
|
||||
bool FindCICPTag(const uint8_t* icc_data, size_t len, bool is_first_chunk,
|
||||
size_t* cicp_offset, size_t* cicp_length, uint8_t* cicp_tag,
|
||||
size_t* cicp_pos) {
|
||||
if (is_first_chunk) {
|
||||
// Look up the offset of the CICP tag from the first chunk of ICC data.
|
||||
if (len < 132) {
|
||||
return false;
|
||||
}
|
||||
uint32_t tag_count = LoadBE32(&icc_data[128]);
|
||||
if (len < 132 + 12 * tag_count) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < tag_count; ++i) {
|
||||
if (memcmp(&icc_data[132 + 12 * i], kCICPTagSignature, 4) == 0) {
|
||||
*cicp_offset = LoadBE32(&icc_data[136 + 12 * i]);
|
||||
*cicp_length = LoadBE32(&icc_data[140 + 12 * i]);
|
||||
}
|
||||
}
|
||||
if (*cicp_length < kCICPTagSize) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (*cicp_offset < len) {
|
||||
size_t n_bytes = std::min(len - *cicp_offset, kCICPTagSize - *cicp_pos);
|
||||
memcpy(&cicp_tag[*cicp_pos], &icc_data[*cicp_offset], n_bytes);
|
||||
*cicp_pos += n_bytes;
|
||||
*cicp_offset = 0;
|
||||
} else {
|
||||
*cicp_offset -= len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t LookupCICPTransferFunctionFromAppData(const uint8_t* app_data,
|
||||
size_t len) {
|
||||
size_t last_index = 0;
|
||||
size_t cicp_offset = 0;
|
||||
size_t cicp_length = 0;
|
||||
uint8_t cicp_tag[kCICPTagSize] = {};
|
||||
size_t cicp_pos = 0;
|
||||
size_t pos = 0;
|
||||
while (pos < len) {
|
||||
const uint8_t* marker = &app_data[pos];
|
||||
if (pos + 4 > len) {
|
||||
return kUnknownTf;
|
||||
}
|
||||
size_t marker_size = (marker[2] << 8) + marker[3] + 2;
|
||||
if (pos + marker_size > len) {
|
||||
return kUnknownTf;
|
||||
}
|
||||
if (marker_size < 18 || marker[0] != 0xff || marker[1] != kICCMarker ||
|
||||
memcmp(&marker[4], kICCSignature, 12) != 0) {
|
||||
pos += marker_size;
|
||||
continue;
|
||||
}
|
||||
uint8_t index = marker[16];
|
||||
uint8_t total = marker[17];
|
||||
const uint8_t* payload = marker + 18;
|
||||
const size_t payload_size = marker_size - 18;
|
||||
if (index != last_index + 1 || index > total) {
|
||||
return kUnknownTf;
|
||||
}
|
||||
if (!FindCICPTag(payload, payload_size, last_index == 0, &cicp_offset,
|
||||
&cicp_length, &cicp_tag[0], &cicp_pos)) {
|
||||
return kUnknownTf;
|
||||
}
|
||||
if (cicp_pos == kCICPTagSize) {
|
||||
break;
|
||||
}
|
||||
++last_index;
|
||||
}
|
||||
if (cicp_pos >= kCICPTagSize && memcmp(cicp_tag, kCICPTagSignature, 4) == 0) {
|
||||
return cicp_tag[9];
|
||||
}
|
||||
return kUnknownTf;
|
||||
}
|
||||
|
||||
uint8_t LookupCICPTransferFunctionFromICCProfile(const uint8_t* icc_data,
|
||||
size_t len) {
|
||||
size_t cicp_offset = 0;
|
||||
size_t cicp_length = 0;
|
||||
uint8_t cicp_tag[kCICPTagSize] = {};
|
||||
size_t cicp_pos = 0;
|
||||
if (!FindCICPTag(icc_data, len, true, &cicp_offset, &cicp_length,
|
||||
&cicp_tag[0], &cicp_pos)) {
|
||||
return kUnknownTf;
|
||||
}
|
||||
if (cicp_pos >= kCICPTagSize && memcmp(cicp_tag, kCICPTagSignature, 4) == 0) {
|
||||
return cicp_tag[9];
|
||||
}
|
||||
return kUnknownTf;
|
||||
}
|
||||
|
||||
JpegliDataType ConvertDataType(JxlDataType type) {
|
||||
switch (type) {
|
||||
case JXL_TYPE_UINT8:
|
||||
|
@ -165,13 +265,16 @@ Status EncodeJpegToTargetSize(const PackedPixelFile& ppf,
|
|||
const JpegSettings& jpeg_settings,
|
||||
size_t target_size, ThreadPool* pool,
|
||||
std::vector<uint8_t>* output) {
|
||||
float distance = 1.0f;
|
||||
output->clear();
|
||||
size_t best_error = std::numeric_limits<size_t>::max();
|
||||
for (int step = 0; step < 10; ++step) {
|
||||
float distance0 = -1.0f;
|
||||
float distance1 = -1.0f;
|
||||
float distance = 1.0f;
|
||||
for (int step = 0; step < 15; ++step) {
|
||||
JpegSettings settings = jpeg_settings;
|
||||
settings.libjpeg_quality = 0;
|
||||
settings.distance = distance;
|
||||
settings.target_size = 0;
|
||||
std::vector<uint8_t> compressed;
|
||||
JXL_RETURN_IF_ERROR(EncodeJpeg(ppf, settings, pool, &compressed));
|
||||
size_t size = compressed.size();
|
||||
|
@ -184,10 +287,21 @@ Status EncodeJpegToTargetSize(const PackedPixelFile& ppf,
|
|||
std::swap(*output, compressed);
|
||||
}
|
||||
float rel_error = size * 1.0f / target_size;
|
||||
if (std::abs(rel_error - 1.0f) < 0.0005f) {
|
||||
if (std::abs(rel_error - 1.0f) < 0.002f) {
|
||||
break;
|
||||
}
|
||||
distance *= rel_error;
|
||||
if (size < target_size) {
|
||||
distance1 = distance;
|
||||
} else {
|
||||
distance0 = distance;
|
||||
}
|
||||
if (distance1 == -1) {
|
||||
distance *= std::pow(rel_error, 1.5) * 1.05;
|
||||
} else if (distance0 == -1) {
|
||||
distance *= std::pow(rel_error, 1.5) * 0.95;
|
||||
} else {
|
||||
distance = 0.5 * (distance0 + distance1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -209,6 +323,10 @@ Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings,
|
|||
return EncodeJpegToTargetSize(ppf, jpeg_settings, target_size, pool,
|
||||
compressed);
|
||||
}
|
||||
if (jpeg_settings.target_size > 0) {
|
||||
return EncodeJpegToTargetSize(ppf, jpeg_settings, jpeg_settings.target_size,
|
||||
pool, compressed);
|
||||
}
|
||||
JXL_RETURN_IF_ERROR(VerifyInput(ppf));
|
||||
|
||||
ColorEncoding color_encoding;
|
||||
|
@ -269,6 +387,15 @@ Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings,
|
|||
} else if (jpeg_settings.use_std_quant_tables) {
|
||||
jpegli_use_standard_quant_tables(&cinfo);
|
||||
}
|
||||
uint8_t cicp_tf = kUnknownTf;
|
||||
if (!jpeg_settings.app_data.empty()) {
|
||||
cicp_tf = LookupCICPTransferFunctionFromAppData(
|
||||
jpeg_settings.app_data.data(), jpeg_settings.app_data.size());
|
||||
} else if (!output_encoding.IsSRGB()) {
|
||||
cicp_tf = LookupCICPTransferFunctionFromICCProfile(
|
||||
output_encoding.ICC().data(), output_encoding.ICC().size());
|
||||
}
|
||||
jpegli_set_cicp_transfer_function(&cinfo, cicp_tf);
|
||||
jpegli_set_defaults(&cinfo);
|
||||
if (!jpeg_settings.chroma_subsampling.empty()) {
|
||||
if (jpeg_settings.chroma_subsampling == "444") {
|
||||
|
@ -293,13 +420,21 @@ Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings,
|
|||
}
|
||||
jpegli_enable_adaptive_quantization(
|
||||
&cinfo, jpeg_settings.use_adaptive_quantization);
|
||||
jpegli_set_distance(&cinfo, jpeg_settings.distance);
|
||||
jpegli_set_distance(&cinfo, jpeg_settings.distance, TRUE);
|
||||
jpegli_set_progressive_level(&cinfo, jpeg_settings.progressive_level);
|
||||
cinfo.optimize_coding = jpeg_settings.optimize_coding;
|
||||
if (!jpeg_settings.app_data.empty()) {
|
||||
// Make sure jpegli_start_compress() does not write any APP markers.
|
||||
cinfo.write_JFIF_header = false;
|
||||
cinfo.write_Adobe_marker = false;
|
||||
}
|
||||
const PackedImage& image = ppf.frames[0].color;
|
||||
if (jpeg_settings.xyb) {
|
||||
jpegli_set_input_format(&cinfo, JPEGLI_TYPE_FLOAT, JPEGLI_NATIVE_ENDIAN);
|
||||
} else {
|
||||
jpegli_set_input_format(&cinfo, ConvertDataType(image.format.data_type),
|
||||
ConvertEndianness(image.format.endianness));
|
||||
}
|
||||
jpegli_start_compress(&cinfo, TRUE);
|
||||
if (!jpeg_settings.app_data.empty()) {
|
||||
JXL_RETURN_IF_ERROR(WriteAppData(&cinfo, jpeg_settings.app_data));
|
||||
|
@ -309,10 +444,8 @@ Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings,
|
|||
jpegli_write_icc_profile(&cinfo, output_encoding.ICC().data(),
|
||||
output_encoding.ICC().size());
|
||||
}
|
||||
const PackedImage& image = ppf.frames[0].color;
|
||||
const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels());
|
||||
if (jpeg_settings.xyb) {
|
||||
jpegli_set_input_format(&cinfo, JPEGLI_TYPE_FLOAT, JPEGLI_NATIVE_ENDIAN);
|
||||
float* src_buf = c_transform.BufSrc(0);
|
||||
float* dst_buf = c_transform.BufDst(0);
|
||||
for (size_t y = 0; y < image.ysize; ++y) {
|
||||
|
@ -348,8 +481,6 @@ Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings,
|
|||
jpegli_write_scanlines(&cinfo, row, 1);
|
||||
}
|
||||
} else {
|
||||
jpegli_set_input_format(&cinfo, ConvertDataType(image.format.data_type),
|
||||
ConvertEndianness(image.format.endianness));
|
||||
row_bytes.resize(image.stride);
|
||||
for (size_t y = 0; y < info.ysize; ++y) {
|
||||
memcpy(&row_bytes[0], pixels + y * image.stride, image.stride);
|
||||
|
|
|
@ -27,6 +27,7 @@ struct JpegSettings {
|
|||
bool use_adaptive_quantization = true;
|
||||
bool use_std_quant_tables = false;
|
||||
int progressive_level = 2;
|
||||
bool optimize_coding = true;
|
||||
std::string chroma_subsampling;
|
||||
int libjpeg_quality = 0;
|
||||
std::string libjpeg_chroma_subsampling;
|
||||
|
|
|
@ -230,7 +230,8 @@ Status EncodeWithLibJpeg(const PackedImage& image, const JxlBasicInfo& info,
|
|||
const std::vector<uint8_t>& icc,
|
||||
std::vector<uint8_t> exif, size_t quality,
|
||||
const std::string& chroma_subsampling,
|
||||
int progressive_id, std::vector<uint8_t>* bytes) {
|
||||
int progressive_id, bool optimize_coding,
|
||||
std::vector<uint8_t>* bytes) {
|
||||
if (BITS_IN_JSAMPLE != 8 || sizeof(JSAMPLE) != 1) {
|
||||
return JXL_FAILURE("Only 8 bit JSAMPLE is supported.");
|
||||
}
|
||||
|
@ -246,7 +247,7 @@ Status EncodeWithLibJpeg(const PackedImage& image, const JxlBasicInfo& info,
|
|||
cinfo.input_components = info.num_color_channels;
|
||||
cinfo.in_color_space = info.num_color_channels == 1 ? JCS_GRAYSCALE : JCS_RGB;
|
||||
jpeg_set_defaults(&cinfo);
|
||||
cinfo.optimize_coding = TRUE;
|
||||
cinfo.optimize_coding = optimize_coding;
|
||||
if (cinfo.input_components == 3) {
|
||||
JXL_RETURN_IF_ERROR(SetChromaSubsampling(chroma_subsampling, &cinfo));
|
||||
}
|
||||
|
@ -327,8 +328,8 @@ Status EncodeImageJPG(const PackedImage& image, const JxlBasicInfo& info,
|
|||
const std::vector<uint8_t>& icc,
|
||||
std::vector<uint8_t> exif, JpegEncoder encoder,
|
||||
size_t quality, const std::string& chroma_subsampling,
|
||||
int progressive_id, ThreadPool* pool,
|
||||
std::vector<uint8_t>* bytes) {
|
||||
int progressive_id, bool optimize_coding,
|
||||
ThreadPool* pool, std::vector<uint8_t>* bytes) {
|
||||
if (image.format.data_type != JXL_TYPE_UINT8) {
|
||||
return JXL_FAILURE("Unsupported pixel data type");
|
||||
}
|
||||
|
@ -343,7 +344,7 @@ Status EncodeImageJPG(const PackedImage& image, const JxlBasicInfo& info,
|
|||
case JpegEncoder::kLibJpeg:
|
||||
JXL_RETURN_IF_ERROR(EncodeWithLibJpeg(
|
||||
image, info, color_encoding, icc, std::move(exif), quality,
|
||||
chroma_subsampling, progressive_id, bytes));
|
||||
chroma_subsampling, progressive_id, optimize_coding, bytes));
|
||||
break;
|
||||
case JpegEncoder::kSJpeg:
|
||||
JXL_RETURN_IF_ERROR(EncodeWithSJpeg(image, info, icc, std::move(exif),
|
||||
|
@ -376,6 +377,7 @@ class JPEGEncoder : public Encoder {
|
|||
std::string chroma_subsampling = "444";
|
||||
JpegEncoder jpeg_encoder = JpegEncoder::kLibJpeg;
|
||||
int progressive_id = -1;
|
||||
bool optimize_coding = true;
|
||||
for (const auto& it : options()) {
|
||||
if (it.first == "q") {
|
||||
std::istringstream is(it.second);
|
||||
|
@ -393,6 +395,8 @@ class JPEGEncoder : public Encoder {
|
|||
} else if (it.first == "progressive") {
|
||||
std::istringstream is(it.second);
|
||||
JXL_RETURN_IF_ERROR(static_cast<bool>(is >> progressive_id));
|
||||
} else if (it.first == "optimize" && it.second == "OFF") {
|
||||
optimize_coding = false;
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> icc;
|
||||
|
@ -406,8 +410,8 @@ class JPEGEncoder : public Encoder {
|
|||
encoded_image->bitstreams.emplace_back();
|
||||
JXL_RETURN_IF_ERROR(EncodeImageJPG(
|
||||
frame.color, ppf.info, ppf.color_encoding, icc, ppf.metadata.exif,
|
||||
jpeg_encoder, quality, chroma_subsampling, progressive_id, pool,
|
||||
&encoded_image->bitstreams.back()));
|
||||
jpeg_encoder, quality, chroma_subsampling, progressive_id,
|
||||
optimize_coding, pool, &encoded_image->bitstreams.back()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
|
||||
#include "lib/extras/enc/jxl.h"
|
||||
|
||||
#include "jxl/encode_cxx.h"
|
||||
#include <jxl/encode_cxx.h>
|
||||
|
||||
#include "lib/jxl/exif.h"
|
||||
|
||||
namespace jxl {
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
#ifndef LIB_EXTRAS_ENC_JXL_H_
|
||||
#define LIB_EXTRAS_ENC_JXL_H_
|
||||
|
||||
#include <jxl/encode.h>
|
||||
#include <jxl/parallel_runner.h>
|
||||
#include <jxl/thread_parallel_runner.h>
|
||||
#include <jxl/types.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/encode.h"
|
||||
#include "jxl/parallel_runner.h"
|
||||
#include "jxl/thread_parallel_runner.h"
|
||||
#include "jxl/types.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
|
||||
namespace jxl {
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
|
||||
#include "lib/extras/enc/npy.h"
|
||||
|
||||
#include <jxl/types.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/types.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
|
||||
namespace jxl {
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
|
||||
#include "lib/extras/enc/pgx.h"
|
||||
|
||||
#include <jxl/codestream_header.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "jxl/codestream_header.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/byte_order.h"
|
||||
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
#include "lib/extras/dec/jpegli.h"
|
||||
|
||||
#include <jxl/color_encoding.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "jxl/color_encoding.h"
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/extras/dec/decode.h"
|
||||
#include "lib/extras/dec/jpg.h"
|
||||
|
@ -20,18 +20,18 @@
|
|||
#include "lib/extras/enc/jpegli.h"
|
||||
#include "lib/extras/enc/jpg.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jpegli/testing.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/size_constraints.h"
|
||||
#include "lib/jxl/test_image.h"
|
||||
#include "lib/jxl/test_utils.h"
|
||||
#include "lib/jxl/testing.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
namespace {
|
||||
|
||||
using test::Butteraugli3Norm;
|
||||
using test::ButteraugliDistance;
|
||||
using test::TestImage;
|
||||
|
||||
|
@ -43,8 +43,7 @@ Status ReadTestImage(const std::string& pathname, PackedPixelFile* ppf) {
|
|||
} else if (pathname.find(".pgm") != std::string::npos) {
|
||||
color_hints.Add("color_space", "Gra_D65_Rel_SRG");
|
||||
}
|
||||
return DecodeBytes(Span<const uint8_t>(encoded), color_hints,
|
||||
SizeConstraints(), ppf);
|
||||
return DecodeBytes(Span<const uint8_t>(encoded), color_hints, ppf);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GetAppData(const std::vector<uint8_t>& compressed) {
|
||||
|
@ -66,9 +65,10 @@ std::vector<uint8_t> GetAppData(const std::vector<uint8_t>& compressed) {
|
|||
}
|
||||
|
||||
Status DecodeWithLibjpeg(const std::vector<uint8_t>& compressed,
|
||||
PackedPixelFile* ppf) {
|
||||
return DecodeImageJPG(Span<const uint8_t>(compressed), ColorHints(),
|
||||
SizeConstraints(), ppf);
|
||||
PackedPixelFile* ppf,
|
||||
const JPGDecompressParams* dparams = nullptr) {
|
||||
return DecodeImageJPG(Span<const uint8_t>(compressed), ColorHints(), ppf,
|
||||
/*constraints=*/nullptr, dparams);
|
||||
}
|
||||
|
||||
Status EncodeWithLibjpeg(const PackedPixelFile& ppf, int quality,
|
||||
|
@ -107,7 +107,8 @@ TEST(JpegliTest, JpegliSRGBDecodeTest) {
|
|||
PackedPixelFile ppf1;
|
||||
ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf1));
|
||||
PackedPixelFile ppf2;
|
||||
ASSERT_TRUE(DecodeJpeg(compressed, JXL_TYPE_UINT8, nullptr, &ppf2));
|
||||
JpegDecompressParams dparams;
|
||||
ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf2));
|
||||
EXPECT_LT(ButteraugliDistance(ppf0, ppf2), ButteraugliDistance(ppf0, ppf1));
|
||||
}
|
||||
|
||||
|
@ -124,7 +125,8 @@ TEST(JpegliTest, JpegliGrayscaleDecodeTest) {
|
|||
PackedPixelFile ppf1;
|
||||
ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf1));
|
||||
PackedPixelFile ppf2;
|
||||
ASSERT_TRUE(DecodeJpeg(compressed, JXL_TYPE_UINT8, nullptr, &ppf2));
|
||||
JpegDecompressParams dparams;
|
||||
ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf2));
|
||||
EXPECT_LT(ButteraugliDistance(ppf0, ppf2), ButteraugliDistance(ppf0, ppf1));
|
||||
}
|
||||
|
||||
|
@ -170,7 +172,8 @@ TEST(JpegliTest, JpegliDecodeTestLargeSmoothArea) {
|
|||
ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed));
|
||||
|
||||
PackedPixelFile ppf1;
|
||||
ASSERT_TRUE(DecodeJpeg(compressed, JXL_TYPE_UINT8, nullptr, &ppf1));
|
||||
JpegDecompressParams dparams;
|
||||
ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf1));
|
||||
EXPECT_LT(ButteraugliDistance(ppf0, ppf1), 3.0f);
|
||||
}
|
||||
|
||||
|
@ -188,8 +191,8 @@ TEST(JpegliTest, JpegliYUVEncodeTest) {
|
|||
|
||||
PackedPixelFile ppf_out;
|
||||
ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out));
|
||||
EXPECT_THAT(BitsPerPixel(ppf_in, compressed), IsSlightlyBelow(1.68f));
|
||||
EXPECT_THAT(ButteraugliDistance(ppf_in, ppf_out), IsSlightlyBelow(1.28f));
|
||||
EXPECT_THAT(BitsPerPixel(ppf_in, compressed), IsSlightlyBelow(1.7f));
|
||||
EXPECT_THAT(ButteraugliDistance(ppf_in, ppf_out), IsSlightlyBelow(1.32f));
|
||||
}
|
||||
|
||||
TEST(JpegliTest, JpegliYUVChromaSubsamplingEncodeTest) {
|
||||
|
@ -208,8 +211,8 @@ TEST(JpegliTest, JpegliYUVChromaSubsamplingEncodeTest) {
|
|||
|
||||
PackedPixelFile ppf_out;
|
||||
ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out));
|
||||
EXPECT_LE(BitsPerPixel(ppf_in, compressed), 1.6f);
|
||||
EXPECT_LE(ButteraugliDistance(ppf_in, ppf_out), 1.8f);
|
||||
EXPECT_LE(BitsPerPixel(ppf_in, compressed), 1.55f);
|
||||
EXPECT_LE(ButteraugliDistance(ppf_in, ppf_out), 1.82f);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -228,8 +231,8 @@ TEST(JpegliTest, JpegliYUVEncodeTestNoAq) {
|
|||
|
||||
PackedPixelFile ppf_out;
|
||||
ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out));
|
||||
EXPECT_THAT(BitsPerPixel(ppf_in, compressed), IsSlightlyBelow(1.72f));
|
||||
EXPECT_THAT(ButteraugliDistance(ppf_in, ppf_out), IsSlightlyBelow(1.45f));
|
||||
EXPECT_THAT(BitsPerPixel(ppf_in, compressed), IsSlightlyBelow(1.85f));
|
||||
EXPECT_THAT(ButteraugliDistance(ppf_in, ppf_out), IsSlightlyBelow(1.25f));
|
||||
}
|
||||
|
||||
TEST(JpegliTest, JpegliHDRRoundtripTest) {
|
||||
|
@ -245,7 +248,9 @@ TEST(JpegliTest, JpegliHDRRoundtripTest) {
|
|||
ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
|
||||
PackedPixelFile ppf_out;
|
||||
ASSERT_TRUE(DecodeJpeg(compressed, JXL_TYPE_UINT16, nullptr, &ppf_out));
|
||||
JpegDecompressParams dparams;
|
||||
dparams.output_data_type = JXL_TYPE_UINT16;
|
||||
ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf_out));
|
||||
EXPECT_THAT(BitsPerPixel(ppf_in, compressed), IsSlightlyBelow(2.95f));
|
||||
EXPECT_THAT(ButteraugliDistance(ppf_in, ppf_out), IsSlightlyBelow(1.05f));
|
||||
}
|
||||
|
@ -305,6 +310,95 @@ TEST(JpegliTest, JpegliSetAppData) {
|
|||
EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
}
|
||||
|
||||
struct TestConfig {
|
||||
int num_colors;
|
||||
int passes;
|
||||
int dither;
|
||||
};
|
||||
|
||||
class JpegliColorQuantTestParam : public ::testing::TestWithParam<TestConfig> {
|
||||
};
|
||||
|
||||
TEST_P(JpegliColorQuantTestParam, JpegliColorQuantizeTest) {
|
||||
TestConfig config = GetParam();
|
||||
std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
|
||||
PackedPixelFile ppf0;
|
||||
ASSERT_TRUE(ReadTestImage(testimage, &ppf0));
|
||||
EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf0.color_encoding));
|
||||
EXPECT_EQ(8, ppf0.info.bits_per_sample);
|
||||
|
||||
std::vector<uint8_t> compressed;
|
||||
ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed));
|
||||
|
||||
PackedPixelFile ppf1;
|
||||
JPGDecompressParams dparams1;
|
||||
dparams1.two_pass_quant = (config.passes == 2);
|
||||
dparams1.num_colors = config.num_colors;
|
||||
dparams1.dither_mode = config.dither;
|
||||
ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf1, &dparams1));
|
||||
|
||||
PackedPixelFile ppf2;
|
||||
JpegDecompressParams dparams2;
|
||||
dparams2.two_pass_quant = (config.passes == 2);
|
||||
dparams2.num_colors = config.num_colors;
|
||||
dparams2.dither_mode = config.dither;
|
||||
ASSERT_TRUE(DecodeJpeg(compressed, dparams2, nullptr, &ppf2));
|
||||
|
||||
double dist1 = Butteraugli3Norm(ppf0, ppf1);
|
||||
double dist2 = Butteraugli3Norm(ppf0, ppf2);
|
||||
printf("distance: %f vs %f\n", dist2, dist1);
|
||||
if (config.passes == 1) {
|
||||
if (config.num_colors == 16 && config.dither == 2) {
|
||||
// TODO(szabadka) Fix this case.
|
||||
EXPECT_LT(dist2, dist1 * 1.5);
|
||||
} else {
|
||||
EXPECT_LT(dist2, dist1 * 1.05);
|
||||
}
|
||||
} else if (config.num_colors > 64) {
|
||||
// TODO(szabadka) Fix 2pass quantization for <= 64 colors.
|
||||
EXPECT_LT(dist2, dist1 * 1.1);
|
||||
} else if (config.num_colors > 32) {
|
||||
EXPECT_LT(dist2, dist1 * 1.2);
|
||||
} else {
|
||||
EXPECT_LT(dist2, dist1 * 1.7);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TestConfig> GenerateTests() {
|
||||
std::vector<TestConfig> all_tests;
|
||||
for (int num_colors = 8; num_colors <= 256; num_colors *= 2) {
|
||||
for (int passes = 1; passes <= 2; ++passes) {
|
||||
for (int dither = 0; dither < 3; dither += passes) {
|
||||
TestConfig config;
|
||||
config.num_colors = num_colors;
|
||||
config.passes = passes;
|
||||
config.dither = dither;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
return all_tests;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const TestConfig& c) {
|
||||
static constexpr const char* kDitherModeStr[] = {"No", "Ordered", "FS"};
|
||||
os << c.passes << "pass";
|
||||
os << c.num_colors << "colors";
|
||||
os << kDitherModeStr[c.dither] << "dither";
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string TestDescription(const testing::TestParamInfo<TestConfig>& info) {
|
||||
std::stringstream name;
|
||||
name << info.param;
|
||||
return name.str();
|
||||
}
|
||||
|
||||
JXL_GTEST_INSTANTIATE_TEST_SUITE_P(JpegliColorQuantTest,
|
||||
JpegliColorQuantTestParam,
|
||||
testing::ValuesIn(GenerateTests()),
|
||||
TestDescription);
|
||||
|
||||
} // namespace
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
// Helper class for storing external (int or float, interleaved) images. This is
|
||||
// the common format used by other libraries and in the libjxl API.
|
||||
|
||||
#include <jxl/codestream_header.h>
|
||||
#include <jxl/encode.h>
|
||||
#include <jxl/types.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
@ -19,9 +22,6 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/codestream_header.h"
|
||||
#include "jxl/encode.h"
|
||||
#include "jxl/types.h"
|
||||
#include "lib/jxl/common.h"
|
||||
|
||||
namespace jxl {
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
|
||||
#include "lib/extras/packed_image_convert.h"
|
||||
|
||||
#include <jxl/color_encoding.h>
|
||||
#include <jxl/types.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "jxl/color_encoding.h"
|
||||
#include "jxl/types.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/color_management.h"
|
||||
|
@ -168,14 +169,12 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
|
|||
}
|
||||
|
||||
// Convert the pixels
|
||||
io->dec_pixels = 0;
|
||||
io->frames.clear();
|
||||
for (const auto& frame : ppf.frames) {
|
||||
ImageBundle bundle(&io->metadata.m);
|
||||
JXL_RETURN_IF_ERROR(
|
||||
ConvertPackedFrameToImageBundle(ppf.info, frame, *io, pool, &bundle));
|
||||
io->frames.push_back(std::move(bundle));
|
||||
io->dec_pixels += frame.color.xsize * frame.color.ysize;
|
||||
}
|
||||
|
||||
if (ppf.info.exponent_bits_per_sample == 0) {
|
||||
|
@ -218,6 +217,12 @@ Status ConvertCodecInOutToPackedPixelFile(const CodecInOut& io,
|
|||
ppf->info.exponent_bits_per_sample =
|
||||
io.metadata.m.bit_depth.exponent_bits_per_sample;
|
||||
|
||||
ppf->info.intensity_target = io.metadata.m.tone_mapping.intensity_target;
|
||||
ppf->info.linear_below = io.metadata.m.tone_mapping.linear_below;
|
||||
ppf->info.min_nits = io.metadata.m.tone_mapping.min_nits;
|
||||
ppf->info.relative_to_max_display =
|
||||
io.metadata.m.tone_mapping.relative_to_max_display;
|
||||
|
||||
ppf->info.alpha_bits = io.metadata.m.GetAlphaBits();
|
||||
ppf->info.alpha_premultiplied = alpha_premultiplied;
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
// Helper functions to convert from the external image types to the internal
|
||||
// CodecInOut to help transitioning to the external types.
|
||||
|
||||
#include "jxl/types.h"
|
||||
#include <jxl/types.h>
|
||||
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
|
|
@ -17,10 +17,10 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "jxl/jxl_export.h"
|
||||
#include "jxl/memory_manager.h"
|
||||
#include "jxl/parallel_runner.h"
|
||||
#include "jxl/types.h"
|
||||
#include <jxl/jxl_export.h>
|
||||
#include <jxl/memory_manager.h>
|
||||
#include <jxl/parallel_runner.h>
|
||||
#include <jxl/types.h>
|
||||
|
||||
/**
|
||||
* Opaque structure that holds a butteraugli API.
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
#ifndef JXL_BUTTERAUGLI_CXX_H_
|
||||
#define JXL_BUTTERAUGLI_CXX_H_
|
||||
|
||||
#include <memory>
|
||||
#include <jxl/butteraugli.h>
|
||||
|
||||
#include "jxl/butteraugli.h"
|
||||
#include <memory>
|
||||
|
||||
#if !(defined(__cplusplus) || defined(c_plusplus))
|
||||
#error "This a C++ only header. Use jxl/butteraugli.h from C sources."
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
#ifndef JXL_CMS_INTERFACE_H_
|
||||
#define JXL_CMS_INTERFACE_H_
|
||||
|
||||
#include "jxl/color_encoding.h"
|
||||
#include "jxl/types.h"
|
||||
#include <jxl/color_encoding.h>
|
||||
#include <jxl/types.h>
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
|
|
|
@ -15,11 +15,10 @@
|
|||
#ifndef JXL_CODESTREAM_HEADER_H_
|
||||
#define JXL_CODESTREAM_HEADER_H_
|
||||
|
||||
#include <jxl/types.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "jxl/types.h"
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
@ -245,7 +244,7 @@ typedef struct {
|
|||
*/
|
||||
uint32_t intrinsic_xsize;
|
||||
|
||||
/** Intrinsic heigth of the image.
|
||||
/** Intrinsic height of the image.
|
||||
* The intrinsic size can be different from the actual size in pixels
|
||||
* (as given by xsize and ysize) and it denotes the recommended dimensions
|
||||
* for displaying the image, i.e. applications are advised to resample the
|
||||
|
@ -380,6 +379,8 @@ typedef struct {
|
|||
/** After blending, save the frame as reference frame with this ID (0-3).
|
||||
* Special case: if the frame duration is nonzero, ID 0 means "will not be
|
||||
* referenced in the future". This value is not used for the last frame.
|
||||
* When encoding, ID 3 is reserved to frames that are generated internally by
|
||||
* the encoder, and should not be used by applications.
|
||||
*/
|
||||
uint32_t save_as_reference;
|
||||
} JxlLayerInfo;
|
||||
|
|
|
@ -13,18 +13,17 @@
|
|||
#ifndef JXL_DECODE_H_
|
||||
#define JXL_DECODE_H_
|
||||
|
||||
#include <jxl/cms_interface.h>
|
||||
#include <jxl/codestream_header.h>
|
||||
#include <jxl/color_encoding.h>
|
||||
#include <jxl/jxl_export.h>
|
||||
#include <jxl/memory_manager.h>
|
||||
#include <jxl/parallel_runner.h>
|
||||
#include <jxl/types.h>
|
||||
#include <jxl/version.h>
|
||||
#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"
|
||||
#include "jxl/memory_manager.h"
|
||||
#include "jxl/parallel_runner.h"
|
||||
#include "jxl/types.h"
|
||||
#include "jxl/version.h"
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
#ifndef JXL_DECODE_CXX_H_
|
||||
#define JXL_DECODE_CXX_H_
|
||||
|
||||
#include <memory>
|
||||
#include <jxl/decode.h>
|
||||
|
||||
#include "jxl/decode.h"
|
||||
#include <memory>
|
||||
|
||||
#if !(defined(__cplusplus) || defined(c_plusplus))
|
||||
#error "This a C++ only header. Use jxl/decode.h from C sources."
|
||||
|
|
|
@ -13,12 +13,12 @@
|
|||
#ifndef JXL_ENCODE_H_
|
||||
#define JXL_ENCODE_H_
|
||||
|
||||
#include "jxl/cms_interface.h"
|
||||
#include "jxl/codestream_header.h"
|
||||
#include "jxl/jxl_export.h"
|
||||
#include "jxl/memory_manager.h"
|
||||
#include "jxl/parallel_runner.h"
|
||||
#include "jxl/version.h"
|
||||
#include <jxl/cms_interface.h>
|
||||
#include <jxl/codestream_header.h>
|
||||
#include <jxl/jxl_export.h>
|
||||
#include <jxl/memory_manager.h>
|
||||
#include <jxl/parallel_runner.h>
|
||||
#include <jxl/version.h>
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
#ifndef JXL_ENCODE_CXX_H_
|
||||
#define JXL_ENCODE_CXX_H_
|
||||
|
||||
#include <memory>
|
||||
#include <jxl/encode.h>
|
||||
|
||||
#include "jxl/encode.h"
|
||||
#include <memory>
|
||||
|
||||
#if !(defined(__cplusplus) || defined(c_plusplus))
|
||||
#error "This a C++ only header. Use jxl/encode.h from C sources."
|
||||
|
|
|
@ -30,15 +30,14 @@
|
|||
#ifndef JXL_RESIZABLE_PARALLEL_RUNNER_H_
|
||||
#define JXL_RESIZABLE_PARALLEL_RUNNER_H_
|
||||
|
||||
#include <jxl/jxl_threads_export.h>
|
||||
#include <jxl/memory_manager.h>
|
||||
#include <jxl/parallel_runner.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "jxl/jxl_threads_export.h"
|
||||
#include "jxl/memory_manager.h"
|
||||
#include "jxl/parallel_runner.h"
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
#ifndef JXL_RESIZABLE_PARALLEL_RUNNER_CXX_H_
|
||||
#define JXL_RESIZABLE_PARALLEL_RUNNER_CXX_H_
|
||||
|
||||
#include <memory>
|
||||
#include <jxl/resizable_parallel_runner.h>
|
||||
|
||||
#include "jxl/resizable_parallel_runner.h"
|
||||
#include <memory>
|
||||
|
||||
#if !(defined(__cplusplus) || defined(c_plusplus))
|
||||
#error \
|
||||
|
|
|
@ -30,15 +30,14 @@
|
|||
#ifndef JXL_THREAD_PARALLEL_RUNNER_H_
|
||||
#define JXL_THREAD_PARALLEL_RUNNER_H_
|
||||
|
||||
#include <jxl/jxl_threads_export.h>
|
||||
#include <jxl/memory_manager.h>
|
||||
#include <jxl/parallel_runner.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "jxl/jxl_threads_export.h"
|
||||
#include "jxl/memory_manager.h"
|
||||
#include "jxl/parallel_runner.h"
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
|
|
@ -15,9 +15,9 @@
|
|||
#ifndef JXL_THREAD_PARALLEL_RUNNER_CXX_H_
|
||||
#define JXL_THREAD_PARALLEL_RUNNER_CXX_H_
|
||||
|
||||
#include <memory>
|
||||
#include <jxl/thread_parallel_runner.h>
|
||||
|
||||
#include "jxl/thread_parallel_runner.h"
|
||||
#include <memory>
|
||||
|
||||
#if !(defined(__cplusplus) || defined(c_plusplus))
|
||||
#error \
|
||||
|
|
|
@ -13,11 +13,10 @@
|
|||
#ifndef JXL_TYPES_H_
|
||||
#define JXL_TYPES_H_
|
||||
|
||||
#include <jxl/jxl_export.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "jxl/jxl_export.h"
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
|
|
@ -8,7 +8,6 @@ include(jxl_lists.cmake)
|
|||
|
||||
set(JPEGLI_INTERNAL_LIBS
|
||||
hwy
|
||||
jxl-static
|
||||
Threads::Threads
|
||||
${ATOMICS_LIBRARIES}
|
||||
)
|
||||
|
@ -55,6 +54,7 @@ foreach (TESTFILE IN LISTS JPEGXL_INTERNAL_JPEGLI_TESTS)
|
|||
GTest::Main
|
||||
${JPEG_LIBRARIES}
|
||||
)
|
||||
set_target_properties(${TESTNAME} PROPERTIES LINK_FLAGS "${JPEGXL_COVERAGE_LINK_FLAGS}")
|
||||
# Output test targets in the test directory.
|
||||
set_target_properties(${TESTNAME} PROPERTIES PREFIX "tests/")
|
||||
if (WIN32 AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
|
@ -69,21 +69,6 @@ endif()
|
|||
#
|
||||
|
||||
if (JPEGXL_ENABLE_JPEGLI_LIBJPEG AND NOT APPLE AND NOT WIN32 AND NOT JPEGXL_EMSCRIPTEN)
|
||||
set(JPEGLI_LIBJPEG_MAJOR_VERSION 62)
|
||||
set(JPEGLI_LIBJPEG_MINOR_VERSION 3)
|
||||
set(JPEGLI_LIBJPEG_PATCH_VERSION 0)
|
||||
set(JPEGLI_LIBJPEG_LIBRARY_VERSION
|
||||
"${JPEGLI_LIBJPEG_MAJOR_VERSION}.${JPEGLI_LIBJPEG_MINOR_VERSION}.${JPEGLI_LIBJPEG_PATCH_VERSION}"
|
||||
)
|
||||
|
||||
set(JPEGLI_LIBJPEG_LIBRARY_SOVERSION "${JPEGLI_LIBJPEG_MAJOR_VERSION}")
|
||||
|
||||
set(JPEGLI_LIBJPEG_OBJ_COMPILE_DEFINITIONS
|
||||
JPEGLI_LIBJPEG_MAJOR_VERSION=${JPEGLI_LIBJPEG_MAJOR_VERSION}
|
||||
JPEGLI_LIBJPEG_MINOR_VERSION=${JPEGLI_LIBJPEG_MINOR_VERSION}
|
||||
JPEGLI_LIBJPEG_PATCH_VERSION=${JPEGLI_LIBJPEG_PATCH_VERSION}
|
||||
)
|
||||
|
||||
add_library(jpegli-libjpeg-obj OBJECT "${JPEGXL_INTERNAL_JPEGLI_WRAPPER_SOURCES}")
|
||||
target_compile_options(jpegli-libjpeg-obj PRIVATE ${JPEGXL_INTERNAL_FLAGS})
|
||||
target_compile_options(jpegli-libjpeg-obj PUBLIC ${JPEGXL_COVERAGE_FLAGS})
|
||||
|
@ -107,9 +92,9 @@ set_target_properties(jpeg PROPERTIES
|
|||
# Add a jpeg.version file as a version script to tag symbols with the
|
||||
# appropriate version number.
|
||||
set_target_properties(jpeg PROPERTIES
|
||||
LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/jpegli/jpeg.version)
|
||||
LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/jpegli/jpeg.version.${JPEGLI_LIBJPEG_LIBRARY_SOVERSION})
|
||||
set_property(TARGET jpeg APPEND_STRING PROPERTY
|
||||
LINK_FLAGS " -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/jpegli/jpeg.version")
|
||||
LINK_FLAGS " -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/jpegli/jpeg.version.${JPEGLI_LIBJPEG_LIBRARY_SOVERSION}")
|
||||
|
||||
# This hides the default visibility symbols from static libraries bundled into
|
||||
# the shared library. In particular this prevents exposing symbols from hwy
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -21,10 +22,7 @@
|
|||
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/image.h"
|
||||
#include "lib/jxl/image_ops.h"
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jpegli {
|
||||
namespace HWY_NAMESPACE {
|
||||
|
@ -49,9 +47,7 @@ using hwy::HWY_NAMESPACE::Sqrt;
|
|||
using hwy::HWY_NAMESPACE::Sub;
|
||||
using hwy::HWY_NAMESPACE::ZeroIfNegative;
|
||||
|
||||
static constexpr size_t kEncTileDim = 64;
|
||||
;
|
||||
static constexpr size_t kEncTileDimInBlocks = kEncTileDim / jxl::kBlockDim;
|
||||
static constexpr float kInputScaling = 1.0f / 255.0f;
|
||||
|
||||
// Primary template: default to actual division.
|
||||
template <typename T, class V>
|
||||
|
@ -211,16 +207,22 @@ V RatioOfDerivativesOfCubicRootToSimpleGamma(const D d, V v) {
|
|||
// SimpleGamma(v * v * v) is the psychovisual space in butteraugli.
|
||||
// This ratio allows quantization to move from jxl's opsin space to
|
||||
// butteraugli's log-gamma space.
|
||||
float kEpsilon = 1e-2;
|
||||
static const float kEpsilon = 1e-2;
|
||||
static const float kNumOffset = kEpsilon / kInputScaling / kInputScaling;
|
||||
static const float kNumMul = kSGRetMul * 3 * kSGmul;
|
||||
static const float kVOffset = (kSGVOffset * kLog2 + kEpsilon) / kInputScaling;
|
||||
static const float kDenMul = kLog2 * kSGmul * kInputScaling * kInputScaling;
|
||||
|
||||
v = ZeroIfNegative(v);
|
||||
const auto kNumMul = Set(d, kSGRetMul * 3 * kSGmul);
|
||||
const auto kVOffset = Set(d, kSGVOffset * kLog2 + kEpsilon);
|
||||
const auto kDenMul = Set(d, kLog2 * kSGmul);
|
||||
const auto num_mul = Set(d, kNumMul);
|
||||
const auto num_offset = Set(d, kNumOffset);
|
||||
const auto den_offset = Set(d, kVOffset);
|
||||
const auto den_mul = Set(d, kDenMul);
|
||||
|
||||
const auto v2 = Mul(v, v);
|
||||
|
||||
const auto num = MulAdd(kNumMul, v2, Set(d, kEpsilon));
|
||||
const auto den = MulAdd(Mul(kDenMul, v), v2, kVOffset);
|
||||
const auto num = MulAdd(num_mul, v2, num_offset);
|
||||
const auto den = MulAdd(Mul(den_mul, v), v2, den_offset);
|
||||
return invert ? Div(num, den) : Div(den, num);
|
||||
}
|
||||
|
||||
|
@ -257,20 +259,23 @@ V SimpleGamma(const D d, V v) {
|
|||
|
||||
template <class D, class V>
|
||||
V GammaModulation(const D d, const size_t x, const size_t y,
|
||||
const jxl::ImageF& xyb_y, const V out_val) {
|
||||
const float kBias = 0.16f;
|
||||
const RowBuffer<float>& input, const V out_val) {
|
||||
static const float kBias = 0.16f / kInputScaling;
|
||||
static const float kScale = kInputScaling / 64.0f;
|
||||
auto overall_ratio = Zero(d);
|
||||
auto bias = Set(d, kBias);
|
||||
const auto bias = Set(d, kBias);
|
||||
const auto scale = Set(d, kScale);
|
||||
const float* const JXL_RESTRICT block_start = input.Row(y) + x;
|
||||
for (size_t dy = 0; dy < 8; ++dy) {
|
||||
const float* const JXL_RESTRICT row_in_y = xyb_y.Row(y + dy);
|
||||
const float* const JXL_RESTRICT row_in = block_start + dy * input.stride();
|
||||
for (size_t dx = 0; dx < 8; dx += Lanes(d)) {
|
||||
const auto iny = Add(Load(d, row_in_y + x + dx), bias);
|
||||
const auto iny = Add(Load(d, row_in + dx), bias);
|
||||
const auto ratio_g =
|
||||
RatioOfDerivativesOfCubicRootToSimpleGamma</*invert=*/true>(d, iny);
|
||||
overall_ratio = Add(overall_ratio, ratio_g);
|
||||
}
|
||||
}
|
||||
overall_ratio = Mul(SumOfLanes(d, overall_ratio), Set(d, 1.0f / 64));
|
||||
overall_ratio = Mul(SumOfLanes(d, overall_ratio), scale);
|
||||
// ideally -1.0, but likely optimal correction adds some entropy, so slightly
|
||||
// less than that.
|
||||
// ln(2) constant folded in because we want std::log but have FastLog2f.
|
||||
|
@ -281,77 +286,64 @@ V GammaModulation(const D d, const size_t x, const size_t y,
|
|||
// Change precision in 8x8 blocks that have high frequency content.
|
||||
template <class D, class V>
|
||||
V HfModulation(const D d, const size_t x, const size_t y,
|
||||
const jxl::ImageF& xyb, const V out_val) {
|
||||
const RowBuffer<float>& input, const V out_val) {
|
||||
// Zero out the invalid differences for the rightmost value per row.
|
||||
const Rebind<uint32_t, D> du;
|
||||
HWY_ALIGN constexpr uint32_t kMaskRight[jxl::kBlockDim] = {~0u, ~0u, ~0u, ~0u,
|
||||
~0u, ~0u, ~0u, 0};
|
||||
HWY_ALIGN constexpr uint32_t kMaskRight[8] = {~0u, ~0u, ~0u, ~0u,
|
||||
~0u, ~0u, ~0u, 0};
|
||||
|
||||
auto sum = Zero(d); // sum of absolute differences with right and below
|
||||
static const float kSumCoeff = -2.0052193233688884f * kInputScaling / 112.0;
|
||||
auto sumcoeff = Set(d, kSumCoeff);
|
||||
|
||||
const float* const JXL_RESTRICT block_start = input.Row(y) + x;
|
||||
for (size_t dy = 0; dy < 8; ++dy) {
|
||||
const float* JXL_RESTRICT row_in = xyb.Row(y + dy) + x;
|
||||
const float* JXL_RESTRICT row_in = block_start + dy * input.stride();
|
||||
const float* JXL_RESTRICT row_in_next =
|
||||
dy == 7 ? row_in : xyb.Row(y + dy + 1) + x;
|
||||
dy == 7 ? row_in : row_in + input.stride();
|
||||
|
||||
// In SCALAR, there is no guarantee of having extra row padding.
|
||||
// Hence, we need to ensure we don't access pixels outside the row itself.
|
||||
// In SIMD modes, however, rows are padded, so it's safe to access one
|
||||
// garbage value after the row. The vector then gets masked with kMaskRight
|
||||
// to remove the influence of that value.
|
||||
#if HWY_TARGET != HWY_SCALAR
|
||||
for (size_t dx = 0; dx < 8; dx += Lanes(d)) {
|
||||
#else
|
||||
for (size_t dx = 0; dx < 7; dx += Lanes(d)) {
|
||||
#endif
|
||||
const auto p = Load(d, row_in + dx);
|
||||
const auto pr = LoadU(d, row_in + dx + 1);
|
||||
const auto mask = BitCast(d, Load(du, kMaskRight + dx));
|
||||
sum = Add(sum, And(mask, AbsDiff(p, pr)));
|
||||
|
||||
const auto pd = Load(d, row_in_next + dx);
|
||||
sum = Add(sum, AbsDiff(p, pd));
|
||||
}
|
||||
#if HWY_TARGET == HWY_SCALAR
|
||||
const auto p = Load(d, row_in + 7);
|
||||
const auto pd = Load(d, row_in_next + 7);
|
||||
sum = Add(sum, AbsDiff(p, pd));
|
||||
#endif
|
||||
}
|
||||
|
||||
sum = SumOfLanes(d, sum);
|
||||
return MulAdd(sum, Set(d, -2.0052193233688884f / 112), out_val);
|
||||
return MulAdd(sum, sumcoeff, out_val);
|
||||
}
|
||||
|
||||
void PerBlockModulations(const float butteraugli_target,
|
||||
const jxl::ImageF& xyb_y, const float scale,
|
||||
const jxl::Rect& rect, jxl::ImageF* out) {
|
||||
JXL_ASSERT(DivCeil(xyb_y.xsize(), jxl::kBlockDim) == out->xsize());
|
||||
JXL_ASSERT(DivCeil(xyb_y.ysize(), jxl::kBlockDim) == out->ysize());
|
||||
|
||||
float base_level = 0.48f * scale;
|
||||
float kDampenRampStart = 2.0f;
|
||||
float kDampenRampEnd = 14.0f;
|
||||
void PerBlockModulations(const float y_quant_01, const RowBuffer<float>& input,
|
||||
const size_t yb0, const size_t yblen,
|
||||
RowBuffer<float>* aq_map) {
|
||||
static const float kAcQuant = 0.841f;
|
||||
float base_level = 0.48f * kAcQuant;
|
||||
float kDampenRampStart = 9.0f;
|
||||
float kDampenRampEnd = 65.0f;
|
||||
float dampen = 1.0f;
|
||||
if (butteraugli_target >= kDampenRampStart) {
|
||||
dampen = 1.0f - ((butteraugli_target - kDampenRampStart) /
|
||||
if (y_quant_01 >= kDampenRampStart) {
|
||||
dampen = 1.0f - ((y_quant_01 - kDampenRampStart) /
|
||||
(kDampenRampEnd - kDampenRampStart));
|
||||
if (dampen < 0) {
|
||||
dampen = 0;
|
||||
}
|
||||
}
|
||||
const float mul = scale * dampen;
|
||||
const float mul = kAcQuant * dampen;
|
||||
const float add = (1.0f - dampen) * base_level;
|
||||
for (size_t iy = rect.y0(); iy < rect.y0() + rect.ysize(); iy++) {
|
||||
const size_t y = iy * 8;
|
||||
float* const JXL_RESTRICT row_out = out->Row(iy);
|
||||
const HWY_CAPPED(float, jxl::kBlockDim) df;
|
||||
for (size_t ix = rect.x0(); ix < rect.x0() + rect.xsize(); ix++) {
|
||||
for (size_t iy = 0; iy < yblen; iy++) {
|
||||
const size_t yb = yb0 + iy;
|
||||
const size_t y = yb * 8;
|
||||
float* const JXL_RESTRICT row_out = aq_map->Row(yb);
|
||||
const HWY_CAPPED(float, 8) df;
|
||||
for (size_t ix = 0; ix < aq_map->xsize(); ix++) {
|
||||
size_t x = ix * 8;
|
||||
auto out_val = Set(df, row_out[ix]);
|
||||
out_val = ComputeMask(df, out_val);
|
||||
out_val = HfModulation(df, x, y, xyb_y, out_val);
|
||||
out_val = GammaModulation(df, x, y, xyb_y, out_val);
|
||||
out_val = HfModulation(df, x, y, input, out_val);
|
||||
out_val = GammaModulation(df, x, y, input, out_val);
|
||||
// We want multiplicative quantization field, so everything
|
||||
// until this point has been modulating the exponent.
|
||||
row_out[ix] = FastPow2f(GetLane(out_val) * 1.442695041f) * mul + add;
|
||||
|
@ -368,241 +360,133 @@ V MaskingSqrt(const D d, V v) {
|
|||
return Mul(Set(d, 0.25f), Sqrt(MulAdd(v, Sqrt(mul_v), offset_v)));
|
||||
}
|
||||
|
||||
float MaskingSqrt(const float v) {
|
||||
using DScalar = HWY_CAPPED(float, 1);
|
||||
auto vscalar = Load(DScalar(), &v);
|
||||
return GetLane(MaskingSqrt(DScalar(), vscalar));
|
||||
template <typename V>
|
||||
void Sort4(V& min0, V& min1, V& min2, V& min3) {
|
||||
const auto tmp0 = Min(min0, min1);
|
||||
const auto tmp1 = Max(min0, min1);
|
||||
const auto tmp2 = Min(min2, min3);
|
||||
const auto tmp3 = Max(min2, min3);
|
||||
const auto tmp4 = Max(tmp0, tmp2);
|
||||
const auto tmp5 = Min(tmp1, tmp3);
|
||||
min0 = Min(tmp0, tmp2);
|
||||
min1 = Min(tmp4, tmp5);
|
||||
min2 = Max(tmp4, tmp5);
|
||||
min3 = Max(tmp1, tmp3);
|
||||
}
|
||||
|
||||
void StoreMin4(const float v, float& min0, float& min1, float& min2,
|
||||
float& min3) {
|
||||
if (v < min3) {
|
||||
if (v < min0) {
|
||||
min3 = min2;
|
||||
min2 = min1;
|
||||
min1 = min0;
|
||||
min0 = v;
|
||||
} else if (v < min1) {
|
||||
min3 = min2;
|
||||
min2 = min1;
|
||||
min1 = v;
|
||||
} else if (v < min2) {
|
||||
min3 = min2;
|
||||
min2 = v;
|
||||
} else {
|
||||
min3 = v;
|
||||
template <typename V>
|
||||
void UpdateMin4(const V v, V& min0, V& min1, V& min2, V& min3) {
|
||||
const auto tmp0 = Max(min0, v);
|
||||
const auto tmp1 = Max(min1, tmp0);
|
||||
const auto tmp2 = Max(min2, tmp1);
|
||||
min0 = Min(min0, v);
|
||||
min1 = Min(min1, tmp0);
|
||||
min2 = Min(min2, tmp1);
|
||||
min3 = Min(min3, tmp2);
|
||||
}
|
||||
|
||||
// Computes a linear combination of the 4 lowest values of the 3x3 neighborhood
|
||||
// of each pixel. Output is downsampled 2x.
|
||||
void FuzzyErosion(const RowBuffer<float>& pre_erosion, const size_t yb0,
|
||||
const size_t yblen, RowBuffer<float>* tmp,
|
||||
RowBuffer<float>* aq_map) {
|
||||
int xsize_blocks = aq_map->xsize();
|
||||
int xsize = pre_erosion.xsize();
|
||||
HWY_FULL(float) d;
|
||||
const auto mul0 = Set(d, 0.125f);
|
||||
const auto mul1 = Set(d, 0.075f);
|
||||
const auto mul2 = Set(d, 0.06f);
|
||||
const auto mul3 = Set(d, 0.05f);
|
||||
for (size_t iy = 0; iy < 2 * yblen; ++iy) {
|
||||
size_t y = 2 * yb0 + iy;
|
||||
const float* JXL_RESTRICT rowt = pre_erosion.Row(y - 1);
|
||||
const float* JXL_RESTRICT rowm = pre_erosion.Row(y);
|
||||
const float* JXL_RESTRICT rowb = pre_erosion.Row(y + 1);
|
||||
float* row_out = tmp->Row(y);
|
||||
for (int x = 0; x < xsize; x += Lanes(d)) {
|
||||
int xm1 = x - 1;
|
||||
int xp1 = x + 1;
|
||||
auto min0 = LoadU(d, rowm + x);
|
||||
auto min1 = LoadU(d, rowm + xm1);
|
||||
auto min2 = LoadU(d, rowm + xp1);
|
||||
auto min3 = LoadU(d, rowt + xm1);
|
||||
Sort4(min0, min1, min2, min3);
|
||||
UpdateMin4(LoadU(d, rowt + x), min0, min1, min2, min3);
|
||||
UpdateMin4(LoadU(d, rowt + xp1), min0, min1, min2, min3);
|
||||
UpdateMin4(LoadU(d, rowb + xm1), min0, min1, min2, min3);
|
||||
UpdateMin4(LoadU(d, rowb + x), min0, min1, min2, min3);
|
||||
UpdateMin4(LoadU(d, rowb + xp1), min0, min1, min2, min3);
|
||||
const auto v = Add(Add(Mul(mul0, min0), Mul(mul1, min1)),
|
||||
Add(Mul(mul2, min2), Mul(mul3, min3)));
|
||||
Store(v, d, row_out + x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Look for smooth areas near the area of degradation.
|
||||
// If the areas are generally smooth, don't do masking.
|
||||
// Output is downsampled 2x.
|
||||
void FuzzyErosion(const jxl::Rect& from_rect, const jxl::ImageF& from,
|
||||
const jxl::Rect& to_rect, jxl::ImageF* to) {
|
||||
const size_t xsize = from.xsize();
|
||||
const size_t ysize = from.ysize();
|
||||
constexpr int kStep = 1;
|
||||
static_assert(kStep == 1, "Step must be 1");
|
||||
JXL_ASSERT(to_rect.xsize() * 2 == from_rect.xsize());
|
||||
JXL_ASSERT(to_rect.ysize() * 2 == from_rect.ysize());
|
||||
for (size_t fy = 0; fy < from_rect.ysize(); ++fy) {
|
||||
size_t y = fy + from_rect.y0();
|
||||
size_t ym1 = y >= kStep ? y - kStep : y;
|
||||
size_t yp1 = y + kStep < ysize ? y + kStep : y;
|
||||
const float* rowt = from.Row(ym1);
|
||||
const float* row = from.Row(y);
|
||||
const float* rowb = from.Row(yp1);
|
||||
float* row_out = to_rect.Row(to, fy / 2);
|
||||
for (size_t fx = 0; fx < from_rect.xsize(); ++fx) {
|
||||
size_t x = fx + from_rect.x0();
|
||||
size_t xm1 = x >= kStep ? x - kStep : x;
|
||||
size_t xp1 = x + kStep < xsize ? x + kStep : x;
|
||||
float min0 = row[x];
|
||||
float min1 = row[xm1];
|
||||
float min2 = row[xp1];
|
||||
float min3 = rowt[xm1];
|
||||
// Sort the first four values.
|
||||
if (min0 > min1) std::swap(min0, min1);
|
||||
if (min0 > min2) std::swap(min0, min2);
|
||||
if (min0 > min3) std::swap(min0, min3);
|
||||
if (min1 > min2) std::swap(min1, min2);
|
||||
if (min1 > min3) std::swap(min1, min3);
|
||||
if (min2 > min3) std::swap(min2, min3);
|
||||
// The remaining five values of a 3x3 neighbourhood.
|
||||
StoreMin4(rowt[x], min0, min1, min2, min3);
|
||||
StoreMin4(rowt[xp1], min0, min1, min2, min3);
|
||||
StoreMin4(rowb[xm1], min0, min1, min2, min3);
|
||||
StoreMin4(rowb[x], min0, min1, min2, min3);
|
||||
StoreMin4(rowb[xp1], min0, min1, min2, min3);
|
||||
static const float kMul0 = 0.125f;
|
||||
static const float kMul1 = 0.075f;
|
||||
static const float kMul2 = 0.06f;
|
||||
static const float kMul3 = 0.05f;
|
||||
float v = kMul0 * min0 + kMul1 * min1 + kMul2 * min2 + kMul3 * min3;
|
||||
if (fx % 2 == 0 && fy % 2 == 0) {
|
||||
row_out[fx / 2] = v;
|
||||
} else {
|
||||
row_out[fx / 2] += v;
|
||||
if (iy % 2 == 1) {
|
||||
const float* JXL_RESTRICT row_out0 = tmp->Row(y - 1);
|
||||
float* JXL_RESTRICT aq_out = aq_map->Row(yb0 + iy / 2);
|
||||
for (int bx = 0, x = 0; bx < xsize_blocks; ++bx, x += 2) {
|
||||
aq_out[bx] =
|
||||
(row_out[x] + row_out[x + 1] + row_out0[x] + row_out0[x + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AdaptiveQuantizationImpl {
|
||||
void Init(const jxl::ImageF& xyb_y) {
|
||||
JXL_DASSERT(xyb_y.xsize() % jxl::kBlockDim == 0);
|
||||
JXL_DASSERT(xyb_y.ysize() % jxl::kBlockDim == 0);
|
||||
const size_t xsize = xyb_y.xsize();
|
||||
const size_t ysize = xyb_y.ysize();
|
||||
aq_map = jxl::ImageF(xsize / jxl::kBlockDim, ysize / jxl::kBlockDim);
|
||||
}
|
||||
void PrepareBuffers(size_t num_threads) {
|
||||
diff_buffer = jxl::ImageF(kEncTileDim + 8, num_threads);
|
||||
for (size_t i = pre_erosion.size(); i < num_threads; i++) {
|
||||
pre_erosion.emplace_back(kEncTileDimInBlocks * 2 + 2,
|
||||
kEncTileDimInBlocks * 2 + 2);
|
||||
void ComputePreErosion(const RowBuffer<float>& input, const size_t xsize,
|
||||
const size_t y0, const size_t ylen, int border,
|
||||
float* diff_buffer, RowBuffer<float>* pre_erosion) {
|
||||
const size_t xsize_out = xsize / 4;
|
||||
const size_t y0_out = y0 / 4;
|
||||
|
||||
// The XYB gamma is 3.0 to be able to decode faster with two muls.
|
||||
// Butteraugli's gamma is matching the gamma of human eye, around 2.6.
|
||||
// We approximate the gamma difference by adding one cubic root into
|
||||
// the adaptive quantization. This gives us a total gamma of 2.6666
|
||||
// for quantization uses.
|
||||
static const float match_gamma_offset = 0.019 / kInputScaling;
|
||||
|
||||
const HWY_CAPPED(float, 8) df;
|
||||
|
||||
static const float limit = 0.2f;
|
||||
// Computes image (padded to multiple of 8x8) of local pixel differences.
|
||||
// Subsample both directions by 4.
|
||||
for (size_t iy = 0; iy < ylen; ++iy) {
|
||||
size_t y = y0 + iy;
|
||||
const float* row_in = input.Row(y);
|
||||
const float* row_in1 = input.Row(y + 1);
|
||||
const float* row_in2 = input.Row(y - 1);
|
||||
float* JXL_RESTRICT row_out = diff_buffer;
|
||||
const auto match_gamma_offset_v = Set(df, match_gamma_offset);
|
||||
const auto quarter = Set(df, 0.25f);
|
||||
for (size_t x = 0; x < xsize; x += Lanes(df)) {
|
||||
const auto in = LoadU(df, row_in + x);
|
||||
const auto in_r = LoadU(df, row_in + x + 1);
|
||||
const auto in_l = LoadU(df, row_in + x - 1);
|
||||
const auto in_t = LoadU(df, row_in2 + x);
|
||||
const auto in_b = LoadU(df, row_in1 + x);
|
||||
const auto base = Mul(quarter, Add(Add(in_r, in_l), Add(in_t, in_b)));
|
||||
const auto gammacv =
|
||||
RatioOfDerivativesOfCubicRootToSimpleGamma</*invert=*/false>(
|
||||
df, Add(in, match_gamma_offset_v));
|
||||
auto diff = Mul(gammacv, Sub(in, base));
|
||||
diff = Mul(diff, diff);
|
||||
diff = Min(diff, Set(df, limit));
|
||||
diff = MaskingSqrt(df, diff);
|
||||
if ((iy & 3) != 0) {
|
||||
diff = Add(diff, LoadU(df, row_out + x));
|
||||
}
|
||||
StoreU(diff, df, row_out + x);
|
||||
}
|
||||
if (iy % 4 == 3) {
|
||||
size_t y_out = y0_out + iy / 4;
|
||||
float* row_dout = pre_erosion->Row(y_out);
|
||||
for (size_t x = 0; x < xsize_out; x++) {
|
||||
row_dout[x] = (row_out[x * 4] + row_out[x * 4 + 1] +
|
||||
row_out[x * 4 + 2] + row_out[x * 4 + 3]) *
|
||||
0.25f;
|
||||
}
|
||||
pre_erosion->PadRow(y_out, xsize_out, border);
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeTile(float butteraugli_target, float scale,
|
||||
const jxl::ImageF& xyb_y, const jxl::Rect& rect,
|
||||
const int thread) {
|
||||
const size_t xsize = xyb_y.xsize();
|
||||
const size_t ysize = xyb_y.ysize();
|
||||
|
||||
// The XYB gamma is 3.0 to be able to decode faster with two muls.
|
||||
// Butteraugli's gamma is matching the gamma of human eye, around 2.6.
|
||||
// We approximate the gamma difference by adding one cubic root into
|
||||
// the adaptive quantization. This gives us a total gamma of 2.6666
|
||||
// for quantization uses.
|
||||
const float match_gamma_offset = 0.019;
|
||||
|
||||
const HWY_FULL(float) df;
|
||||
|
||||
size_t y_start = rect.y0() * 8;
|
||||
size_t y_end = y_start + rect.ysize() * 8;
|
||||
|
||||
size_t x0 = rect.x0() * 8;
|
||||
size_t x1 = x0 + rect.xsize() * 8;
|
||||
if (x0 != 0) x0 -= 4;
|
||||
if (x1 != xyb_y.xsize()) x1 += 4;
|
||||
if (y_start != 0) y_start -= 4;
|
||||
if (y_end != xyb_y.ysize()) y_end += 4;
|
||||
pre_erosion[thread].ShrinkTo((x1 - x0) / 4, (y_end - y_start) / 4);
|
||||
|
||||
static const float limit = 0.2f;
|
||||
// Computes image (padded to multiple of 8x8) of local pixel differences.
|
||||
// Subsample both directions by 4.
|
||||
for (size_t y = y_start; y < y_end; ++y) {
|
||||
size_t y2 = y + 1 < ysize ? y + 1 : y;
|
||||
size_t y1 = y > 0 ? y - 1 : y;
|
||||
|
||||
const float* row_in = xyb_y.Row(y);
|
||||
const float* row_in1 = xyb_y.Row(y1);
|
||||
const float* row_in2 = xyb_y.Row(y2);
|
||||
float* JXL_RESTRICT row_out = diff_buffer.Row(thread);
|
||||
|
||||
auto scalar_pixel = [&](size_t x) {
|
||||
const size_t x2 = x + 1 < xsize ? x + 1 : x;
|
||||
const size_t x1 = x > 0 ? x - 1 : x;
|
||||
const float base =
|
||||
0.25f * (row_in2[x] + row_in1[x] + row_in[x1] + row_in[x2]);
|
||||
const float gammac = RatioOfDerivativesOfCubicRootToSimpleGamma(
|
||||
row_in[x] + match_gamma_offset);
|
||||
float diff = gammac * (row_in[x] - base);
|
||||
diff *= diff;
|
||||
if (diff >= limit) {
|
||||
diff = limit;
|
||||
}
|
||||
diff = MaskingSqrt(diff);
|
||||
if ((y % 4) != 0) {
|
||||
row_out[x - x0] += diff;
|
||||
} else {
|
||||
row_out[x - x0] = diff;
|
||||
}
|
||||
};
|
||||
|
||||
size_t x = x0;
|
||||
// First pixel of the row.
|
||||
if (x0 == 0) {
|
||||
scalar_pixel(x0);
|
||||
++x;
|
||||
}
|
||||
// SIMD
|
||||
const auto match_gamma_offset_v = Set(df, match_gamma_offset);
|
||||
const auto quarter = Set(df, 0.25f);
|
||||
for (; x + 1 + Lanes(df) < x1; x += Lanes(df)) {
|
||||
const auto in = LoadU(df, row_in + x);
|
||||
const auto in_r = LoadU(df, row_in + x + 1);
|
||||
const auto in_l = LoadU(df, row_in + x - 1);
|
||||
const auto in_t = LoadU(df, row_in2 + x);
|
||||
const auto in_b = LoadU(df, row_in1 + x);
|
||||
auto base = Mul(quarter, Add(Add(in_r, in_l), Add(in_t, in_b)));
|
||||
auto gammacv =
|
||||
RatioOfDerivativesOfCubicRootToSimpleGamma</*invert=*/false>(
|
||||
df, Add(in, match_gamma_offset_v));
|
||||
auto diff = Mul(gammacv, Sub(in, base));
|
||||
diff = Mul(diff, diff);
|
||||
diff = Min(diff, Set(df, limit));
|
||||
diff = MaskingSqrt(df, diff);
|
||||
if ((y & 3) != 0) {
|
||||
diff = Add(diff, LoadU(df, row_out + x - x0));
|
||||
}
|
||||
StoreU(diff, df, row_out + x - x0);
|
||||
}
|
||||
// Scalar
|
||||
for (; x < x1; ++x) {
|
||||
scalar_pixel(x);
|
||||
}
|
||||
if (y % 4 == 3) {
|
||||
float* row_dout = pre_erosion[thread].Row((y - y_start) / 4);
|
||||
for (size_t x = 0; x < (x1 - x0) / 4; x++) {
|
||||
row_dout[x] = (row_out[x * 4] + row_out[x * 4 + 1] +
|
||||
row_out[x * 4 + 2] + row_out[x * 4 + 3]) *
|
||||
0.25f;
|
||||
}
|
||||
}
|
||||
}
|
||||
jxl::Rect from_rect(x0 % 8 == 0 ? 0 : 1, y_start % 8 == 0 ? 0 : 1,
|
||||
rect.xsize() * 2, rect.ysize() * 2);
|
||||
FuzzyErosion(from_rect, pre_erosion[thread], rect, &aq_map);
|
||||
PerBlockModulations(butteraugli_target, xyb_y, scale, rect, &aq_map);
|
||||
}
|
||||
std::vector<jxl::ImageF> pre_erosion;
|
||||
jxl::ImageF aq_map;
|
||||
jxl::ImageF diff_buffer;
|
||||
};
|
||||
|
||||
jxl::ImageF AdaptiveQuantizationMap(const float butteraugli_target,
|
||||
const jxl::ImageF& xyb_y, float scale,
|
||||
jxl::ThreadPool* pool) {
|
||||
AdaptiveQuantizationImpl impl;
|
||||
impl.Init(xyb_y);
|
||||
const size_t xsize_blocks = xyb_y.xsize() / jxl::kBlockDim;
|
||||
const size_t ysize_blocks = xyb_y.ysize() / jxl::kBlockDim;
|
||||
JXL_CHECK(RunOnPool(
|
||||
pool, 0,
|
||||
DivCeil(xsize_blocks, kEncTileDimInBlocks) *
|
||||
DivCeil(ysize_blocks, kEncTileDimInBlocks),
|
||||
[&](const size_t num_threads) {
|
||||
impl.PrepareBuffers(num_threads);
|
||||
return true;
|
||||
},
|
||||
[&](const uint32_t tid, const size_t thread) {
|
||||
size_t n_enc_tiles = DivCeil(xsize_blocks, kEncTileDimInBlocks);
|
||||
size_t tx = tid % n_enc_tiles;
|
||||
size_t ty = tid / n_enc_tiles;
|
||||
size_t by0 = ty * kEncTileDimInBlocks;
|
||||
size_t by1 = std::min((ty + 1) * kEncTileDimInBlocks, ysize_blocks);
|
||||
size_t bx0 = tx * kEncTileDimInBlocks;
|
||||
size_t bx1 = std::min((tx + 1) * kEncTileDimInBlocks, xsize_blocks);
|
||||
jxl::Rect r(bx0, by0, bx1 - bx0, by1 - by0);
|
||||
impl.ComputeTile(butteraugli_target, scale, xyb_y, r, thread);
|
||||
},
|
||||
"AQ DiffPrecompute"));
|
||||
|
||||
return std::move(impl).aq_map;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
@ -614,72 +498,63 @@ HWY_AFTER_NAMESPACE();
|
|||
|
||||
#if HWY_ONCE
|
||||
namespace jpegli {
|
||||
HWY_EXPORT(AdaptiveQuantizationMap);
|
||||
HWY_EXPORT(ComputePreErosion);
|
||||
HWY_EXPORT(FuzzyErosion);
|
||||
HWY_EXPORT(PerBlockModulations);
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kDcQuantPow = 0.66f;
|
||||
static const float kDcQuant = 1.1f;
|
||||
static const float kAcQuant = 0.841f;
|
||||
static constexpr int kPreErosionBorder = 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
float InitialQuantDC(float butteraugli_target) {
|
||||
const float kDcMul = 1.5; // Butteraugli target where non-linearity kicks in.
|
||||
const float butteraugli_target_dc = std::max<float>(
|
||||
0.5f * butteraugli_target,
|
||||
std::min<float>(butteraugli_target,
|
||||
kDcMul * std::pow((1.0f / kDcMul) * butteraugli_target,
|
||||
kDcQuantPow)));
|
||||
// We want the maximum DC value to be at most 2**15 * kInvDCQuant / quant_dc.
|
||||
// The maximum DC value might not be in the kXybRange because of inverse
|
||||
// gaborish, so we add some slack to the maximum theoretical quant obtained
|
||||
// this way (64).
|
||||
return std::min(kDcQuant / butteraugli_target_dc, 50.f);
|
||||
}
|
||||
|
||||
jxl::ImageF InitialQuantField(const float butteraugli_target,
|
||||
const jxl::ImageF& opsin_y, jxl::ThreadPool* pool,
|
||||
float rescale) {
|
||||
const float quant_ac = kAcQuant / butteraugli_target;
|
||||
return HWY_DYNAMIC_DISPATCH(AdaptiveQuantizationMap)(
|
||||
butteraugli_target, opsin_y, quant_ac * rescale, pool);
|
||||
}
|
||||
|
||||
void ComputeAdaptiveQuantField(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
const size_t xsize_blocks = DivCeil(cinfo->image_width, DCTSIZE);
|
||||
const size_t ysize_blocks = DivCeil(cinfo->image_height, DCTSIZE);
|
||||
if (!m->use_adaptive_quantization) {
|
||||
return;
|
||||
}
|
||||
int y_channel = cinfo->jpeg_color_space == JCS_RGB ? 1 : 0;
|
||||
jpeg_component_info* y_comp = &cinfo->comp_info[y_channel];
|
||||
m->quant_field.Allocate(ysize_blocks, xsize_blocks);
|
||||
if (m->use_adaptive_quantization &&
|
||||
y_comp->h_samp_factor == cinfo->max_h_samp_factor &&
|
||||
y_comp->v_samp_factor == cinfo->max_v_samp_factor) {
|
||||
JXL_ASSERT(y_comp->width_in_blocks == xsize_blocks);
|
||||
JXL_ASSERT(y_comp->height_in_blocks == ysize_blocks);
|
||||
jxl::ImageF input(y_comp->width_in_blocks * DCTSIZE,
|
||||
y_comp->height_in_blocks * DCTSIZE);
|
||||
for (size_t y = 0; y < input.ysize(); ++y) {
|
||||
memcpy(input.Row(y), m->input_buffer[y_channel].Row(y),
|
||||
input.xsize() * sizeof(float));
|
||||
}
|
||||
jxl::ImageF qf =
|
||||
jpegli::InitialQuantField(m->distance, input, nullptr, m->distance);
|
||||
float qfmin, qfmax;
|
||||
ImageMinMax(qf, &qfmin, &qfmax);
|
||||
m->quant_field_max = qfmax;
|
||||
for (size_t y = 0; y < y_comp->height_in_blocks; ++y) {
|
||||
const float* row_in = qf.Row(y);
|
||||
float* row_out = m->quant_field.Row(y);
|
||||
for (size_t x = 0; x < y_comp->width_in_blocks; ++x) {
|
||||
row_out[x] = (qfmax / row_in[x]) - 1.0f;
|
||||
}
|
||||
}
|
||||
int y_quant_01 = cinfo->quant_tbl_ptrs[y_comp->quant_tbl_no]->quantval[1];
|
||||
if (m->next_iMCU_row == 0) {
|
||||
m->input_buffer[y_channel].CopyRow(-1, 0, 1);
|
||||
}
|
||||
if (m->next_iMCU_row + 1 == cinfo->total_iMCU_rows) {
|
||||
size_t last_row = m->ysize_blocks * DCTSIZE - 1;
|
||||
m->input_buffer[y_channel].CopyRow(last_row + 1, last_row, 1);
|
||||
}
|
||||
const RowBuffer<float>& input = m->input_buffer[y_channel];
|
||||
const size_t xsize_blocks = y_comp->width_in_blocks;
|
||||
const size_t xsize = xsize_blocks * DCTSIZE;
|
||||
const size_t yb0 = m->next_iMCU_row * cinfo->max_v_samp_factor;
|
||||
const size_t yblen = cinfo->max_v_samp_factor;
|
||||
size_t y0 = yb0 * DCTSIZE;
|
||||
size_t ylen = cinfo->max_v_samp_factor * DCTSIZE;
|
||||
if (y0 == 0) {
|
||||
ylen += 4;
|
||||
} else {
|
||||
m->quant_field_max = kDefaultQuantFieldMax;
|
||||
for (size_t y = 0; y < ysize_blocks; ++y) {
|
||||
m->quant_field.FillRow(y, 0.0f, xsize_blocks);
|
||||
y0 += 4;
|
||||
}
|
||||
if (m->next_iMCU_row + 1 == cinfo->total_iMCU_rows) {
|
||||
ylen -= 4;
|
||||
}
|
||||
HWY_DYNAMIC_DISPATCH(ComputePreErosion)
|
||||
(input, xsize, y0, ylen, kPreErosionBorder, m->diff_buffer, &m->pre_erosion);
|
||||
if (y0 == 0) {
|
||||
m->pre_erosion.CopyRow(-1, 0, kPreErosionBorder);
|
||||
}
|
||||
if (m->next_iMCU_row + 1 == cinfo->total_iMCU_rows) {
|
||||
size_t last_row = m->ysize_blocks * 2 - 1;
|
||||
m->pre_erosion.CopyRow(last_row + 1, last_row, kPreErosionBorder);
|
||||
}
|
||||
HWY_DYNAMIC_DISPATCH(FuzzyErosion)
|
||||
(m->pre_erosion, yb0, yblen, &m->fuzzy_erosion_tmp, &m->quant_field);
|
||||
HWY_DYNAMIC_DISPATCH(PerBlockModulations)
|
||||
(y_quant_01, input, yb0, yblen, &m->quant_field);
|
||||
for (int y = 0; y < cinfo->max_v_samp_factor; ++y) {
|
||||
float* row = m->quant_field.Row(yb0 + y);
|
||||
for (size_t x = 0; x < xsize_blocks; ++x) {
|
||||
row[x] = std::max(0.0f, (0.6f / row[x]) - 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,8 +16,6 @@ namespace jpegli {
|
|||
|
||||
void ComputeAdaptiveQuantField(j_compress_ptr cinfo);
|
||||
|
||||
float InitialQuantDC(float butteraugli_target);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_ADAPTIVE_QUANTIZATION_H_
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/bit_writer.h"
|
||||
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void JpegBitWriterInit(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
JpegBitWriter* bw = &m->bw;
|
||||
size_t buffer_size = m->blocks_per_iMCU_row * (DCTSIZE2 * 16 + 8) + (1 << 16);
|
||||
bw->cinfo = cinfo;
|
||||
bw->data = Allocate<uint8_t>(cinfo, buffer_size, JPOOL_IMAGE);
|
||||
bw->len = buffer_size;
|
||||
bw->pos = 0;
|
||||
bw->output_pos = 0;
|
||||
bw->put_buffer = 0;
|
||||
bw->free_bits = 64;
|
||||
bw->healthy = true;
|
||||
}
|
||||
|
||||
bool EmptyBitWriterBuffer(JpegBitWriter* bw) {
|
||||
while (bw->output_pos < bw->pos) {
|
||||
j_compress_ptr cinfo = bw->cinfo;
|
||||
if (cinfo->dest->free_in_buffer == 0 &&
|
||||
!(*cinfo->dest->empty_output_buffer)(cinfo)) {
|
||||
return false;
|
||||
}
|
||||
size_t buflen = bw->pos - bw->output_pos;
|
||||
size_t copylen = std::min<size_t>(cinfo->dest->free_in_buffer, buflen);
|
||||
memcpy(cinfo->dest->next_output_byte, bw->data + bw->output_pos, copylen);
|
||||
bw->output_pos += copylen;
|
||||
cinfo->dest->free_in_buffer -= copylen;
|
||||
cinfo->dest->next_output_byte += copylen;
|
||||
}
|
||||
bw->output_pos = bw->pos = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void JumpToByteBoundary(JpegBitWriter* bw) {
|
||||
size_t n_bits = bw->free_bits & 7u;
|
||||
if (n_bits > 0) {
|
||||
WriteBits(bw, n_bits, (1u << n_bits) - 1);
|
||||
}
|
||||
bw->put_buffer <<= bw->free_bits;
|
||||
while (bw->free_bits <= 56) {
|
||||
int c = (bw->put_buffer >> 56) & 0xFF;
|
||||
EmitByte(bw, c);
|
||||
bw->put_buffer <<= 8;
|
||||
bw->free_bits += 8;
|
||||
}
|
||||
bw->put_buffer = 0;
|
||||
bw->free_bits = 64;
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
|
@ -0,0 +1,107 @@
|
|||
// 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_JPEGLI_BIT_WRITER_H_
|
||||
#define LIB_JPEGLI_BIT_WRITER_H_
|
||||
|
||||
/* clang-format off */
|
||||
#include <stdio.h>
|
||||
#include <jpeglib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
/* clang-format on */
|
||||
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
// Handles the packing of bits into output bytes.
|
||||
struct JpegBitWriter {
|
||||
j_compress_ptr cinfo;
|
||||
uint8_t* data;
|
||||
size_t len;
|
||||
size_t pos;
|
||||
size_t output_pos;
|
||||
uint64_t put_buffer;
|
||||
int free_bits;
|
||||
bool healthy;
|
||||
};
|
||||
|
||||
void JpegBitWriterInit(j_compress_ptr cinfo);
|
||||
|
||||
bool EmptyBitWriterBuffer(JpegBitWriter* bw);
|
||||
|
||||
void JumpToByteBoundary(JpegBitWriter* bw);
|
||||
|
||||
// Returns non-zero if and only if x has a zero byte, i.e. one of
|
||||
// x & 0xff, x & 0xff00, ..., x & 0xff00000000000000 is zero.
|
||||
static JXL_INLINE uint64_t HasZeroByte(uint64_t x) {
|
||||
return (x - 0x0101010101010101ULL) & ~x & 0x8080808080808080ULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given byte to the output, writes an extra zero if byte is 0xFF.
|
||||
*
|
||||
* This method is "careless" - caller must make sure that there is enough
|
||||
* space in the output buffer. Emits up to 2 bytes to buffer.
|
||||
*/
|
||||
static JXL_INLINE void EmitByte(JpegBitWriter* bw, int byte) {
|
||||
bw->data[bw->pos++] = byte;
|
||||
if (byte == 0xFF) bw->data[bw->pos++] = 0;
|
||||
}
|
||||
|
||||
static JXL_INLINE void DischargeBitBuffer(JpegBitWriter* bw) {
|
||||
// At this point we are ready to emit the bytes of put_buffer to the output.
|
||||
// The JPEG format requires that after every 0xff byte in the entropy
|
||||
// coded section, there is a zero byte, therefore we first check if any of
|
||||
// the bytes of put_buffer is 0xFF.
|
||||
if (HasZeroByte(~bw->put_buffer)) {
|
||||
// We have a 0xFF byte somewhere, examine each byte and append a zero
|
||||
// byte if necessary.
|
||||
EmitByte(bw, (bw->put_buffer >> 56) & 0xFF);
|
||||
EmitByte(bw, (bw->put_buffer >> 48) & 0xFF);
|
||||
EmitByte(bw, (bw->put_buffer >> 40) & 0xFF);
|
||||
EmitByte(bw, (bw->put_buffer >> 32) & 0xFF);
|
||||
EmitByte(bw, (bw->put_buffer >> 24) & 0xFF);
|
||||
EmitByte(bw, (bw->put_buffer >> 16) & 0xFF);
|
||||
EmitByte(bw, (bw->put_buffer >> 8) & 0xFF);
|
||||
EmitByte(bw, (bw->put_buffer >> 0) & 0xFF);
|
||||
} else {
|
||||
// We don't have any 0xFF bytes, output all 6 bytes without checking.
|
||||
bw->data[bw->pos] = (bw->put_buffer >> 56) & 0xFF;
|
||||
bw->data[bw->pos + 1] = (bw->put_buffer >> 48) & 0xFF;
|
||||
bw->data[bw->pos + 2] = (bw->put_buffer >> 40) & 0xFF;
|
||||
bw->data[bw->pos + 3] = (bw->put_buffer >> 32) & 0xFF;
|
||||
bw->data[bw->pos + 4] = (bw->put_buffer >> 24) & 0xFF;
|
||||
bw->data[bw->pos + 5] = (bw->put_buffer >> 16) & 0xFF;
|
||||
bw->data[bw->pos + 6] = (bw->put_buffer >> 8) & 0xFF;
|
||||
bw->data[bw->pos + 7] = (bw->put_buffer >> 0) & 0xFF;
|
||||
bw->pos += 8;
|
||||
}
|
||||
}
|
||||
|
||||
static JXL_INLINE void WriteBits(JpegBitWriter* bw, int nbits, uint64_t bits) {
|
||||
// This is an optimization; if everything goes well,
|
||||
// then |nbits| is positive; if non-existing Huffman symbol is going to be
|
||||
// encoded, its length should be zero; later encoder could check the
|
||||
// "health" of JpegBitWriter.
|
||||
if (nbits == 0) {
|
||||
bw->healthy = false;
|
||||
return;
|
||||
}
|
||||
bw->free_bits -= nbits;
|
||||
if (bw->free_bits < 0) {
|
||||
bw->put_buffer <<= (bw->free_bits + nbits);
|
||||
bw->put_buffer |= (bits >> -bw->free_bits);
|
||||
DischargeBitBuffer(bw);
|
||||
bw->free_bits += 64;
|
||||
bw->put_buffer = nbits;
|
||||
}
|
||||
bw->put_buffer <<= nbits;
|
||||
bw->put_buffer |= bits;
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
#endif // LIB_JPEGLI_BIT_WRITER_H_
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -19,19 +19,18 @@ void WriteOutput(j_compress_ptr cinfo, std::initializer_list<uint8_t> bytes);
|
|||
|
||||
void EncodeAPP0(j_compress_ptr cinfo);
|
||||
void EncodeAPP14(j_compress_ptr cinfo);
|
||||
void EncodeSOF(j_compress_ptr cinfo);
|
||||
void EncodeSOF(j_compress_ptr cinfo, bool is_baseline);
|
||||
void EncodeSOS(j_compress_ptr cinfo, int scan_index);
|
||||
void EncodeDHT(j_compress_ptr cinfo, const JPEGHuffmanCode* huffman_codes,
|
||||
size_t num_huffman_codes);
|
||||
void EncodeDQT(j_compress_ptr cinfo);
|
||||
size_t num_huffman_codes, bool pre_shifted = false);
|
||||
void EncodeDQT(j_compress_ptr cinfo, bool write_all_tables, bool* is_baseline);
|
||||
bool EncodeDRI(j_compress_ptr cinfo);
|
||||
|
||||
bool EncodeScan(j_compress_ptr cinfo,
|
||||
const std::vector<std::vector<coeff_t>>& coeffs,
|
||||
int scan_index);
|
||||
bool EncodeScan(j_compress_ptr cinfo, int scan_index);
|
||||
|
||||
void EncodeSingleScan(j_compress_ptr cinfo,
|
||||
const std::vector<std::vector<coeff_t>>& coeffs);
|
||||
void EncodeSingleScan(j_compress_ptr cinfo);
|
||||
|
||||
void WriteiMCURow(j_compress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
|
|
|
@ -0,0 +1,533 @@
|
|||
// 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/jpegli/color_quantize.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "lib/jpegli/decode_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
namespace {
|
||||
|
||||
static constexpr int kNumColorCellBits[kMaxComponents] = {3, 4, 3, 3};
|
||||
static constexpr int kCompW[kMaxComponents] = {2, 3, 1, 1};
|
||||
|
||||
int Pow(int a, int b) {
|
||||
int r = 1;
|
||||
for (int i = 0; i < b; ++i) {
|
||||
r *= a;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int ComponentOrder(j_decompress_ptr cinfo, int i) {
|
||||
if (cinfo->out_color_components == 3) {
|
||||
return i < 2 ? 1 - i : i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
int GetColorComponent(int i, int N) {
|
||||
return (i * 255 + (N - 1) / 2) / (N - 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ChooseColorMap1Pass(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
int components = cinfo->out_color_components;
|
||||
int desired = std::min(cinfo->desired_number_of_colors, 256);
|
||||
int num = 1;
|
||||
while (Pow(num + 1, components) <= desired) {
|
||||
++num;
|
||||
}
|
||||
if (num == 1) {
|
||||
JPEGLI_ERROR("Too few colors (%d) in requested colormap", desired);
|
||||
}
|
||||
int actual = Pow(num, components);
|
||||
for (int i = 0; i < components; ++i) {
|
||||
m->num_colors_[i] = num;
|
||||
}
|
||||
while (actual < desired) {
|
||||
int total = actual;
|
||||
for (int i = 0; i < components; ++i) {
|
||||
int c = ComponentOrder(cinfo, i);
|
||||
int new_total = (actual / m->num_colors_[c]) * (m->num_colors_[c] + 1);
|
||||
if (new_total <= desired) {
|
||||
++m->num_colors_[c];
|
||||
actual = new_total;
|
||||
}
|
||||
}
|
||||
if (actual == total) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
cinfo->actual_number_of_colors = actual;
|
||||
cinfo->colormap = (*cinfo->mem->alloc_sarray)(
|
||||
reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, actual, components);
|
||||
int next_color[kMaxComponents] = {0};
|
||||
for (int i = 0; i < actual; ++i) {
|
||||
for (int c = 0; c < components; ++c) {
|
||||
cinfo->colormap[c][i] =
|
||||
GetColorComponent(next_color[c], m->num_colors_[c]);
|
||||
}
|
||||
int c = components - 1;
|
||||
while (c > 0 && next_color[c] + 1 == m->num_colors_[c]) {
|
||||
next_color[c--] = 0;
|
||||
}
|
||||
++next_color[c];
|
||||
}
|
||||
if (!m->colormap_lut_) {
|
||||
m->colormap_lut_ = Allocate<uint8_t>(cinfo, components * 256, JPOOL_IMAGE);
|
||||
}
|
||||
int stride = actual;
|
||||
for (int c = 0; c < components; ++c) {
|
||||
int N = m->num_colors_[c];
|
||||
stride /= N;
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
int index = ((2 * i - 1) * (N - 1) + 254) / 510;
|
||||
m->colormap_lut_[c * 256 + i] = index * stride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// 2^13 priority levels for the PQ seems to be a good compromise between
|
||||
// accuracy, running time and stack space usage.
|
||||
static const int kMaxPriority = 1 << 13;
|
||||
static const int kMaxLevel = 3;
|
||||
|
||||
// This function is used in the multi-resolution grid to be able to compute
|
||||
// the keys for the different resolutions by just shifting the first key.
|
||||
inline int InterlaceBitsRGB(uint8_t r, uint8_t g, uint8_t b) {
|
||||
int z = 0;
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
z += (r >> 5) & 4;
|
||||
z += (g >> 6) & 2;
|
||||
z += (b >> 7);
|
||||
z <<= 3;
|
||||
r <<= 1;
|
||||
g <<= 1;
|
||||
b <<= 1;
|
||||
}
|
||||
z += (r >> 5) & 4;
|
||||
z += (g >> 6) & 2;
|
||||
z += (b >> 7);
|
||||
return z;
|
||||
}
|
||||
|
||||
// This function will compute the actual priorities of the colors based on
|
||||
// the current distance from the palette, the population count and the signals
|
||||
// from the multi-resolution grid.
|
||||
inline int Priority(int d, int n, const int* density, const int* radius) {
|
||||
int p = d * n;
|
||||
for (int level = 0; level < kMaxLevel; ++level) {
|
||||
if (d > radius[level]) {
|
||||
p += density[level] * (d - radius[level]);
|
||||
}
|
||||
}
|
||||
return std::min(kMaxPriority - 1, p >> 4);
|
||||
}
|
||||
|
||||
inline int ColorIntQuadDistanceRGB(uint8_t r1, uint8_t g1, uint8_t b1,
|
||||
uint8_t r2, uint8_t g2, uint8_t b2) {
|
||||
// weights for the intensity calculation
|
||||
static constexpr int ired = 2;
|
||||
static constexpr int igreen = 5;
|
||||
static constexpr int iblue = 1;
|
||||
// normalization factor for the intensity calculation (2^ishift)
|
||||
static constexpr int ishift = 3;
|
||||
const int rd = r1 - r2;
|
||||
const int gd = g1 - g2;
|
||||
const int bd = b1 - b2;
|
||||
const int id = ired * rd + igreen * gd + iblue * bd;
|
||||
return rd * rd + gd * gd + bd * bd + ((id * id) >> (2 * ishift));
|
||||
}
|
||||
|
||||
inline int ScaleQuadDistanceRGB(int d) {
|
||||
return static_cast<int>(sqrt(d * 0.25) + 0.5);
|
||||
}
|
||||
|
||||
// The function updates the minimal distances, the clustering and the
|
||||
// quantization error after the insertion of the new color into the palette.
|
||||
void AddToRGBPalette(const uint8_t* red, const uint8_t* green,
|
||||
const uint8_t* blue,
|
||||
const int* count, // histogram of colors
|
||||
const int index, // index of color to be added
|
||||
const int k, // size of current palette
|
||||
const int n, // number of colors
|
||||
int* dist, // array of distances from palette
|
||||
int* cluster, // mapping of color indices to palette
|
||||
int* center, // the inverse mapping
|
||||
int64_t* error) { // measure of the quantization error
|
||||
center[k] = index;
|
||||
cluster[index] = k;
|
||||
*error -=
|
||||
static_cast<int64_t>(dist[index]) * static_cast<int64_t>(count[index]);
|
||||
dist[index] = 0;
|
||||
for (int j = 0; j < n; ++j) {
|
||||
if (dist[j] > 0) {
|
||||
const int d = ColorIntQuadDistanceRGB(
|
||||
red[index], green[index], blue[index], red[j], green[j], blue[j]);
|
||||
if (d < dist[j]) {
|
||||
*error += static_cast<int64_t>((d - dist[j])) *
|
||||
static_cast<int64_t>(count[j]);
|
||||
dist[j] = d;
|
||||
cluster[j] = k;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RGBPixelHasher {
|
||||
// A quick but good-enough hash to get 24 bits of RGB into the lower 12 bits.
|
||||
size_t operator()(uint32_t a) const { return (a ^ (a >> 12)) * 0x9e3779b9; }
|
||||
};
|
||||
|
||||
struct WangHasher {
|
||||
// Thomas Wang's Hash. Nearly perfect and still quite fast. Above (for
|
||||
// pixels) we use a simpler hash because the number of hash calls is
|
||||
// proportional to the number of pixels and that hash dominates; we want the
|
||||
// cost to be minimal and we start with a large table. We can use a better
|
||||
// hash for the histogram since the number of hash calls is proportional to
|
||||
// the number of unique colors in the image, which is hopefully much smaller.
|
||||
// Note that the difference is slight; e.g. replacing RGBPixelHasher with
|
||||
// WangHasher only slows things down by 5% on an Opteron.
|
||||
size_t operator()(uint32_t a) const {
|
||||
a = (a ^ 61) ^ (a >> 16);
|
||||
a = a + (a << 3);
|
||||
a = a ^ (a >> 4);
|
||||
a = a * 0x27d4eb2d;
|
||||
a = a ^ (a >> 15);
|
||||
return a;
|
||||
}
|
||||
};
|
||||
|
||||
// Build an index of all the different colors in the input
|
||||
// image. To do this we map the 24 bit RGB representation of the colors
|
||||
// to a unique integer index assigned to the different colors in order of
|
||||
// appearence in the image. Return the number of unique colors found.
|
||||
// The colors are pre-quantized to 3 * 6 bits precision.
|
||||
static int BuildRGBColorIndex(const uint8_t* const image, int const num_pixels,
|
||||
int* const count, uint8_t* const red,
|
||||
uint8_t* const green, uint8_t* const blue) {
|
||||
// Impossible because rgb are in the low 24 bits, and the upper 8 bits is 0.
|
||||
const uint32_t impossible_pixel_value = 0x10000000;
|
||||
std::unordered_map<uint32_t, int, RGBPixelHasher> index_map(1 << 12);
|
||||
std::unordered_map<uint32_t, int, RGBPixelHasher>::iterator index_map_lookup;
|
||||
const uint8_t* imagep = &image[0];
|
||||
uint32_t prev_pixel = impossible_pixel_value;
|
||||
int index = 0;
|
||||
int n = 0;
|
||||
for (int i = 0; i < num_pixels; ++i) {
|
||||
uint8_t r = ((*imagep++) & 0xfc) + 2;
|
||||
uint8_t g = ((*imagep++) & 0xfc) + 2;
|
||||
uint8_t b = ((*imagep++) & 0xfc) + 2;
|
||||
uint32_t pixel = (b << 16) | (g << 8) | r;
|
||||
if (pixel != prev_pixel) {
|
||||
prev_pixel = pixel;
|
||||
index_map_lookup = index_map.find(pixel);
|
||||
if (index_map_lookup != index_map.end()) {
|
||||
index = index_map_lookup->second;
|
||||
} else {
|
||||
index_map[pixel] = index = n++;
|
||||
red[index] = r;
|
||||
green[index] = g;
|
||||
blue[index] = b;
|
||||
}
|
||||
}
|
||||
++count[index];
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ChooseColorMap2Pass(j_decompress_ptr cinfo) {
|
||||
if (cinfo->out_color_space != JCS_RGB) {
|
||||
JPEGLI_ERROR("Two-pass quantizer must use RGB output color space.");
|
||||
}
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
const size_t num_pixels = cinfo->output_width * cinfo->output_height;
|
||||
const int max_color_count = std::max<size_t>(num_pixels, 1u << 18);
|
||||
const int max_palette_size = cinfo->desired_number_of_colors;
|
||||
std::unique_ptr<uint8_t[]> red(new uint8_t[max_color_count]);
|
||||
std::unique_ptr<uint8_t[]> green(new uint8_t[max_color_count]);
|
||||
std::unique_ptr<uint8_t[]> blue(new uint8_t[max_color_count]);
|
||||
std::vector<int> count(max_color_count, 0);
|
||||
// number of colors
|
||||
int n = BuildRGBColorIndex(m->pixels_, num_pixels, &count[0], &red[0],
|
||||
&green[0], &blue[0]);
|
||||
|
||||
std::vector<int> dist(n, std::numeric_limits<int>::max());
|
||||
std::vector<int> cluster(n);
|
||||
std::vector<bool> in_palette(n, false);
|
||||
int center[256];
|
||||
int k = 0; // palette size
|
||||
const int count_threshold = (num_pixels * 4) / max_palette_size;
|
||||
static constexpr int kAveragePixelErrorThreshold = 1;
|
||||
const int64_t error_threshold = num_pixels * kAveragePixelErrorThreshold;
|
||||
int64_t error = 0; // quantization error
|
||||
|
||||
int max_count = 0;
|
||||
int winner = 0;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (count[i] > max_count) {
|
||||
max_count = count[i];
|
||||
winner = i;
|
||||
}
|
||||
if (!in_palette[i] && count[i] > count_threshold) {
|
||||
AddToRGBPalette(&red[0], &green[0], &blue[0], &count[0], i, k++, n,
|
||||
&dist[0], &cluster[0], ¢er[0], &error);
|
||||
in_palette[i] = true;
|
||||
}
|
||||
}
|
||||
if (k == 0) {
|
||||
AddToRGBPalette(&red[0], &green[0], &blue[0], &count[0], winner, k++, n,
|
||||
&dist[0], &cluster[0], ¢er[0], &error);
|
||||
in_palette[winner] = true;
|
||||
}
|
||||
|
||||
// Calculation of the multi-resolution density grid.
|
||||
std::vector<int> density(n * kMaxLevel);
|
||||
std::vector<int> radius(n * kMaxLevel);
|
||||
std::unordered_map<uint32_t, int, WangHasher> histogram[kMaxLevel];
|
||||
for (int level = 0; level < kMaxLevel; ++level) {
|
||||
// This value is never used because key = InterlaceBitsRGB(...) >> 6
|
||||
}
|
||||
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (!in_palette[i]) {
|
||||
const int key = InterlaceBitsRGB(red[i], green[i], blue[i]) >> 6;
|
||||
for (int level = 0; level < kMaxLevel; ++level) {
|
||||
histogram[level][key >> (3 * level)] += count[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (!in_palette[i]) {
|
||||
for (int level = 0; level < kMaxLevel; ++level) {
|
||||
const int mask = (4 << level) - 1;
|
||||
const int rd = std::max(red[i] & mask, mask - (red[i] & mask));
|
||||
const int gd = std::max(green[i] & mask, mask - (green[i] & mask));
|
||||
const int bd = std::max(blue[i] & mask, mask - (blue[i] & mask));
|
||||
radius[i * kMaxLevel + level] =
|
||||
ScaleQuadDistanceRGB(ColorIntQuadDistanceRGB(0, 0, 0, rd, gd, bd));
|
||||
}
|
||||
const int key = InterlaceBitsRGB(red[i], green[i], blue[i]) >> 6;
|
||||
if (kMaxLevel > 0) {
|
||||
density[i * kMaxLevel] = histogram[0][key] - count[i];
|
||||
}
|
||||
for (int level = 1; level < kMaxLevel; ++level) {
|
||||
density[i * kMaxLevel + level] =
|
||||
(histogram[level][key >> (3 * level)] -
|
||||
histogram[level - 1][key >> (3 * level - 3)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the initial error now that the palette has been initialized.
|
||||
error = 0;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
error += static_cast<int64_t>(dist[i]) * static_cast<int64_t>(count[i]);
|
||||
}
|
||||
|
||||
std::unique_ptr<std::vector<int>[]> bucket_array(
|
||||
new std::vector<int>[kMaxPriority]);
|
||||
int top_priority = -1;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (!in_palette[i]) {
|
||||
int priority = Priority(ScaleQuadDistanceRGB(dist[i]), count[i],
|
||||
&density[i * kMaxLevel], &radius[i * kMaxLevel]);
|
||||
bucket_array[priority].push_back(i);
|
||||
top_priority = std::max(priority, top_priority);
|
||||
}
|
||||
}
|
||||
double error_accum = 0;
|
||||
while (top_priority >= 0 && k < max_palette_size) {
|
||||
if (error < error_threshold) {
|
||||
error_accum += std::min(error_threshold, error_threshold - error);
|
||||
if (error_accum >= 10 * error_threshold) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
int i = bucket_array[top_priority].back();
|
||||
int priority = Priority(ScaleQuadDistanceRGB(dist[i]), count[i],
|
||||
&density[i * kMaxLevel], &radius[i * kMaxLevel]);
|
||||
if (priority < top_priority) {
|
||||
bucket_array[priority].push_back(i);
|
||||
} else {
|
||||
AddToRGBPalette(&red[0], &green[0], &blue[0], &count[0], i, k++, n,
|
||||
&dist[0], &cluster[0], ¢er[0], &error);
|
||||
}
|
||||
bucket_array[top_priority].pop_back();
|
||||
while (top_priority >= 0 && bucket_array[top_priority].empty()) {
|
||||
--top_priority;
|
||||
}
|
||||
}
|
||||
|
||||
cinfo->actual_number_of_colors = k;
|
||||
cinfo->colormap = (*cinfo->mem->alloc_sarray)(
|
||||
reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, k, 3);
|
||||
for (int i = 0; i < k; ++i) {
|
||||
int index = center[i];
|
||||
cinfo->colormap[0][i] = red[index];
|
||||
cinfo->colormap[1][i] = green[index];
|
||||
cinfo->colormap[2][i] = blue[index];
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void FindCandidatesForCell(j_decompress_ptr cinfo, int ncomp, int cell[],
|
||||
std::vector<uint8_t>* candidates) {
|
||||
int cell_min[kMaxComponents];
|
||||
int cell_max[kMaxComponents];
|
||||
int cell_center[kMaxComponents];
|
||||
for (int c = 0; c < ncomp; ++c) {
|
||||
cell_min[c] = cell[c] << (8 - kNumColorCellBits[c]);
|
||||
cell_max[c] = cell_min[c] + (1 << (8 - kNumColorCellBits[c])) - 1;
|
||||
cell_center[c] = (cell_min[c] + cell_max[c]) >> 1;
|
||||
}
|
||||
int min_maxdist = std::numeric_limits<int>::max();
|
||||
int mindist[256];
|
||||
for (int i = 0; i < cinfo->actual_number_of_colors; ++i) {
|
||||
int dmin = 0;
|
||||
int dmax = 0;
|
||||
for (int c = 0; c < ncomp; ++c) {
|
||||
int palette_c = cinfo->colormap[c][i];
|
||||
int dminc = 0, dmaxc;
|
||||
if (palette_c < cell_min[c]) {
|
||||
dminc = cell_min[c] - palette_c;
|
||||
dmaxc = cell_max[c] - palette_c;
|
||||
} else if (palette_c > cell_max[c]) {
|
||||
dminc = palette_c - cell_max[c];
|
||||
dmaxc = palette_c - cell_min[c];
|
||||
} else if (palette_c > cell_center[c]) {
|
||||
dmaxc = palette_c - cell_min[c];
|
||||
} else {
|
||||
dmaxc = cell_max[c] - palette_c;
|
||||
}
|
||||
dminc *= kCompW[c];
|
||||
dmaxc *= kCompW[c];
|
||||
dmin += dminc * dminc;
|
||||
dmax += dmaxc * dmaxc;
|
||||
}
|
||||
mindist[i] = dmin;
|
||||
min_maxdist = std::min(dmax, min_maxdist);
|
||||
}
|
||||
for (int i = 0; i < cinfo->actual_number_of_colors; ++i) {
|
||||
if (mindist[i] < min_maxdist) {
|
||||
candidates->push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CreateInverseColorMap(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
int ncomp = cinfo->out_color_components;
|
||||
int num_cells = 1;
|
||||
for (int c = 0; c < ncomp; ++c) {
|
||||
num_cells *= (1 << kNumColorCellBits[c]);
|
||||
}
|
||||
m->candidate_lists_.resize(num_cells);
|
||||
|
||||
int next_cell[kMaxComponents] = {0};
|
||||
for (int i = 0; i < num_cells; ++i) {
|
||||
m->candidate_lists_[i].clear();
|
||||
FindCandidatesForCell(cinfo, ncomp, next_cell, &m->candidate_lists_[i]);
|
||||
int c = ncomp - 1;
|
||||
while (c > 0 && next_cell[c] + 1 == (1 << kNumColorCellBits[c])) {
|
||||
next_cell[c--] = 0;
|
||||
}
|
||||
++next_cell[c];
|
||||
}
|
||||
m->regenerate_inverse_colormap_ = false;
|
||||
}
|
||||
|
||||
int LookupColorIndex(j_decompress_ptr cinfo, JSAMPLE* pixel) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
int num_channels = cinfo->out_color_components;
|
||||
int index = 0;
|
||||
if (m->quant_mode_ == 1) {
|
||||
for (int c = 0; c < num_channels; ++c) {
|
||||
index += m->colormap_lut_[c * 256 + pixel[c]];
|
||||
}
|
||||
} else {
|
||||
size_t cell_idx = 0;
|
||||
size_t stride = 1;
|
||||
for (int c = num_channels - 1; c >= 0; --c) {
|
||||
cell_idx += (pixel[c] >> (8 - kNumColorCellBits[c])) * stride;
|
||||
stride <<= kNumColorCellBits[c];
|
||||
}
|
||||
JXL_ASSERT(cell_idx < m->candidate_lists_.size());
|
||||
int mindist = std::numeric_limits<int>::max();
|
||||
const auto& candidates = m->candidate_lists_[cell_idx];
|
||||
for (uint8_t i : candidates) {
|
||||
int dist = 0;
|
||||
for (int c = 0; c < num_channels; ++c) {
|
||||
int d = (cinfo->colormap[c][i] - pixel[c]) * kCompW[c];
|
||||
dist += d * d;
|
||||
}
|
||||
if (dist < mindist) {
|
||||
mindist = dist;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
JXL_ASSERT(index < cinfo->actual_number_of_colors);
|
||||
return index;
|
||||
}
|
||||
|
||||
void CreateOrderedDitherTables(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
static constexpr size_t kDitherSize = 4;
|
||||
static constexpr size_t kDitherMask = kDitherSize - 1;
|
||||
static constexpr float kBaseDitherMatrix[] = {
|
||||
0, 8, 2, 10, //
|
||||
12, 4, 14, 6, //
|
||||
3, 11, 1, 9, //
|
||||
15, 7, 13, 5, //
|
||||
};
|
||||
m->dither_size_ = kDitherSize;
|
||||
m->dither_mask_ = kDitherMask;
|
||||
size_t ncells = m->dither_size_ * m->dither_size_;
|
||||
for (int c = 0; c < cinfo->out_color_components; ++c) {
|
||||
float spread = 1.0f / (m->num_colors_[c] - 1);
|
||||
float mul = spread / ncells;
|
||||
float offset = 0.5f * spread;
|
||||
if (m->dither_[c] == nullptr) {
|
||||
m->dither_[c] = Allocate<float>(cinfo, ncells, JPOOL_IMAGE_ALIGNED);
|
||||
}
|
||||
for (size_t idx = 0; idx < ncells; ++idx) {
|
||||
m->dither_[c][idx] = kBaseDitherMatrix[idx] * mul - offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitFSDitherState(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
for (int c = 0; c < cinfo->out_color_components; ++c) {
|
||||
if (m->error_row_[c] == nullptr) {
|
||||
m->error_row_[c] =
|
||||
Allocate<float>(cinfo, cinfo->output_width, JPOOL_IMAGE_ALIGNED);
|
||||
m->error_row_[c + kMaxComponents] =
|
||||
Allocate<float>(cinfo, cinfo->output_width, JPOOL_IMAGE_ALIGNED);
|
||||
}
|
||||
memset(m->error_row_[c], 0.0, cinfo->output_width * sizeof(float));
|
||||
memset(m->error_row_[c + kMaxComponents], 0.0,
|
||||
cinfo->output_width * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
|
@ -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_JPEGLI_COLOR_QUANTIZE_H_
|
||||
#define LIB_JPEGLI_COLOR_QUANTIZE_H_
|
||||
|
||||
/* clang-format off */
|
||||
#include <stdio.h>
|
||||
#include <jpeglib.h>
|
||||
/* clang-format on */
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void ChooseColorMap1Pass(j_decompress_ptr cinfo);
|
||||
|
||||
void ChooseColorMap2Pass(j_decompress_ptr cinfo);
|
||||
|
||||
void CreateInverseColorMap(j_decompress_ptr cinfo);
|
||||
|
||||
void CreateOrderedDitherTables(j_decompress_ptr cinfo);
|
||||
|
||||
void InitFSDitherState(j_decompress_ptr cinfo);
|
||||
|
||||
int LookupColorIndex(j_decompress_ptr cinfo, JSAMPLE* pixel);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_COLOR_QUANTIZE_H_
|
|
@ -10,6 +10,11 @@
|
|||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/jpegli/decode_internal.h"
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jpegli {
|
||||
namespace HWY_NAMESPACE {
|
||||
|
@ -21,50 +26,21 @@ using hwy::HWY_NAMESPACE::Mul;
|
|||
using hwy::HWY_NAMESPACE::MulAdd;
|
||||
using hwy::HWY_NAMESPACE::Sub;
|
||||
|
||||
void YCCKToCMYK(float* JXL_RESTRICT row0, float* JXL_RESTRICT row1,
|
||||
float* JXL_RESTRICT row2, float* JXL_RESTRICT row3,
|
||||
size_t xsize) {
|
||||
void YCbCrToRGB(float* row[kMaxComponents], size_t xsize) {
|
||||
const HWY_CAPPED(float, 8) df;
|
||||
float* JXL_RESTRICT row0 = row[0];
|
||||
float* JXL_RESTRICT row1 = row[1];
|
||||
float* JXL_RESTRICT row2 = row[2];
|
||||
|
||||
// Full-range BT.601 as defined by JFIF Clause 7:
|
||||
// https://www.itu.int/rec/T-REC-T.871-201105-I/en
|
||||
// After, C = 1-R, M = 1-G, Y = 1-B, K = K.
|
||||
const auto c128 = Set(df, 128.0f / 255);
|
||||
const auto crcr = Set(df, 1.402f);
|
||||
const auto cgcb = Set(df, -0.114f * 1.772f / 0.587f);
|
||||
const auto cgcr = Set(df, -0.299f * 1.402f / 0.587f);
|
||||
const auto cbcb = Set(df, 1.772f);
|
||||
const auto unity = Set(df, 1.0f);
|
||||
|
||||
for (size_t x = 0; x < xsize; x += Lanes(df)) {
|
||||
const auto y_vec = Add(Load(df, row0 + x), c128);
|
||||
const auto cb_vec = Load(df, row1 + x);
|
||||
const auto cr_vec = Load(df, row2 + x);
|
||||
const auto r_vec = Sub(unity, MulAdd(crcr, cr_vec, y_vec));
|
||||
const auto g_vec =
|
||||
Sub(unity, MulAdd(cgcr, cr_vec, MulAdd(cgcb, cb_vec, y_vec)));
|
||||
const auto b_vec = Sub(unity, MulAdd(cbcb, cb_vec, y_vec));
|
||||
Store(r_vec, df, row0 + x);
|
||||
Store(g_vec, df, row1 + x);
|
||||
Store(b_vec, df, row2 + x);
|
||||
Store(Add(Load(df, row3 + x), c128), df, row3 + x);
|
||||
}
|
||||
}
|
||||
|
||||
void YCbCrToRGB(float* JXL_RESTRICT row0, float* JXL_RESTRICT row1,
|
||||
float* JXL_RESTRICT row2, size_t xsize) {
|
||||
const HWY_CAPPED(float, 8) df;
|
||||
|
||||
// Full-range BT.601 as defined by JFIF Clause 7:
|
||||
// https://www.itu.int/rec/T-REC-T.871-201105-I/en
|
||||
const auto c128 = Set(df, 128.0f / 255);
|
||||
const auto crcr = Set(df, 1.402f);
|
||||
const auto cgcb = Set(df, -0.114f * 1.772f / 0.587f);
|
||||
const auto cgcr = Set(df, -0.299f * 1.402f / 0.587f);
|
||||
const auto cbcb = Set(df, 1.772f);
|
||||
|
||||
for (size_t x = 0; x < xsize; x += Lanes(df)) {
|
||||
const auto y_vec = Add(Load(df, row0 + x), c128);
|
||||
const auto y_vec = Load(df, row0 + x);
|
||||
const auto cb_vec = Load(df, row1 + x);
|
||||
const auto cr_vec = Load(df, row2 + x);
|
||||
const auto r_vec = MulAdd(crcr, cr_vec, y_vec);
|
||||
|
@ -76,13 +52,28 @@ void YCbCrToRGB(float* JXL_RESTRICT row0, float* JXL_RESTRICT row1,
|
|||
}
|
||||
}
|
||||
|
||||
void RGBToYCbCr(float* JXL_RESTRICT row0, float* JXL_RESTRICT row1,
|
||||
float* JXL_RESTRICT row2, size_t xsize) {
|
||||
void YCCKToCMYK(float* row[kMaxComponents], size_t xsize) {
|
||||
const HWY_CAPPED(float, 8) df;
|
||||
float* JXL_RESTRICT row0 = row[0];
|
||||
float* JXL_RESTRICT row1 = row[1];
|
||||
float* JXL_RESTRICT row2 = row[2];
|
||||
YCbCrToRGB(row, xsize);
|
||||
const auto offset = Set(df, -1.0f / 255.0f);
|
||||
for (size_t x = 0; x < xsize; x += Lanes(df)) {
|
||||
Store(Sub(offset, Load(df, row0 + x)), df, row0 + x);
|
||||
Store(Sub(offset, Load(df, row1 + x)), df, row1 + x);
|
||||
Store(Sub(offset, Load(df, row2 + x)), df, row2 + x);
|
||||
}
|
||||
}
|
||||
|
||||
void RGBToYCbCr(float* row[kMaxComponents], size_t xsize) {
|
||||
const HWY_CAPPED(float, 8) df;
|
||||
float* JXL_RESTRICT row0 = row[0];
|
||||
float* JXL_RESTRICT row1 = row[1];
|
||||
float* JXL_RESTRICT row2 = row[2];
|
||||
// Full-range BT.601 as defined by JFIF Clause 7:
|
||||
// https://www.itu.int/rec/T-REC-T.871-201105-I/en
|
||||
const auto c128 = Set(df, 128.0f / 255);
|
||||
const auto c128 = Set(df, 128.0f);
|
||||
const auto kR = Set(df, 0.299f); // NTSC luma
|
||||
const auto kG = Set(df, 0.587f);
|
||||
const auto kB = Set(df, 0.114f);
|
||||
|
@ -111,17 +102,18 @@ void RGBToYCbCr(float* JXL_RESTRICT row0, float* JXL_RESTRICT row1,
|
|||
}
|
||||
}
|
||||
|
||||
void CMYKToYCCK(float* JXL_RESTRICT row0, float* JXL_RESTRICT row1,
|
||||
float* JXL_RESTRICT row2, float* JXL_RESTRICT row3,
|
||||
size_t xsize) {
|
||||
void CMYKToYCCK(float* row[kMaxComponents], size_t xsize) {
|
||||
const HWY_CAPPED(float, 8) df;
|
||||
const auto unity = Set(df, 1.0f);
|
||||
float* JXL_RESTRICT row0 = row[0];
|
||||
float* JXL_RESTRICT row1 = row[1];
|
||||
float* JXL_RESTRICT row2 = row[2];
|
||||
const auto unity = Set(df, 255.0f);
|
||||
for (size_t x = 0; x < xsize; x += Lanes(df)) {
|
||||
Store(Sub(unity, Load(df, row0 + x)), df, row0 + x);
|
||||
Store(Sub(unity, Load(df, row1 + x)), df, row1 + x);
|
||||
Store(Sub(unity, Load(df, row2 + x)), df, row2 + x);
|
||||
}
|
||||
RGBToYCbCr(row0, row1, row2, xsize);
|
||||
RGBToYCbCr(row, xsize);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
|
@ -137,26 +129,152 @@ HWY_EXPORT(YCCKToCMYK);
|
|||
HWY_EXPORT(YCbCrToRGB);
|
||||
HWY_EXPORT(RGBToYCbCr);
|
||||
|
||||
void CMYKToYCCK(float* JXL_RESTRICT row0, float* JXL_RESTRICT row1,
|
||||
float* JXL_RESTRICT row2, float* JXL_RESTRICT row3,
|
||||
size_t xsize) {
|
||||
return HWY_DYNAMIC_DISPATCH(CMYKToYCCK)(row0, row1, row2, row3, xsize);
|
||||
bool CheckColorSpaceComponents(int num_components, J_COLOR_SPACE colorspace) {
|
||||
switch (colorspace) {
|
||||
case JCS_GRAYSCALE:
|
||||
return num_components == 1;
|
||||
case JCS_RGB:
|
||||
case JCS_YCbCr:
|
||||
case JCS_EXT_RGB:
|
||||
case JCS_EXT_BGR:
|
||||
return num_components == 3;
|
||||
case JCS_CMYK:
|
||||
case JCS_YCCK:
|
||||
case JCS_EXT_RGBX:
|
||||
case JCS_EXT_BGRX:
|
||||
case JCS_EXT_XBGR:
|
||||
case JCS_EXT_XRGB:
|
||||
case JCS_EXT_RGBA:
|
||||
case JCS_EXT_BGRA:
|
||||
case JCS_EXT_ABGR:
|
||||
case JCS_EXT_ARGB:
|
||||
return num_components == 4;
|
||||
default:
|
||||
// Unrecognized colorspaces can have any number of channels, since no
|
||||
// color transform will be performed on them.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void YCCKToCMYK(float* JXL_RESTRICT row0, float* JXL_RESTRICT row1,
|
||||
float* JXL_RESTRICT row2, float* JXL_RESTRICT row3,
|
||||
size_t xsize) {
|
||||
return HWY_DYNAMIC_DISPATCH(YCCKToCMYK)(row0, row1, row2, row3, xsize);
|
||||
void NullTransform(float* row[kMaxComponents], size_t len) {}
|
||||
|
||||
void GrayscaleToRGB(float* row[kMaxComponents], size_t len) {
|
||||
memcpy(row[1], row[0], len * sizeof(row[1][0]));
|
||||
memcpy(row[2], row[0], len * sizeof(row[2][0]));
|
||||
}
|
||||
|
||||
void YCbCrToRGB(float* JXL_RESTRICT row0, float* JXL_RESTRICT row1,
|
||||
float* JXL_RESTRICT row2, size_t xsize) {
|
||||
return HWY_DYNAMIC_DISPATCH(YCbCrToRGB)(row0, row1, row2, xsize);
|
||||
void GrayscaleToYCbCr(float* row[kMaxComponents], size_t len) {
|
||||
memset(row[1], 0, len * sizeof(row[1][0]));
|
||||
memset(row[2], 0, len * sizeof(row[2][0]));
|
||||
}
|
||||
|
||||
void RGBToYCbCr(float* JXL_RESTRICT row0, float* JXL_RESTRICT row1,
|
||||
float* JXL_RESTRICT row2, size_t xsize) {
|
||||
return HWY_DYNAMIC_DISPATCH(RGBToYCbCr)(row0, row1, row2, xsize);
|
||||
void ChooseColorTransform(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
if (!CheckColorSpaceComponents(cinfo->input_components,
|
||||
cinfo->in_color_space)) {
|
||||
JPEGLI_ERROR("Invalid number of input components %d for colorspace %d",
|
||||
cinfo->input_components, cinfo->in_color_space);
|
||||
}
|
||||
if (!CheckColorSpaceComponents(cinfo->num_components,
|
||||
cinfo->jpeg_color_space)) {
|
||||
JPEGLI_ERROR("Invalid number of components %d for colorspace %d",
|
||||
cinfo->num_components, cinfo->jpeg_color_space);
|
||||
}
|
||||
if (cinfo->jpeg_color_space == cinfo->in_color_space) {
|
||||
if (cinfo->num_components != cinfo->input_components) {
|
||||
JPEGLI_ERROR("Input/output components mismatch: %d vs %d",
|
||||
cinfo->input_components, cinfo->num_components);
|
||||
}
|
||||
// No color transform requested.
|
||||
m->color_transform = NullTransform;
|
||||
return;
|
||||
}
|
||||
|
||||
if (cinfo->in_color_space == JCS_RGB && m->xyb_mode) {
|
||||
JPEGLI_ERROR("Color transform on XYB colorspace is not supported.");
|
||||
}
|
||||
|
||||
m->color_transform = nullptr;
|
||||
if (cinfo->jpeg_color_space == JCS_GRAYSCALE) {
|
||||
if (cinfo->in_color_space == JCS_RGB) {
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr);
|
||||
} else if (cinfo->in_color_space == JCS_YCbCr ||
|
||||
cinfo->in_color_space == JCS_YCCK) {
|
||||
// Since the first luminance channel is the grayscale version of the
|
||||
// image, nothing to do here
|
||||
m->color_transform = NullTransform;
|
||||
}
|
||||
} else if (cinfo->jpeg_color_space == JCS_RGB) {
|
||||
if (cinfo->in_color_space == JCS_GRAYSCALE) {
|
||||
m->color_transform = GrayscaleToRGB;
|
||||
}
|
||||
} else if (cinfo->jpeg_color_space == JCS_YCbCr) {
|
||||
if (cinfo->in_color_space == JCS_RGB) {
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr);
|
||||
} else if (cinfo->in_color_space == JCS_GRAYSCALE) {
|
||||
m->color_transform = GrayscaleToYCbCr;
|
||||
}
|
||||
} else if (cinfo->jpeg_color_space == JCS_YCCK) {
|
||||
if (cinfo->in_color_space == JCS_CMYK) {
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(CMYKToYCCK);
|
||||
}
|
||||
}
|
||||
|
||||
if (m->color_transform == nullptr) {
|
||||
// TODO(szabadka) Support more color transforms.
|
||||
JPEGLI_ERROR("Unsupported color transform %d -> %d", cinfo->in_color_space,
|
||||
cinfo->jpeg_color_space);
|
||||
}
|
||||
}
|
||||
|
||||
void ChooseColorTransform(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (!CheckColorSpaceComponents(cinfo->out_color_components,
|
||||
cinfo->out_color_space)) {
|
||||
JPEGLI_ERROR("Invalid number of output components %d for colorspace %d",
|
||||
cinfo->out_color_components, cinfo->out_color_space);
|
||||
}
|
||||
if (!CheckColorSpaceComponents(cinfo->num_components,
|
||||
cinfo->jpeg_color_space)) {
|
||||
JPEGLI_ERROR("Invalid number of components %d for colorspace %d",
|
||||
cinfo->num_components, cinfo->jpeg_color_space);
|
||||
}
|
||||
if (cinfo->jpeg_color_space == cinfo->out_color_space) {
|
||||
if (cinfo->num_components != cinfo->out_color_components) {
|
||||
JPEGLI_ERROR("Input/output components mismatch: %d vs %d",
|
||||
cinfo->num_components, cinfo->out_color_components);
|
||||
}
|
||||
// No color transform requested.
|
||||
m->color_transform = NullTransform;
|
||||
return;
|
||||
}
|
||||
|
||||
m->color_transform = nullptr;
|
||||
if (cinfo->jpeg_color_space == JCS_GRAYSCALE) {
|
||||
if (cinfo->out_color_space == JCS_RGB) {
|
||||
m->color_transform = GrayscaleToRGB;
|
||||
}
|
||||
} else if (cinfo->jpeg_color_space == JCS_RGB) {
|
||||
if (cinfo->out_color_space == JCS_GRAYSCALE) {
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr);
|
||||
}
|
||||
} else if (cinfo->jpeg_color_space == JCS_YCbCr) {
|
||||
if (cinfo->out_color_space == JCS_RGB) {
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGB);
|
||||
} else if (cinfo->out_color_space == JCS_GRAYSCALE) {
|
||||
m->color_transform = NullTransform;
|
||||
}
|
||||
} else if (cinfo->jpeg_color_space == JCS_YCCK) {
|
||||
if (cinfo->out_color_space == JCS_CMYK) {
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(YCCKToCMYK);
|
||||
}
|
||||
}
|
||||
|
||||
if (m->color_transform == nullptr) {
|
||||
// TODO(szabadka) Support more color transforms.
|
||||
JPEGLI_ERROR("Unsupported color transform %d -> %d",
|
||||
cinfo->jpeg_color_space, cinfo->out_color_space);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
|
|
|
@ -6,25 +6,18 @@
|
|||
#ifndef LIB_JPEGLI_COLOR_TRANSFORM_H_
|
||||
#define LIB_JPEGLI_COLOR_TRANSFORM_H_
|
||||
|
||||
#include <stddef.h>
|
||||
/* clang-format off */
|
||||
#include <stdio.h>
|
||||
#include <jpeglib.h>
|
||||
/* clang-format on */
|
||||
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void CMYKToYCCK(float* JXL_RESTRICT row0, float* JXL_RESTRICT row1,
|
||||
float* JXL_RESTRICT row2, float* JXL_RESTRICT row3,
|
||||
size_t xsize);
|
||||
void ChooseColorTransform(j_compress_ptr cinfo);
|
||||
|
||||
void YCCKToCMYK(float* JXL_RESTRICT row0, float* JXL_RESTRICT row1,
|
||||
float* JXL_RESTRICT row2, float* JXL_RESTRICT row3,
|
||||
size_t xsize);
|
||||
|
||||
void YCbCrToRGB(float* JXL_RESTRICT row0, float* JXL_RESTRICT row1,
|
||||
float* JXL_RESTRICT row2, size_t xsize);
|
||||
|
||||
void RGBToYCbCr(float* JXL_RESTRICT row0, float* JXL_RESTRICT row1,
|
||||
float* JXL_RESTRICT row2, size_t xsize);
|
||||
void ChooseColorTransform(j_decompress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ void jpegli_destroy(j_common_ptr cinfo) {
|
|||
delete reinterpret_cast<j_decompress_ptr>(cinfo)->master;
|
||||
} else {
|
||||
cinfo->global_state = jpegli::kEncNull;
|
||||
delete reinterpret_cast<j_compress_ptr>(cinfo)->master;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,9 +10,13 @@
|
|||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <hwy/aligned_allocator.h>
|
||||
|
||||
#include "lib/jpegli/memory_manager.h"
|
||||
#include "lib/jpegli/simd.h"
|
||||
#include "lib/jxl/base/compiler_specific.h" // for ssize_t
|
||||
#include "lib/jxl/base/status.h" // for JXL_CHECK
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
|
@ -35,6 +39,11 @@ constexpr inline T1 DivCeil(T1 a, T2 b) {
|
|||
return (a + b - 1) / b;
|
||||
}
|
||||
|
||||
template <typename T1, typename T2>
|
||||
constexpr inline T1 RoundUpTo(T1 a, T2 b) {
|
||||
return DivCeil(a, b) * b;
|
||||
}
|
||||
|
||||
constexpr size_t kDCTBlockSize = 64;
|
||||
// This is set to the same value as MAX_COMPS_IN_SCAN, because that is the
|
||||
// maximum number of channels the libjpeg-turbo decoder can decode.
|
||||
|
@ -83,19 +92,42 @@ constexpr uint32_t kJPEGZigZagOrder[64] = {
|
|||
template <typename T>
|
||||
class RowBuffer {
|
||||
public:
|
||||
void Allocate(size_t num_rows, size_t stride) {
|
||||
template <typename CInfoType>
|
||||
void Allocate(CInfoType cinfo, size_t num_rows, size_t rowsize) {
|
||||
size_t vec_size = std::max(VectorSize(), sizeof(T));
|
||||
JXL_CHECK(vec_size % sizeof(T) == 0);
|
||||
size_t alignment = std::max<size_t>(HWY_ALIGNMENT, vec_size);
|
||||
size_t min_memstride = alignment + rowsize * sizeof(T) + vec_size;
|
||||
size_t memstride = RoundUpTo(min_memstride, alignment);
|
||||
xsize_ = rowsize;
|
||||
ysize_ = num_rows;
|
||||
stride_ = stride;
|
||||
data_ = hwy::AllocateAligned<T>(ysize_ * stride_);
|
||||
stride_ = memstride / sizeof(T);
|
||||
offset_ = alignment / sizeof(T);
|
||||
data_ = ::jpegli::Allocate<T>(cinfo, ysize_ * stride_, JPOOL_IMAGE_ALIGNED);
|
||||
}
|
||||
|
||||
T* Row(ssize_t y) { return &data_[((ysize_ + y) % ysize_) * stride_]; }
|
||||
T* Row(ssize_t y) const {
|
||||
return &data_[((ysize_ + y) % ysize_) * stride_ + offset_];
|
||||
}
|
||||
|
||||
size_t xsize() const { return xsize_; };
|
||||
size_t ysize() const { return ysize_; };
|
||||
size_t stride() const { return stride_; }
|
||||
size_t memstride() const { return stride_ * sizeof(T); }
|
||||
|
||||
void CopyRow(ssize_t y, const T* src, size_t len) {
|
||||
memcpy(Row(y), src, len * sizeof(T));
|
||||
void PadRow(size_t y, size_t from, int border) {
|
||||
float* row = Row(y);
|
||||
for (int offset = -border; offset < 0; ++offset) {
|
||||
row[offset] = row[0];
|
||||
}
|
||||
float last_val = row[from - 1];
|
||||
for (size_t x = from; x < xsize_ + border; ++x) {
|
||||
row[x] = last_val;
|
||||
}
|
||||
}
|
||||
|
||||
void CopyRow(ssize_t dst_row, ssize_t src_row, int border) {
|
||||
memcpy(Row(dst_row) - border, Row(src_row) - border,
|
||||
(xsize_ + 2 * border) * sizeof(T));
|
||||
}
|
||||
|
||||
void FillRow(ssize_t y, T val, size_t len) {
|
||||
|
@ -106,9 +138,11 @@ class RowBuffer {
|
|||
}
|
||||
|
||||
private:
|
||||
size_t ysize_ = 0;
|
||||
size_t stride_ = 0;
|
||||
hwy::AlignedFreeUniquePtr<T[]> data_;
|
||||
size_t xsize_;
|
||||
size_t ysize_;
|
||||
size_t stride_;
|
||||
size_t offset_;
|
||||
T* data_;
|
||||
};
|
||||
|
||||
} // namespace jpegli
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
// 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.
|
||||
|
||||
#if defined(LIB_JPEGLI_DCT_INL_H_) == defined(HWY_TARGET_TOGGLE)
|
||||
#ifdef LIB_JPEGLI_DCT_INL_H_
|
||||
#undef LIB_JPEGLI_DCT_INL_H_
|
||||
#else
|
||||
#define LIB_JPEGLI_DCT_INL_H_
|
||||
#endif
|
||||
|
||||
#include "lib/jpegli/transpose-inl.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jpegli {
|
||||
namespace HWY_NAMESPACE {
|
||||
namespace {
|
||||
|
||||
// These templates are not found via ADL.
|
||||
using hwy::HWY_NAMESPACE::Abs;
|
||||
using hwy::HWY_NAMESPACE::Add;
|
||||
using hwy::HWY_NAMESPACE::DemoteTo;
|
||||
using hwy::HWY_NAMESPACE::Ge;
|
||||
using hwy::HWY_NAMESPACE::IfThenElseZero;
|
||||
using hwy::HWY_NAMESPACE::Mul;
|
||||
using hwy::HWY_NAMESPACE::MulAdd;
|
||||
using hwy::HWY_NAMESPACE::Rebind;
|
||||
using hwy::HWY_NAMESPACE::Round;
|
||||
using hwy::HWY_NAMESPACE::Sub;
|
||||
using hwy::HWY_NAMESPACE::Vec;
|
||||
|
||||
using D = HWY_FULL(float);
|
||||
using DI = HWY_FULL(int32_t);
|
||||
|
||||
template <size_t N>
|
||||
void AddReverse(const float* JXL_RESTRICT ain1, const float* JXL_RESTRICT ain2,
|
||||
float* JXL_RESTRICT aout) {
|
||||
HWY_CAPPED(float, 8) d8;
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
auto in1 = Load(d8, ain1 + i * 8);
|
||||
auto in2 = Load(d8, ain2 + (N - i - 1) * 8);
|
||||
Store(Add(in1, in2), d8, aout + i * 8);
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
void SubReverse(const float* JXL_RESTRICT ain1, const float* JXL_RESTRICT ain2,
|
||||
float* JXL_RESTRICT aout) {
|
||||
HWY_CAPPED(float, 8) d8;
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
auto in1 = Load(d8, ain1 + i * 8);
|
||||
auto in2 = Load(d8, ain2 + (N - i - 1) * 8);
|
||||
Store(Sub(in1, in2), d8, aout + i * 8);
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
void B(float* JXL_RESTRICT coeff) {
|
||||
HWY_CAPPED(float, 8) d8;
|
||||
constexpr float kSqrt2 = 1.41421356237f;
|
||||
auto sqrt2 = Set(d8, kSqrt2);
|
||||
auto in1 = Load(d8, coeff);
|
||||
auto in2 = Load(d8, coeff + 8);
|
||||
Store(MulAdd(in1, sqrt2, in2), d8, coeff);
|
||||
for (size_t i = 1; i + 1 < N; i++) {
|
||||
auto in1 = Load(d8, coeff + i * 8);
|
||||
auto in2 = Load(d8, coeff + (i + 1) * 8);
|
||||
Store(Add(in1, in2), d8, coeff + i * 8);
|
||||
}
|
||||
}
|
||||
|
||||
// Ideally optimized away by compiler (except the multiply).
|
||||
template <size_t N>
|
||||
void InverseEvenOdd(const float* JXL_RESTRICT ain, float* JXL_RESTRICT aout) {
|
||||
HWY_CAPPED(float, 8) d8;
|
||||
for (size_t i = 0; i < N / 2; i++) {
|
||||
auto in1 = Load(d8, ain + i * 8);
|
||||
Store(in1, d8, aout + 2 * i * 8);
|
||||
}
|
||||
for (size_t i = N / 2; i < N; i++) {
|
||||
auto in1 = Load(d8, ain + i * 8);
|
||||
Store(in1, d8, aout + (2 * (i - N / 2) + 1) * 8);
|
||||
}
|
||||
}
|
||||
|
||||
// Constants for DCT implementation. Generated by the following snippet:
|
||||
// for i in range(N // 2):
|
||||
// print(1.0 / (2 * math.cos((i + 0.5) * math.pi / N)), end=", ")
|
||||
template <size_t N>
|
||||
struct WcMultipliers;
|
||||
|
||||
template <>
|
||||
struct WcMultipliers<4> {
|
||||
static constexpr float kMultipliers[] = {
|
||||
0.541196100146197,
|
||||
1.3065629648763764,
|
||||
};
|
||||
};
|
||||
|
||||
template <>
|
||||
struct WcMultipliers<8> {
|
||||
static constexpr float kMultipliers[] = {
|
||||
0.5097955791041592,
|
||||
0.6013448869350453,
|
||||
0.8999762231364156,
|
||||
2.5629154477415055,
|
||||
};
|
||||
};
|
||||
|
||||
constexpr float WcMultipliers<4>::kMultipliers[];
|
||||
constexpr float WcMultipliers<8>::kMultipliers[];
|
||||
|
||||
// Invoked on full vector.
|
||||
template <size_t N>
|
||||
void Multiply(float* JXL_RESTRICT coeff) {
|
||||
HWY_CAPPED(float, 8) d8;
|
||||
for (size_t i = 0; i < N / 2; i++) {
|
||||
auto in1 = Load(d8, coeff + (N / 2 + i) * 8);
|
||||
auto mul = Set(d8, WcMultipliers<N>::kMultipliers[i]);
|
||||
Store(Mul(in1, mul), d8, coeff + (N / 2 + i) * 8);
|
||||
}
|
||||
}
|
||||
|
||||
void LoadFromBlock(const float* JXL_RESTRICT pixels, size_t pixels_stride,
|
||||
size_t off, float* JXL_RESTRICT coeff) {
|
||||
HWY_CAPPED(float, 8) d8;
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
Store(LoadU(d8, pixels + i * pixels_stride + off), d8, coeff + i * 8);
|
||||
}
|
||||
}
|
||||
|
||||
void StoreToBlockAndScale(const float* JXL_RESTRICT coeff, float* output,
|
||||
size_t off) {
|
||||
HWY_CAPPED(float, 8) d8;
|
||||
auto mul = Set(d8, 1.0f / 8);
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
StoreU(Mul(mul, Load(d8, coeff + i * 8)), d8, output + i * 8 + off);
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
struct DCT1DImpl;
|
||||
|
||||
template <>
|
||||
struct DCT1DImpl<1> {
|
||||
JXL_INLINE void operator()(float* JXL_RESTRICT mem) {}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct DCT1DImpl<2> {
|
||||
JXL_INLINE void operator()(float* JXL_RESTRICT mem) {
|
||||
HWY_CAPPED(float, 8) d8;
|
||||
auto in1 = Load(d8, mem);
|
||||
auto in2 = Load(d8, mem + 8);
|
||||
Store(Add(in1, in2), d8, mem);
|
||||
Store(Sub(in1, in2), d8, mem + 8);
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
struct DCT1DImpl {
|
||||
void operator()(float* JXL_RESTRICT mem) {
|
||||
HWY_ALIGN float tmp[N * 8];
|
||||
AddReverse<N / 2>(mem, mem + N * 4, tmp);
|
||||
DCT1DImpl<N / 2>()(tmp);
|
||||
SubReverse<N / 2>(mem, mem + N * 4, tmp + N * 4);
|
||||
Multiply<N>(tmp);
|
||||
DCT1DImpl<N / 2>()(tmp + N * 4);
|
||||
B<N / 2>(tmp + N * 4);
|
||||
InverseEvenOdd<N>(tmp, mem);
|
||||
}
|
||||
};
|
||||
|
||||
void DCT1D(const float* JXL_RESTRICT pixels, size_t pixels_stride,
|
||||
float* JXL_RESTRICT output) {
|
||||
HWY_CAPPED(float, 8) d8;
|
||||
HWY_ALIGN float tmp[64];
|
||||
for (size_t i = 0; i < 8; i += Lanes(d8)) {
|
||||
// TODO(veluca): consider removing the temporary memory here (as is done in
|
||||
// IDCT), if it turns out that some compilers don't optimize away the loads
|
||||
// and this is performance-critical.
|
||||
LoadFromBlock(pixels, pixels_stride, i, tmp);
|
||||
DCT1DImpl<8>()(tmp);
|
||||
StoreToBlockAndScale(tmp, output, i);
|
||||
}
|
||||
}
|
||||
|
||||
void TransformFromPixels(const float* JXL_RESTRICT pixels, size_t pixels_stride,
|
||||
float* JXL_RESTRICT coefficients,
|
||||
float* JXL_RESTRICT scratch_space) {
|
||||
DCT1D(pixels, pixels_stride, scratch_space);
|
||||
Transpose8x8Block(scratch_space, coefficients);
|
||||
DCT1D(coefficients, 8, scratch_space);
|
||||
Transpose8x8Block(scratch_space, coefficients);
|
||||
}
|
||||
|
||||
void StoreQuantizedValue(const Vec<DI>& ival, int16_t* out) {
|
||||
Rebind<int16_t, DI> di16;
|
||||
Store(DemoteTo(di16, ival), di16, out);
|
||||
}
|
||||
|
||||
void StoreQuantizedValue(const Vec<DI>& ival, int32_t* out) {
|
||||
DI di;
|
||||
Store(ival, di, out);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void QuantizeBlock(const float* dct, const float* qmc, float aq_strength,
|
||||
const float* zero_bias_offset, const float* zero_bias_mul,
|
||||
T* block) {
|
||||
D d;
|
||||
DI di;
|
||||
const auto aq_mul = Set(d, aq_strength);
|
||||
for (size_t k = 0; k < DCTSIZE2; k += Lanes(d)) {
|
||||
const auto val = Load(d, dct + k);
|
||||
const auto q = Load(d, qmc + k);
|
||||
const auto qval = Mul(val, q);
|
||||
const auto zb_offset = Load(d, zero_bias_offset + k);
|
||||
const auto zb_mul = Load(d, zero_bias_mul + k);
|
||||
const auto threshold = Add(zb_offset, Mul(zb_mul, aq_mul));
|
||||
const auto nzero_mask = Ge(Abs(qval), threshold);
|
||||
const auto ival = ConvertTo(di, IfThenElseZero(nzero_mask, Round(qval)));
|
||||
StoreQuantizedValue(ival, block + k);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void QuantizeBlockNoAQ(const float* dct, const float* qmc, T* block) {
|
||||
D d;
|
||||
DI di;
|
||||
for (size_t k = 0; k < DCTSIZE2; k += Lanes(d)) {
|
||||
const auto val = Load(d, dct + k);
|
||||
const auto q = Load(d, qmc + k);
|
||||
const auto ival = ConvertTo(di, Round(Mul(val, q)));
|
||||
StoreQuantizedValue(ival, block + k);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ComputeCoefficientBlock(const float* JXL_RESTRICT pixels, size_t stride,
|
||||
const float* JXL_RESTRICT qmc, float aq_strength,
|
||||
const float* zero_bias_offset,
|
||||
const float* zero_bias_mul,
|
||||
float* JXL_RESTRICT tmp, T* block) {
|
||||
float* JXL_RESTRICT dct = tmp;
|
||||
float* JXL_RESTRICT scratch_space = tmp + DCTSIZE2;
|
||||
TransformFromPixels(pixels, stride, dct, scratch_space);
|
||||
if (aq_strength > 0.0f) {
|
||||
QuantizeBlock(dct, qmc, aq_strength, zero_bias_offset, zero_bias_mul,
|
||||
block);
|
||||
} else {
|
||||
QuantizeBlockNoAQ(dct, qmc, block);
|
||||
}
|
||||
// Center DC values around zero.
|
||||
static constexpr float kDCBias = 128.0f;
|
||||
block[0] = std::round((dct[0] - kDCBias) * qmc[0]);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
#endif // LIB_JPEGLI_DCT_INL_H_
|
|
@ -12,90 +12,52 @@
|
|||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/jxl/enc_transforms.h"
|
||||
#include "lib/jpegli/dct-inl.h"
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
#include "lib/jpegli/memory_manager.h"
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jpegli {
|
||||
namespace HWY_NAMESPACE {
|
||||
namespace {
|
||||
|
||||
constexpr float kZeroBiasMulXYB[] = {0.5f, 0.5f, 0.5f};
|
||||
constexpr float kZeroBiasMulYCbCr[] = {0.7f, 1.0f, 0.8f};
|
||||
|
||||
void QuantizeBlock(const float* dct, const float* qmc, const float zero_bias,
|
||||
coeff_t* block) {
|
||||
for (size_t iy = 0, i = 0; iy < 8; iy++) {
|
||||
for (size_t ix = 0; ix < 8; ix++, i++) {
|
||||
float coeff = 2040 * dct[ix * 8 + iy] * qmc[i];
|
||||
int cc = std::abs(coeff) < zero_bias ? 0 : std::round(coeff);
|
||||
block[i] = cc;
|
||||
}
|
||||
}
|
||||
// Center DC values around zero.
|
||||
block[0] = std::round((2040 * dct[0] - 1024) * qmc[0]);
|
||||
}
|
||||
|
||||
void QuantizeBlockNoAQ(const float* dct, const float* qmc, coeff_t* block) {
|
||||
for (size_t iy = 0, i = 0; iy < 8; iy++) {
|
||||
for (size_t ix = 0; ix < 8; ix++, i++) {
|
||||
block[i] = std::round(2040 * dct[ix * 8 + iy] * qmc[i]);
|
||||
}
|
||||
}
|
||||
// Center DC values around zero.
|
||||
block[0] = std::round((2040 * dct[0] - 1024) * qmc[0]);
|
||||
}
|
||||
|
||||
void ComputeDCTCoefficients(
|
||||
j_compress_ptr cinfo,
|
||||
std::vector<std::vector<jpegli::coeff_t> >* all_coeffs) {
|
||||
void ComputeDCTCoefficients(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
std::vector<float> zero_bias_mul(cinfo->num_components, 0.5f);
|
||||
const bool xyb = m->xyb_mode && cinfo->jpeg_color_space == JCS_RGB;
|
||||
if (m->distance <= 1.0f) {
|
||||
for (int c = 0; c < 3 && c < cinfo->num_components; ++c) {
|
||||
zero_bias_mul[c] = xyb ? kZeroBiasMulXYB[c] : kZeroBiasMulYCbCr[c];
|
||||
}
|
||||
}
|
||||
HWY_ALIGN float scratch_space[2 * kDCTBlockSize];
|
||||
jxl::ImageF tmp;
|
||||
float* tmp = m->dct_buffer;
|
||||
for (int c = 0; c < cinfo->num_components; c++) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
const size_t xsize_blocks = comp->width_in_blocks;
|
||||
const size_t ysize_blocks = comp->height_in_blocks;
|
||||
JXL_DASSERT(cinfo->max_h_samp_factor % comp->h_samp_factor == 0);
|
||||
JXL_DASSERT(cinfo->max_v_samp_factor % comp->v_samp_factor == 0);
|
||||
const int h_factor = cinfo->max_h_samp_factor / comp->h_samp_factor;
|
||||
const int v_factor = cinfo->max_v_samp_factor / comp->v_samp_factor;
|
||||
std::vector<coeff_t> coeffs(xsize_blocks * ysize_blocks * kDCTBlockSize);
|
||||
JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[comp->quant_tbl_no];
|
||||
std::vector<float> qmc(kDCTBlockSize);
|
||||
for (size_t k = 0; k < kDCTBlockSize; k++) {
|
||||
qmc[k] = 1.0f / quant_table->quantval[k];
|
||||
}
|
||||
RowBuffer<float>* plane = &m->input_buffer[c];
|
||||
for (size_t by = 0, bix = 0; by < ysize_blocks; by++) {
|
||||
int by0 = m->next_iMCU_row * comp->v_samp_factor;
|
||||
int block_rows_left = comp->height_in_blocks - by0;
|
||||
int max_block_rows = std::min(comp->v_samp_factor, block_rows_left);
|
||||
JBLOCKARRAY ba = (*cinfo->mem->access_virt_barray)(
|
||||
reinterpret_cast<j_common_ptr>(cinfo), m->coeff_buffers[c], by0,
|
||||
max_block_rows, true);
|
||||
float* qmc = m->quant_mul[c];
|
||||
RowBuffer<float>* plane = m->raw_data[c];
|
||||
const int h_factor = m->h_factor[c];
|
||||
const int v_factor = m->v_factor[c];
|
||||
const float* zero_bias_offset = m->zero_bias_offset[c];
|
||||
const float* zero_bias_mul = m->zero_bias_mul[c];
|
||||
float aq_strength = 0.0f;
|
||||
for (int iy = 0; iy < comp->v_samp_factor; iy++) {
|
||||
size_t by = by0 + iy;
|
||||
if (by >= comp->height_in_blocks) continue;
|
||||
JBLOCKROW brow = ba[iy];
|
||||
const float* row = plane->Row(8 * by);
|
||||
for (size_t bx = 0; bx < xsize_blocks; bx++, bix++) {
|
||||
coeff_t* block = &coeffs[bix * kDCTBlockSize];
|
||||
HWY_ALIGN float dct[kDCTBlockSize];
|
||||
TransformFromPixels(jxl::AcStrategy::Type::DCT, row + 8 * bx,
|
||||
plane->stride(), dct, scratch_space);
|
||||
for (size_t bx = 0; bx < comp->width_in_blocks; bx++) {
|
||||
JCOEF* block = &brow[bx][0];
|
||||
if (m->use_adaptive_quantization) {
|
||||
// Create more zeros in areas where jpeg xl would have used a lower
|
||||
// quantization multiplier.
|
||||
float relq = m->quant_field.Row(by * v_factor)[bx * h_factor];
|
||||
float zero_bias = 0.5f + zero_bias_mul[c] * relq;
|
||||
zero_bias = std::min(1.5f, zero_bias);
|
||||
QuantizeBlock(dct, &qmc[0], zero_bias, block);
|
||||
} else {
|
||||
QuantizeBlockNoAQ(dct, &qmc[0], block);
|
||||
aq_strength = m->quant_field.Row(by * v_factor)[bx * h_factor];
|
||||
}
|
||||
ComputeCoefficientBlock(row + 8 * bx, plane->stride(), qmc, aq_strength,
|
||||
zero_bias_offset, zero_bias_mul, tmp, block);
|
||||
}
|
||||
}
|
||||
all_coeffs->emplace_back(std::move(coeffs));
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
|
@ -105,9 +67,8 @@ namespace jpegli {
|
|||
|
||||
HWY_EXPORT(ComputeDCTCoefficients);
|
||||
|
||||
void ComputeDCTCoefficients(
|
||||
j_compress_ptr cinfo, std::vector<std::vector<jpegli::coeff_t> >* coeffs) {
|
||||
HWY_DYNAMIC_DISPATCH(ComputeDCTCoefficients)(cinfo, coeffs);
|
||||
void ComputeDCTCoefficients(j_compress_ptr cinfo) {
|
||||
HWY_DYNAMIC_DISPATCH(ComputeDCTCoefficients)(cinfo);
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
|
|
|
@ -11,14 +11,9 @@
|
|||
#include <jpeglib.h>
|
||||
/* clang-format on */
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void ComputeDCTCoefficients(j_compress_ptr cinfo,
|
||||
std::vector<std::vector<jpegli::coeff_t> >* coeffs);
|
||||
void ComputeDCTCoefficients(j_compress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
|
|
|
@ -9,20 +9,19 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/color_quantize.h"
|
||||
#include "lib/jpegli/decode_internal.h"
|
||||
#include "lib/jpegli/decode_marker.h"
|
||||
#include "lib/jpegli/decode_scan.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jpegli/memory_manager.h"
|
||||
#include "lib/jpegli/render.h"
|
||||
#include "lib/jpegli/source_manager.h"
|
||||
#include "lib/jxl/base/byte_order.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void InitializeImage(j_decompress_ptr cinfo) {
|
||||
cinfo->jpeg_color_space = JCS_UNKNOWN;
|
||||
cinfo->restart_interval = 0;
|
||||
cinfo->saw_JFIF_marker = FALSE;
|
||||
cinfo->JFIF_major_version = 1;
|
||||
|
@ -32,53 +31,352 @@ void InitializeImage(j_decompress_ptr cinfo) {
|
|||
cinfo->Y_density = 1;
|
||||
cinfo->saw_Adobe_marker = FALSE;
|
||||
cinfo->Adobe_transform = 0;
|
||||
for (int i = 0; i < NUM_QUANT_TBLS; ++i) {
|
||||
cinfo->quant_tbl_ptrs[i] = nullptr;
|
||||
cinfo->CCIR601_sampling = FALSE; // not used
|
||||
cinfo->marker_list = nullptr;
|
||||
cinfo->comp_info = nullptr;
|
||||
cinfo->input_scan_number = 0;
|
||||
cinfo->input_iMCU_row = 0;
|
||||
cinfo->output_scan_number = 0;
|
||||
cinfo->output_iMCU_row = 0;
|
||||
cinfo->output_scanline = 0;
|
||||
cinfo->unread_marker = 0;
|
||||
cinfo->coef_bits = nullptr;
|
||||
// We set all these to zero since we don't yet support arithmetic coding.
|
||||
memset(cinfo->arith_dc_L, 0, sizeof(cinfo->arith_dc_L));
|
||||
memset(cinfo->arith_dc_U, 0, sizeof(cinfo->arith_dc_U));
|
||||
memset(cinfo->arith_ac_K, 0, sizeof(cinfo->arith_ac_K));
|
||||
// Initialize the private fields.
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
m->input_buffer_.clear();
|
||||
m->input_buffer_pos_ = 0;
|
||||
m->codestream_bits_ahead_ = 0;
|
||||
m->is_multiscan_ = false;
|
||||
m->found_soi_ = false;
|
||||
m->found_dri_ = false;
|
||||
m->found_sof_ = false;
|
||||
m->found_eoi_ = false;
|
||||
m->icc_index_ = 0;
|
||||
m->icc_total_ = 0;
|
||||
m->icc_profile_.clear();
|
||||
memset(m->dc_huff_lut_, 0, sizeof(m->dc_huff_lut_));
|
||||
memset(m->ac_huff_lut_, 0, sizeof(m->ac_huff_lut_));
|
||||
// Initialize the values to an invalid symbol so that we can recognize it
|
||||
// when reading the bit stream using a Huffman code with space > 0.
|
||||
for (size_t i = 0; i < kAllHuffLutSize; ++i) {
|
||||
m->dc_huff_lut_[i].bits = 0;
|
||||
m->dc_huff_lut_[i].value = 0xffff;
|
||||
m->ac_huff_lut_[i].bits = 0;
|
||||
m->ac_huff_lut_[i].value = 0xffff;
|
||||
}
|
||||
for (int i = 0; i < NUM_HUFF_TBLS; ++i) {
|
||||
cinfo->dc_huff_tbl_ptrs[i] = nullptr;
|
||||
cinfo->ac_huff_tbl_ptrs[i] = nullptr;
|
||||
m->colormap_lut_ = nullptr;
|
||||
m->pixels_ = nullptr;
|
||||
m->scanlines_ = nullptr;
|
||||
m->regenerate_inverse_colormap_ = true;
|
||||
for (int i = 0; i < kMaxComponents; ++i) {
|
||||
m->dither_[i] = nullptr;
|
||||
m->error_row_[i] = nullptr;
|
||||
}
|
||||
m->output_passes_done_ = 0;
|
||||
m->xoffset_ = 0;
|
||||
m->dequant_ = nullptr;
|
||||
}
|
||||
|
||||
void InitializeDecompressParams(j_decompress_ptr cinfo) {
|
||||
cinfo->jpeg_color_space = JCS_UNKNOWN;
|
||||
cinfo->out_color_space = JCS_UNKNOWN;
|
||||
cinfo->scale_num = 1;
|
||||
cinfo->scale_denom = 1;
|
||||
cinfo->output_gamma = 0.0f;
|
||||
cinfo->buffered_image = FALSE;
|
||||
cinfo->raw_data_out = FALSE;
|
||||
cinfo->dct_method = JDCT_DEFAULT;
|
||||
cinfo->do_fancy_upsampling = TRUE;
|
||||
cinfo->do_block_smoothing = TRUE;
|
||||
cinfo->quantize_colors = FALSE;
|
||||
cinfo->dither_mode = JDITHER_FS;
|
||||
cinfo->two_pass_quantize = TRUE;
|
||||
cinfo->desired_number_of_colors = 256;
|
||||
cinfo->enable_1pass_quant = FALSE;
|
||||
cinfo->enable_external_quant = FALSE;
|
||||
cinfo->enable_2pass_quant = FALSE;
|
||||
cinfo->actual_number_of_colors = 0;
|
||||
cinfo->colormap = nullptr;
|
||||
}
|
||||
|
||||
void InitProgressMonitor(j_decompress_ptr cinfo, bool coef_only) {
|
||||
if (!cinfo->progress) return;
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
int nc = cinfo->num_components;
|
||||
int estimated_num_scans =
|
||||
cinfo->progressive_mode ? 2 + 3 * nc : (m->is_multiscan_ ? nc : 1);
|
||||
cinfo->progress->pass_limit = cinfo->total_iMCU_rows * estimated_num_scans;
|
||||
cinfo->progress->pass_counter = 0;
|
||||
if (coef_only) {
|
||||
cinfo->progress->total_passes = 1;
|
||||
} else {
|
||||
int input_passes = !cinfo->buffered_image && m->is_multiscan_ ? 1 : 0;
|
||||
bool two_pass_quant = cinfo->quantize_colors && !cinfo->colormap &&
|
||||
cinfo->two_pass_quantize && cinfo->enable_2pass_quant;
|
||||
cinfo->progress->total_passes = input_passes + (two_pass_quant ? 2 : 1);
|
||||
}
|
||||
cinfo->progress->completed_passes = 0;
|
||||
}
|
||||
|
||||
void InitProgressMonitorForOutput(j_decompress_ptr cinfo) {
|
||||
if (!cinfo->progress) return;
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
int passes_per_output = cinfo->enable_2pass_quant ? 2 : 1;
|
||||
int output_passes_left = cinfo->buffered_image && !m->found_eoi_ ? 2 : 1;
|
||||
cinfo->progress->total_passes =
|
||||
m->output_passes_done_ + passes_per_output * output_passes_left;
|
||||
cinfo->progress->completed_passes = m->output_passes_done_;
|
||||
}
|
||||
|
||||
void ProgressMonitorInputPass(j_decompress_ptr cinfo) {
|
||||
if (!cinfo->progress) return;
|
||||
cinfo->progress->pass_counter =
|
||||
((cinfo->input_scan_number - 1) * cinfo->total_iMCU_rows +
|
||||
cinfo->input_iMCU_row);
|
||||
if (cinfo->progress->pass_counter > cinfo->progress->pass_limit) {
|
||||
cinfo->progress->pass_limit =
|
||||
cinfo->input_scan_number * cinfo->total_iMCU_rows;
|
||||
}
|
||||
(*cinfo->progress->progress_monitor)(reinterpret_cast<j_common_ptr>(cinfo));
|
||||
}
|
||||
|
||||
void ProgressMonitorOutputPass(j_decompress_ptr cinfo) {
|
||||
if (!cinfo->progress) return;
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
int input_passes = !cinfo->buffered_image && m->is_multiscan_ ? 1 : 0;
|
||||
cinfo->progress->pass_counter = cinfo->output_scanline;
|
||||
cinfo->progress->pass_limit = cinfo->output_height;
|
||||
cinfo->progress->completed_passes = input_passes + m->output_passes_done_;
|
||||
(*cinfo->progress->progress_monitor)(reinterpret_cast<j_common_ptr>(cinfo));
|
||||
}
|
||||
|
||||
void BuildHuffmanLookupTable(j_decompress_ptr cinfo, JHUFF_TBL* table,
|
||||
HuffmanTableEntry* huff_lut) {
|
||||
uint32_t counts[kJpegHuffmanMaxBitLength + 1] = {};
|
||||
counts[0] = 0;
|
||||
int total_count = 0;
|
||||
int space = 1 << kJpegHuffmanMaxBitLength;
|
||||
int max_depth = 1;
|
||||
for (size_t i = 1; i <= kJpegHuffmanMaxBitLength; ++i) {
|
||||
int count = table->bits[i];
|
||||
if (count != 0) {
|
||||
max_depth = i;
|
||||
}
|
||||
counts[i] = count;
|
||||
total_count += count;
|
||||
space -= count * (1 << (kJpegHuffmanMaxBitLength - i));
|
||||
}
|
||||
uint32_t values[kJpegHuffmanAlphabetSize + 1] = {};
|
||||
uint8_t values_seen[256] = {0};
|
||||
for (int i = 0; i < total_count; ++i) {
|
||||
int value = table->huffval[i];
|
||||
if (values_seen[value]) {
|
||||
return JPEGLI_ERROR("Duplicate Huffman code value %d", value);
|
||||
}
|
||||
values_seen[value] = 1;
|
||||
values[i] = value;
|
||||
}
|
||||
// Add an invalid symbol that will have the all 1 code.
|
||||
++counts[max_depth];
|
||||
values[total_count] = kJpegHuffmanAlphabetSize;
|
||||
space -= (1 << (kJpegHuffmanMaxBitLength - max_depth));
|
||||
if (space < 0) {
|
||||
JPEGLI_ERROR("Invalid Huffman code lengths.");
|
||||
} else if (space > 0 && huff_lut[0].value != 0xffff) {
|
||||
// Re-initialize the values to an invalid symbol so that we can recognize
|
||||
// it when reading the bit stream using a Huffman code with space > 0.
|
||||
for (int i = 0; i < kJpegHuffmanLutSize; ++i) {
|
||||
huff_lut[i].bits = 0;
|
||||
huff_lut[i].value = 0xffff;
|
||||
}
|
||||
}
|
||||
BuildJpegHuffmanTable(&counts[0], &values[0], huff_lut);
|
||||
}
|
||||
|
||||
void PrepareForScan(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
for (int i = 0; i < cinfo->comps_in_scan; ++i) {
|
||||
int comp_idx = cinfo->cur_comp_info[i]->component_index;
|
||||
int* prev_coef_bits = cinfo->coef_bits[comp_idx + cinfo->num_components];
|
||||
for (int k = std::min(cinfo->Ss, 1); k <= std::max(cinfo->Se, 9); k++) {
|
||||
prev_coef_bits[k] =
|
||||
(cinfo->input_scan_number > 0) ? cinfo->coef_bits[comp_idx][k] : 0;
|
||||
}
|
||||
for (int k = cinfo->Ss; k <= cinfo->Se; ++k) {
|
||||
cinfo->coef_bits[comp_idx][k] = cinfo->Al;
|
||||
}
|
||||
}
|
||||
AddStandardHuffmanTables(reinterpret_cast<j_common_ptr>(cinfo),
|
||||
/*is_dc=*/false);
|
||||
AddStandardHuffmanTables(reinterpret_cast<j_common_ptr>(cinfo),
|
||||
/*is_dc=*/true);
|
||||
// Check that all the Huffman tables needed for this scan are defined and
|
||||
// build derived lookup tables.
|
||||
for (int i = 0; i < cinfo->comps_in_scan; ++i) {
|
||||
if (cinfo->Ss == 0) {
|
||||
int dc_tbl_idx = cinfo->cur_comp_info[i]->dc_tbl_no;
|
||||
JHUFF_TBL* table = cinfo->dc_huff_tbl_ptrs[dc_tbl_idx];
|
||||
HuffmanTableEntry* huff_lut =
|
||||
&m->dc_huff_lut_[dc_tbl_idx * kJpegHuffmanLutSize];
|
||||
if (!table) {
|
||||
return JPEGLI_ERROR("DC Huffman table %d not found", dc_tbl_idx);
|
||||
}
|
||||
BuildHuffmanLookupTable(cinfo, table, huff_lut);
|
||||
}
|
||||
if (cinfo->Se > 0) {
|
||||
int ac_tbl_idx = cinfo->cur_comp_info[i]->ac_tbl_no;
|
||||
JHUFF_TBL* table = cinfo->ac_huff_tbl_ptrs[ac_tbl_idx];
|
||||
HuffmanTableEntry* huff_lut =
|
||||
&m->ac_huff_lut_[ac_tbl_idx * kJpegHuffmanLutSize];
|
||||
if (!table) {
|
||||
return JPEGLI_ERROR("AC Huffman table %d not found", ac_tbl_idx);
|
||||
}
|
||||
BuildHuffmanLookupTable(cinfo, table, huff_lut);
|
||||
}
|
||||
}
|
||||
// Copy quantization tables into comp_info.
|
||||
for (int i = 0; i < cinfo->comps_in_scan; ++i) {
|
||||
jpeg_component_info* comp = cinfo->cur_comp_info[i];
|
||||
if (comp->quant_table == nullptr) {
|
||||
comp->quant_table = Allocate<JQUANT_TBL>(cinfo, 1, JPOOL_IMAGE);
|
||||
memcpy(comp->quant_table, cinfo->quant_tbl_ptrs[comp->quant_tbl_no],
|
||||
sizeof(JQUANT_TBL));
|
||||
}
|
||||
}
|
||||
if (cinfo->comps_in_scan == 1) {
|
||||
const auto& comp = *cinfo->cur_comp_info[0];
|
||||
cinfo->MCUs_per_row = DivCeil(cinfo->image_width * comp.h_samp_factor,
|
||||
cinfo->max_h_samp_factor * DCTSIZE);
|
||||
cinfo->MCU_rows_in_scan = DivCeil(cinfo->image_height * comp.v_samp_factor,
|
||||
cinfo->max_v_samp_factor * DCTSIZE);
|
||||
m->mcu_rows_per_iMCU_row_ = cinfo->cur_comp_info[0]->v_samp_factor;
|
||||
} else {
|
||||
cinfo->MCU_rows_in_scan = cinfo->total_iMCU_rows;
|
||||
cinfo->MCUs_per_row = m->iMCU_cols_;
|
||||
m->mcu_rows_per_iMCU_row_ = 1;
|
||||
size_t mcu_size = 0;
|
||||
for (int i = 0; i < cinfo->comps_in_scan; ++i) {
|
||||
jpeg_component_info* comp = cinfo->cur_comp_info[i];
|
||||
mcu_size += comp->h_samp_factor * comp->v_samp_factor;
|
||||
}
|
||||
if (mcu_size > D_MAX_BLOCKS_IN_MCU) {
|
||||
JPEGLI_ERROR("MCU size too big");
|
||||
}
|
||||
}
|
||||
memset(m->last_dc_coeff_, 0, sizeof(m->last_dc_coeff_));
|
||||
m->restarts_to_go_ = cinfo->restart_interval;
|
||||
m->next_restart_marker_ = 0;
|
||||
m->eobrun_ = -1;
|
||||
m->scan_mcu_row_ = 0;
|
||||
m->scan_mcu_col_ = 0;
|
||||
m->codestream_bits_ahead_ = 0;
|
||||
++cinfo->input_scan_number;
|
||||
cinfo->input_iMCU_row = 0;
|
||||
PrepareForiMCURow(cinfo);
|
||||
cinfo->global_state = kDecProcessScan;
|
||||
}
|
||||
|
||||
int ConsumeInput(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (cinfo->global_state == kDecProcessScan && m->streaming_mode_ &&
|
||||
cinfo->input_iMCU_row > cinfo->output_iMCU_row) {
|
||||
// Prevent input from getting ahead of output in streaming mode.
|
||||
return JPEG_SUSPENDED;
|
||||
}
|
||||
jpeg_source_mgr* src = cinfo->src;
|
||||
std::vector<uint8_t> buffer;
|
||||
const uint8_t* last_input_byte = src->next_input_byte + src->bytes_in_buffer;
|
||||
int status;
|
||||
for (;;) {
|
||||
if (cinfo->global_state == kDecProcessScan) {
|
||||
status = ProcessScan(cinfo);
|
||||
const uint8_t* data;
|
||||
size_t len;
|
||||
if (m->input_buffer_.empty()) {
|
||||
data = cinfo->src->next_input_byte;
|
||||
len = cinfo->src->bytes_in_buffer;
|
||||
} else {
|
||||
status = ProcessMarkers(cinfo);
|
||||
data = &m->input_buffer_[m->input_buffer_pos_];
|
||||
len = m->input_buffer_.size() - m->input_buffer_pos_;
|
||||
}
|
||||
if (status != JPEG_SUSPENDED) {
|
||||
size_t pos = 0;
|
||||
if (cinfo->global_state == kDecProcessScan) {
|
||||
status = ProcessScan(cinfo, data, len, &pos, &m->codestream_bits_ahead_);
|
||||
} else {
|
||||
status = ProcessMarkers(cinfo, data, len, &pos);
|
||||
}
|
||||
if (m->input_buffer_.empty()) {
|
||||
cinfo->src->next_input_byte += pos;
|
||||
cinfo->src->bytes_in_buffer -= pos;
|
||||
} else {
|
||||
m->input_buffer_pos_ += pos;
|
||||
size_t bytes_left = m->input_buffer_.size() - m->input_buffer_pos_;
|
||||
if (bytes_left <= src->bytes_in_buffer) {
|
||||
src->next_input_byte += (src->bytes_in_buffer - bytes_left);
|
||||
src->bytes_in_buffer = bytes_left;
|
||||
m->input_buffer_.clear();
|
||||
m->input_buffer_pos_ = 0;
|
||||
}
|
||||
}
|
||||
if (status == kHandleRestart) {
|
||||
JXL_DASSERT(m->input_buffer_.size() <=
|
||||
m->input_buffer_pos_ + src->bytes_in_buffer);
|
||||
m->input_buffer_.clear();
|
||||
m->input_buffer_pos_ = 0;
|
||||
if (cinfo->unread_marker == 0xd0 + m->next_restart_marker_) {
|
||||
cinfo->unread_marker = 0;
|
||||
} else {
|
||||
if (!(*cinfo->src->resync_to_restart)(cinfo, m->next_restart_marker_)) {
|
||||
return JPEG_SUSPENDED;
|
||||
}
|
||||
}
|
||||
m->next_restart_marker_ += 1;
|
||||
m->next_restart_marker_ &= 0x7;
|
||||
m->restarts_to_go_ = cinfo->restart_interval;
|
||||
if (cinfo->unread_marker != 0) {
|
||||
JPEGLI_WARN("Failed to resync to next restart marker, skipping scan.");
|
||||
return JPEG_SCAN_COMPLETED;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (status == kHandleMarkerProcessor) {
|
||||
JXL_DASSERT(m->input_buffer_.size() <=
|
||||
m->input_buffer_pos_ + src->bytes_in_buffer);
|
||||
m->input_buffer_.clear();
|
||||
m->input_buffer_pos_ = 0;
|
||||
if (!(*GetMarkerProcessor(cinfo))(cinfo)) {
|
||||
return JPEG_SUSPENDED;
|
||||
}
|
||||
cinfo->unread_marker = 0;
|
||||
continue;
|
||||
}
|
||||
if (status != kNeedMoreInput) {
|
||||
break;
|
||||
}
|
||||
if (buffer.size() != src->bytes_in_buffer) {
|
||||
// Save the unprocessed bytes in the input to a temporary buffer.
|
||||
buffer.assign(src->next_input_byte,
|
||||
src->next_input_byte + src->bytes_in_buffer);
|
||||
if (m->input_buffer_.empty()) {
|
||||
JXL_DASSERT(m->input_buffer_pos_ == 0);
|
||||
m->input_buffer_.assign(src->next_input_byte,
|
||||
src->next_input_byte + src->bytes_in_buffer);
|
||||
}
|
||||
if (!(*cinfo->src->fill_input_buffer)(cinfo)) {
|
||||
return status;
|
||||
m->input_buffer_.clear();
|
||||
m->input_buffer_pos_ = 0;
|
||||
return JPEG_SUSPENDED;
|
||||
}
|
||||
// Save the end of the current input so that we can restore it after the
|
||||
// input processing succeeds.
|
||||
last_input_byte = cinfo->src->next_input_byte + src->bytes_in_buffer;
|
||||
// Extend the temporary buffer with the new bytes and point the input to it.
|
||||
buffer.insert(buffer.end(), src->next_input_byte, last_input_byte);
|
||||
src->next_input_byte = buffer.data();
|
||||
src->bytes_in_buffer = buffer.size();
|
||||
if (src->bytes_in_buffer == 0) {
|
||||
JPEGLI_ERROR("Empty input.");
|
||||
}
|
||||
m->input_buffer_.insert(m->input_buffer_.end(), src->next_input_byte,
|
||||
src->next_input_byte + src->bytes_in_buffer);
|
||||
}
|
||||
// Restore the input pointer in case we had to change it to a temporary
|
||||
// buffer earlier.
|
||||
src->next_input_byte = last_input_byte - src->bytes_in_buffer;
|
||||
if (status == JPEG_SCAN_COMPLETED) {
|
||||
cinfo->global_state = kDecProcessMarkers;
|
||||
} else if (status == JPEG_REACHED_SOS) {
|
||||
cinfo->global_state =
|
||||
cinfo->global_state == kDecInHeader ? kDecHeaderDone : kDecProcessScan;
|
||||
if (cinfo->global_state == kDecInHeader) {
|
||||
cinfo->global_state = kDecHeaderDone;
|
||||
} else {
|
||||
PrepareForScan(cinfo);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
@ -93,7 +391,97 @@ bool IsInputReady(j_decompress_ptr cinfo) {
|
|||
if (cinfo->input_scan_number < cinfo->output_scan_number) {
|
||||
return false;
|
||||
}
|
||||
return cinfo->input_iMCU_row > cinfo->output_iMCU_row;
|
||||
if (cinfo->input_iMCU_row == cinfo->total_iMCU_rows) {
|
||||
return true;
|
||||
}
|
||||
return cinfo->input_iMCU_row >
|
||||
cinfo->output_iMCU_row + (cinfo->master->streaming_mode_ ? 0 : 2);
|
||||
}
|
||||
|
||||
bool ReadOutputPass(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (!m->pixels_) {
|
||||
size_t stride = cinfo->out_color_components * cinfo->output_width;
|
||||
size_t num_samples = cinfo->output_height * stride;
|
||||
m->pixels_ = Allocate<uint8_t>(cinfo, num_samples, JPOOL_IMAGE);
|
||||
m->scanlines_ =
|
||||
Allocate<JSAMPROW>(cinfo, cinfo->output_height, JPOOL_IMAGE);
|
||||
for (size_t i = 0; i < cinfo->output_height; ++i) {
|
||||
m->scanlines_[i] = &m->pixels_[i * stride];
|
||||
}
|
||||
}
|
||||
size_t num_output_rows = 0;
|
||||
while (num_output_rows < cinfo->output_height) {
|
||||
if (IsInputReady(cinfo)) {
|
||||
ProgressMonitorOutputPass(cinfo);
|
||||
ProcessOutput(cinfo, &num_output_rows, m->scanlines_,
|
||||
cinfo->output_height);
|
||||
} else if (ConsumeInput(cinfo) == JPEG_SUSPENDED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
cinfo->output_scanline = 0;
|
||||
cinfo->output_iMCU_row = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean PrepareQuantizedOutput(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (cinfo->raw_data_out) {
|
||||
JPEGLI_ERROR("Color quantization is not supported in raw data mode.");
|
||||
}
|
||||
if (m->output_data_type_ != JPEGLI_TYPE_UINT8) {
|
||||
JPEGLI_ERROR("Color quantization must use 8-bit mode.");
|
||||
}
|
||||
if (cinfo->colormap) {
|
||||
m->quant_mode_ = 3;
|
||||
} else if (cinfo->two_pass_quantize && cinfo->enable_2pass_quant) {
|
||||
m->quant_mode_ = 2;
|
||||
} else if (cinfo->enable_1pass_quant) {
|
||||
m->quant_mode_ = 1;
|
||||
} else {
|
||||
JPEGLI_ERROR("Invalid quantization mode change");
|
||||
}
|
||||
if (m->quant_mode_ > 1 && cinfo->dither_mode == JDITHER_ORDERED) {
|
||||
cinfo->dither_mode = JDITHER_FS;
|
||||
}
|
||||
if (m->quant_mode_ == 1) {
|
||||
ChooseColorMap1Pass(cinfo);
|
||||
} else if (m->quant_mode_ == 2) {
|
||||
m->quant_pass_ = 0;
|
||||
if (!ReadOutputPass(cinfo)) {
|
||||
return FALSE;
|
||||
}
|
||||
ChooseColorMap2Pass(cinfo);
|
||||
}
|
||||
if (m->quant_mode_ == 2 ||
|
||||
(m->quant_mode_ == 3 && m->regenerate_inverse_colormap_)) {
|
||||
CreateInverseColorMap(cinfo);
|
||||
}
|
||||
if (cinfo->dither_mode == JDITHER_ORDERED) {
|
||||
CreateOrderedDitherTables(cinfo);
|
||||
} else if (cinfo->dither_mode == JDITHER_FS) {
|
||||
InitFSDitherState(cinfo);
|
||||
}
|
||||
m->quant_pass_ = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void AllocateCoefficientBuffer(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
j_common_ptr comptr = reinterpret_cast<j_common_ptr>(cinfo);
|
||||
jvirt_barray_ptr* coef_arrays = jpegli::Allocate<jvirt_barray_ptr>(
|
||||
cinfo, cinfo->num_components, JPOOL_IMAGE);
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
size_t height_in_blocks =
|
||||
m->streaming_mode_ ? comp->v_samp_factor : comp->height_in_blocks;
|
||||
coef_arrays[c] = (*cinfo->mem->request_virt_barray)(
|
||||
comptr, JPOOL_IMAGE, TRUE, comp->width_in_blocks, height_in_blocks,
|
||||
comp->v_samp_factor);
|
||||
}
|
||||
cinfo->master->coef_arrays = coef_arrays;
|
||||
(*cinfo->mem->realize_virt_arrays)(comptr);
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
|
@ -106,25 +494,27 @@ void jpegli_CreateDecompress(j_decompress_ptr cinfo, int version,
|
|||
}
|
||||
jpegli::InitMemoryManager(reinterpret_cast<j_common_ptr>(cinfo));
|
||||
cinfo->is_decompressor = TRUE;
|
||||
cinfo->progress = nullptr;
|
||||
cinfo->src = nullptr;
|
||||
cinfo->marker_list = nullptr;
|
||||
cinfo->input_scan_number = 0;
|
||||
cinfo->quantize_colors = FALSE;
|
||||
cinfo->desired_number_of_colors = 0;
|
||||
for (int i = 0; i < NUM_QUANT_TBLS; i++) {
|
||||
cinfo->quant_tbl_ptrs[i] = nullptr;
|
||||
}
|
||||
for (int i = 0; i < NUM_HUFF_TBLS; i++) {
|
||||
cinfo->dc_huff_tbl_ptrs[i] = nullptr;
|
||||
cinfo->ac_huff_tbl_ptrs[i] = nullptr;
|
||||
}
|
||||
cinfo->global_state = jpegli::kDecStart;
|
||||
cinfo->buffered_image = FALSE;
|
||||
cinfo->raw_data_out = FALSE;
|
||||
cinfo->output_scanline = 0;
|
||||
cinfo->sample_range_limit = nullptr; // not used
|
||||
cinfo->rec_outbuf_height = 1; // output works with any buffer height
|
||||
cinfo->unread_marker = 0; // not used
|
||||
// TODO(szabadka) Fill this in for progressive mode.
|
||||
cinfo->coef_bits = nullptr;
|
||||
cinfo->master = new jpeg_decomp_master;
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
cinfo->master->app_marker_parsers[i] = nullptr;
|
||||
m->app_marker_parsers[i] = nullptr;
|
||||
}
|
||||
cinfo->master->com_marker_parser = nullptr;
|
||||
m->com_marker_parser = nullptr;
|
||||
memset(m->markers_to_save_, 0, sizeof(m->markers_to_save_));
|
||||
jpegli::InitializeDecompressParams(cinfo);
|
||||
jpegli::InitializeImage(cinfo);
|
||||
}
|
||||
|
||||
void jpegli_destroy_decompress(j_decompress_ptr cinfo) {
|
||||
|
@ -137,8 +527,12 @@ void jpegli_abort_decompress(j_decompress_ptr cinfo) {
|
|||
|
||||
void jpegli_save_markers(j_decompress_ptr cinfo, int marker_code,
|
||||
unsigned int length_limit) {
|
||||
// TODO(szabadka) Limit our memory usage by taking into account length_limit.
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
m->markers_to_save_.insert(marker_code);
|
||||
if (marker_code < 0xe0) {
|
||||
JPEGLI_ERROR("jpegli_save_markers: invalid marker code %d", marker_code);
|
||||
}
|
||||
m->markers_to_save_[marker_code - 0xe0] = 1;
|
||||
}
|
||||
|
||||
void jpegli_set_marker_processor(j_decompress_ptr cinfo, int marker_code,
|
||||
|
@ -156,7 +550,9 @@ void jpegli_set_marker_processor(j_decompress_ptr cinfo, int marker_code,
|
|||
|
||||
int jpegli_consume_input(j_decompress_ptr cinfo) {
|
||||
if (cinfo->global_state == jpegli::kDecStart) {
|
||||
(*cinfo->err->reset_error_mgr)(reinterpret_cast<j_common_ptr>(cinfo));
|
||||
(*cinfo->src->init_source)(cinfo);
|
||||
jpegli::InitializeDecompressParams(cinfo);
|
||||
jpegli::InitializeImage(cinfo);
|
||||
cinfo->global_state = jpegli::kDecInHeader;
|
||||
}
|
||||
|
@ -181,6 +577,9 @@ int jpegli_read_header(j_decompress_ptr cinfo, boolean require_image) {
|
|||
JPEGLI_ERROR("jpegli_read_header: unexpected state %d",
|
||||
cinfo->global_state);
|
||||
}
|
||||
if (cinfo->src == nullptr) {
|
||||
JPEGLI_ERROR("Missing source.");
|
||||
}
|
||||
for (;;) {
|
||||
int retcode = jpegli_consume_input(cinfo);
|
||||
if (retcode == JPEG_SUSPENDED) {
|
||||
|
@ -188,7 +587,11 @@ int jpegli_read_header(j_decompress_ptr cinfo, boolean require_image) {
|
|||
} else if (retcode == JPEG_REACHED_SOS) {
|
||||
break;
|
||||
} else if (retcode == JPEG_REACHED_EOI) {
|
||||
JPEGLI_ERROR("jpegli_read_header: unexpected EOI marker.");
|
||||
if (require_image) {
|
||||
JPEGLI_ERROR("jpegli_read_header: unexpected EOI marker.");
|
||||
}
|
||||
jpegli_abort_decompress(cinfo);
|
||||
return JPEG_HEADER_TABLES_ONLY;
|
||||
}
|
||||
};
|
||||
return JPEG_HEADER_OK;
|
||||
|
@ -219,15 +622,71 @@ boolean jpegli_read_icc_profile(j_decompress_ptr cinfo, JOCTET** icc_data_ptr,
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
void jpegli_calc_output_dimensions(j_decompress_ptr cinfo) {
|
||||
void jpegli_core_output_dimensions(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (!m->found_sof_) {
|
||||
JPEGLI_ERROR("No SOF marker found.");
|
||||
}
|
||||
// Resampling is not yet implemented.
|
||||
cinfo->output_width = cinfo->image_width;
|
||||
cinfo->output_height = cinfo->image_height;
|
||||
cinfo->output_components = cinfo->out_color_components;
|
||||
if (cinfo->raw_data_out) {
|
||||
if (cinfo->scale_num != 1 || cinfo->scale_denom != 1) {
|
||||
JPEGLI_ERROR("Output scaling is not supported in raw output mode");
|
||||
}
|
||||
}
|
||||
if (cinfo->scale_num != 1 || cinfo->scale_denom != 1) {
|
||||
int dctsize = 16;
|
||||
while (cinfo->scale_num * DCTSIZE <= cinfo->scale_denom * (dctsize - 1)) {
|
||||
--dctsize;
|
||||
}
|
||||
m->min_scaled_dct_size = dctsize;
|
||||
cinfo->output_width =
|
||||
jpegli::DivCeil(cinfo->image_width * dctsize, DCTSIZE);
|
||||
cinfo->output_height =
|
||||
jpegli::DivCeil(cinfo->image_height * dctsize, DCTSIZE);
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
m->scaled_dct_size[c] = m->min_scaled_dct_size;
|
||||
}
|
||||
} else {
|
||||
cinfo->output_width = cinfo->image_width;
|
||||
cinfo->output_height = cinfo->image_height;
|
||||
m->min_scaled_dct_size = DCTSIZE;
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
m->scaled_dct_size[c] = DCTSIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void jpegli_calc_output_dimensions(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
jpegli_core_output_dimensions(cinfo);
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
m->h_factor[c] = cinfo->max_h_samp_factor / comp->h_samp_factor;
|
||||
m->v_factor[c] = cinfo->max_v_samp_factor / comp->v_samp_factor;
|
||||
}
|
||||
if (cinfo->scale_num != 1 || cinfo->scale_denom != 1) {
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
// Prefer IDCT scaling over 2x upsampling.
|
||||
while (m->scaled_dct_size[c] < DCTSIZE && (m->v_factor[c] % 2) == 0 &&
|
||||
(m->h_factor[c] % 2) == 0) {
|
||||
m->scaled_dct_size[c] *= 2;
|
||||
m->v_factor[c] /= 2;
|
||||
m->h_factor[c] /= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cinfo->out_color_space == JCS_GRAYSCALE) {
|
||||
cinfo->out_color_components = 1;
|
||||
} else if (cinfo->out_color_space == JCS_RGB ||
|
||||
cinfo->out_color_space == JCS_YCbCr) {
|
||||
cinfo->out_color_components = 3;
|
||||
} else if (cinfo->out_color_space == JCS_CMYK ||
|
||||
cinfo->out_color_space == JCS_YCCK) {
|
||||
cinfo->out_color_components = 4;
|
||||
} else {
|
||||
cinfo->out_color_components = cinfo->num_components;
|
||||
}
|
||||
cinfo->output_components =
|
||||
cinfo->quantize_colors ? 1 : cinfo->out_color_components;
|
||||
cinfo->rec_outbuf_height = 1;
|
||||
}
|
||||
|
||||
|
@ -243,51 +702,77 @@ boolean jpegli_input_complete(j_decompress_ptr cinfo) {
|
|||
}
|
||||
|
||||
boolean jpegli_start_decompress(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (cinfo->global_state == jpegli::kDecHeaderDone) {
|
||||
m->streaming_mode_ = !m->is_multiscan_ && !cinfo->buffered_image &&
|
||||
(!cinfo->quantize_colors || !cinfo->two_pass_quantize);
|
||||
jpegli::AllocateCoefficientBuffer(cinfo);
|
||||
jpegli_calc_output_dimensions(cinfo);
|
||||
cinfo->global_state = jpegli::kDecProcessScan;
|
||||
jpegli::PrepareForScan(cinfo);
|
||||
if (cinfo->quantize_colors) {
|
||||
if (cinfo->colormap != nullptr) {
|
||||
cinfo->enable_external_quant = TRUE;
|
||||
} else if (cinfo->two_pass_quantize &&
|
||||
cinfo->out_color_space == JCS_RGB) {
|
||||
cinfo->enable_2pass_quant = TRUE;
|
||||
} else {
|
||||
cinfo->enable_1pass_quant = TRUE;
|
||||
}
|
||||
}
|
||||
jpegli::InitProgressMonitor(cinfo, /*coef_only=*/false);
|
||||
if (cinfo->buffered_image == TRUE) {
|
||||
cinfo->output_scan_number = 0;
|
||||
return TRUE;
|
||||
}
|
||||
} else if (!cinfo->master->is_multiscan_) {
|
||||
} else if (!m->is_multiscan_) {
|
||||
JPEGLI_ERROR("jpegli_start_decompress: unexpected state %d",
|
||||
cinfo->global_state);
|
||||
}
|
||||
if (cinfo->master->is_multiscan_) {
|
||||
if (m->is_multiscan_) {
|
||||
if (cinfo->global_state != jpegli::kDecProcessScan &&
|
||||
cinfo->global_state != jpegli::kDecProcessMarkers) {
|
||||
JPEGLI_ERROR("jpegli_start_decompress: unexpected state %d",
|
||||
cinfo->global_state);
|
||||
}
|
||||
while (!cinfo->master->found_eoi_) {
|
||||
int retcode = jpegli::ConsumeInput(cinfo);
|
||||
if (retcode == JPEG_SUSPENDED) {
|
||||
while (!m->found_eoi_) {
|
||||
jpegli::ProgressMonitorInputPass(cinfo);
|
||||
if (jpegli::ConsumeInput(cinfo) == JPEG_SUSPENDED) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
cinfo->output_scan_number = cinfo->input_scan_number;
|
||||
jpegli::PrepareForOutput(cinfo);
|
||||
return TRUE;
|
||||
if (cinfo->quantize_colors) {
|
||||
return jpegli::PrepareQuantizedOutput(cinfo);
|
||||
} else {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
boolean jpegli_start_output(j_decompress_ptr cinfo, int scan_number) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (!cinfo->buffered_image) {
|
||||
JPEGLI_ERROR("jpegli_start_output: buffered image mode was not set");
|
||||
}
|
||||
if (cinfo->global_state != jpegli::kDecProcessScan) {
|
||||
if (cinfo->global_state != jpegli::kDecProcessScan &&
|
||||
cinfo->global_state != jpegli::kDecProcessMarkers) {
|
||||
JPEGLI_ERROR("jpegli_start_output: unexpected state %d",
|
||||
cinfo->global_state);
|
||||
}
|
||||
cinfo->output_scan_number = std::max(1, scan_number);
|
||||
if (cinfo->master->found_eoi_) {
|
||||
if (m->found_eoi_) {
|
||||
cinfo->output_scan_number =
|
||||
std::min(cinfo->output_scan_number, cinfo->input_scan_number);
|
||||
}
|
||||
jpegli::InitProgressMonitorForOutput(cinfo);
|
||||
// TODO(szabadka): Figure out how much we can reuse.
|
||||
jpegli::PrepareForOutput(cinfo);
|
||||
return TRUE;
|
||||
if (cinfo->quantize_colors) {
|
||||
return jpegli::PrepareQuantizedOutput(cinfo);
|
||||
} else {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
boolean jpegli_finish_output(j_decompress_ptr cinfo) {
|
||||
|
@ -331,6 +816,7 @@ JDIMENSION jpegli_read_scanlines(j_decompress_ptr cinfo, JSAMPARRAY scanlines,
|
|||
if (cinfo->output_scanline + max_lines > cinfo->output_height) {
|
||||
max_lines = cinfo->output_height - cinfo->output_scanline;
|
||||
}
|
||||
jpegli::ProgressMonitorOutputPass(cinfo);
|
||||
size_t num_output_rows = 0;
|
||||
while (num_output_rows < max_lines) {
|
||||
if (jpegli::IsInputReady(cinfo)) {
|
||||
|
@ -349,19 +835,24 @@ JDIMENSION jpegli_skip_scanlines(j_decompress_ptr cinfo, JDIMENSION num_lines) {
|
|||
|
||||
void jpegli_crop_scanline(j_decompress_ptr cinfo, JDIMENSION* xoffset,
|
||||
JDIMENSION* width) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if ((cinfo->global_state != jpegli::kDecProcessScan &&
|
||||
cinfo->global_state != jpegli::kDecProcessMarkers) ||
|
||||
cinfo->output_scanline != 0) {
|
||||
JPEGLI_ERROR("jpegli_crop_decompress: unexpected state %d",
|
||||
cinfo->global_state);
|
||||
}
|
||||
if (cinfo->raw_data_out) {
|
||||
JPEGLI_ERROR("Output cropping is not supported in raw data mode");
|
||||
}
|
||||
if (xoffset == nullptr || width == nullptr || *width == 0 ||
|
||||
*xoffset + *width > cinfo->output_width) {
|
||||
JPEGLI_ERROR("jpegli_crop_scanline: Invalid arguments");
|
||||
}
|
||||
// TODO(szabadka) Skip the IDCT for skipped over blocks.
|
||||
size_t xend = *xoffset + *width;
|
||||
*xoffset = (*xoffset / DCTSIZE) * DCTSIZE;
|
||||
size_t iMCU_width = m->min_scaled_dct_size * cinfo->max_h_samp_factor;
|
||||
*xoffset = (*xoffset / iMCU_width) * iMCU_width;
|
||||
*width = xend - *xoffset;
|
||||
cinfo->master->xoffset_ = *xoffset;
|
||||
cinfo->output_width = *width;
|
||||
|
@ -379,6 +870,7 @@ JDIMENSION jpegli_read_raw_data(j_decompress_ptr cinfo, JSAMPIMAGE data,
|
|||
if (max_lines < iMCU_height) {
|
||||
JPEGLI_ERROR("jpegli_read_raw_data: output buffer too small");
|
||||
}
|
||||
jpegli::ProgressMonitorOutputPass(cinfo);
|
||||
while (!jpegli::IsInputReady(cinfo)) {
|
||||
if (jpegli::ConsumeInput(cinfo) == JPEG_SUSPENDED) {
|
||||
return 0;
|
||||
|
@ -392,39 +884,29 @@ JDIMENSION jpegli_read_raw_data(j_decompress_ptr cinfo, JSAMPIMAGE data,
|
|||
}
|
||||
|
||||
jvirt_barray_ptr* jpegli_read_coefficients(j_decompress_ptr cinfo) {
|
||||
if (cinfo->global_state != jpegli::kDecHeaderDone) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
m->streaming_mode_ = false;
|
||||
if (!cinfo->buffered_image && cinfo->global_state == jpegli::kDecHeaderDone) {
|
||||
jpegli::AllocateCoefficientBuffer(cinfo);
|
||||
jpegli_calc_output_dimensions(cinfo);
|
||||
jpegli::InitProgressMonitor(cinfo, /*coef_only=*/true);
|
||||
jpegli::PrepareForScan(cinfo);
|
||||
}
|
||||
if (cinfo->global_state != jpegli::kDecProcessScan &&
|
||||
cinfo->global_state != jpegli::kDecProcessMarkers) {
|
||||
JPEGLI_ERROR("jpegli_read_coefficients: unexpected state %d",
|
||||
cinfo->global_state);
|
||||
}
|
||||
cinfo->global_state = jpegli::kDecProcessScan;
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
while (!m->found_eoi_) {
|
||||
int retcode = jpegli::ConsumeInput(cinfo);
|
||||
if (retcode == JPEG_SUSPENDED) {
|
||||
return nullptr;
|
||||
if (!cinfo->buffered_image) {
|
||||
while (!m->found_eoi_) {
|
||||
jpegli::ProgressMonitorInputPass(cinfo);
|
||||
if (jpegli::ConsumeInput(cinfo) == JPEG_SUSPENDED) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
cinfo->output_scanline = cinfo->output_height;
|
||||
}
|
||||
j_common_ptr comptr = reinterpret_cast<j_common_ptr>(cinfo);
|
||||
jvirt_barray_ptr* coef_arrays = jpegli::Allocate<jvirt_barray_ptr>(
|
||||
cinfo, cinfo->num_components, JPOOL_IMAGE);
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
size_t xsize_blocks = cinfo->comp_info[c].width_in_blocks;
|
||||
size_t ysize_blocks = cinfo->comp_info[c].height_in_blocks;
|
||||
coef_arrays[c] = (*cinfo->mem->request_virt_barray)(
|
||||
comptr, JPOOL_IMAGE, FALSE, xsize_blocks, ysize_blocks, 1);
|
||||
}
|
||||
(*cinfo->mem->realize_virt_arrays)(comptr);
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
for (size_t by = 0; by < comp->height_in_blocks; ++by) {
|
||||
JBLOCKARRAY ba = (*cinfo->mem->access_virt_barray)(comptr, coef_arrays[c],
|
||||
by, 1, true);
|
||||
size_t stride = comp->width_in_blocks * sizeof(JBLOCK);
|
||||
size_t offset = by * comp->width_in_blocks * DCTSIZE2;
|
||||
memcpy(ba[0], &m->components_[c].coeffs[offset], stride);
|
||||
}
|
||||
}
|
||||
return coef_arrays;
|
||||
return m->coef_arrays;
|
||||
}
|
||||
|
||||
boolean jpegli_finish_decompress(j_decompress_ptr cinfo) {
|
||||
|
@ -433,26 +915,67 @@ boolean jpegli_finish_decompress(j_decompress_ptr cinfo) {
|
|||
JPEGLI_ERROR("jpegli_finish_decompress: unexpected state %d",
|
||||
cinfo->global_state);
|
||||
}
|
||||
if (!cinfo->buffered_image && cinfo->output_scanline < cinfo->output_height) {
|
||||
JPEGLI_ERROR("Incomplete output");
|
||||
}
|
||||
while (!cinfo->master->found_eoi_) {
|
||||
int retcode = jpegli::ConsumeInput(cinfo);
|
||||
if (retcode == JPEG_SUSPENDED) {
|
||||
if (jpegli::ConsumeInput(cinfo) == JPEG_SUSPENDED) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
(*cinfo->src->term_source)(cinfo);
|
||||
jpegli_abort_decompress(cinfo);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
boolean jpegli_resync_to_restart(j_decompress_ptr cinfo, int desired) {
|
||||
// The default resync_to_restart will just throw an error.
|
||||
JPEGLI_ERROR("Invalid restart marker found.");
|
||||
JPEGLI_WARN("Invalid restart marker found: 0x%02x vs 0x%02x.",
|
||||
cinfo->unread_marker, 0xd0 + desired);
|
||||
// This is a trivial implementation, we just let the decoder skip the entire
|
||||
// scan and attempt to render the partial input.
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void jpegli_new_colormap(j_decompress_ptr cinfo) {
|
||||
if (cinfo->global_state != jpegli::kDecProcessScan &&
|
||||
cinfo->global_state != jpegli::kDecProcessMarkers) {
|
||||
JPEGLI_ERROR("jpegli_new_colormap: unexpected state %d",
|
||||
cinfo->global_state);
|
||||
}
|
||||
if (!cinfo->buffered_image) {
|
||||
JPEGLI_ERROR("jpegli_new_colormap: not in buffered image mode");
|
||||
}
|
||||
if (!cinfo->enable_external_quant) {
|
||||
JPEGLI_ERROR("external colormap quantizer was not enabled");
|
||||
}
|
||||
if (!cinfo->quantize_colors || cinfo->colormap == nullptr) {
|
||||
JPEGLI_ERROR("jpegli_new_colormap: not in external colormap mode");
|
||||
}
|
||||
cinfo->master->regenerate_inverse_colormap_ = true;
|
||||
}
|
||||
|
||||
void jpegli_set_output_format(j_decompress_ptr cinfo, JpegliDataType data_type,
|
||||
JpegliEndianness endianness) {
|
||||
cinfo->master->output_data_type_ = data_type;
|
||||
cinfo->master->swap_endianness_ =
|
||||
((endianness == JPEGLI_BIG_ENDIAN && IsLittleEndian()) ||
|
||||
(endianness == JPEGLI_LITTLE_ENDIAN && !IsLittleEndian()));
|
||||
switch (data_type) {
|
||||
case JPEGLI_TYPE_UINT8:
|
||||
case JPEGLI_TYPE_UINT16:
|
||||
case JPEGLI_TYPE_FLOAT:
|
||||
cinfo->master->output_data_type_ = data_type;
|
||||
break;
|
||||
default:
|
||||
JPEGLI_ERROR("Unsupported data type %d", data_type);
|
||||
}
|
||||
switch (endianness) {
|
||||
case JPEGLI_NATIVE_ENDIAN:
|
||||
cinfo->master->swap_endianness_ = false;
|
||||
break;
|
||||
case JPEGLI_LITTLE_ENDIAN:
|
||||
cinfo->master->swap_endianness_ = !IsLittleEndian();
|
||||
break;
|
||||
case JPEGLI_BIG_ENDIAN:
|
||||
cinfo->master->swap_endianness_ = IsLittleEndian();
|
||||
break;
|
||||
default:
|
||||
JPEGLI_ERROR("Unsupported endianness %d", endianness);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,6 +72,9 @@ boolean jpegli_input_complete(j_decompress_ptr cinfo);
|
|||
|
||||
int jpegli_consume_input(j_decompress_ptr cinfo);
|
||||
|
||||
#if JPEG_LIB_VERSION >= 80
|
||||
void jpegli_core_output_dimensions(j_decompress_ptr cinfo);
|
||||
#endif
|
||||
void jpegli_calc_output_dimensions(j_decompress_ptr cinfo);
|
||||
|
||||
void jpegli_save_markers(j_decompress_ptr cinfo, int marker_code,
|
||||
|
@ -89,6 +92,8 @@ void jpegli_abort_decompress(j_decompress_ptr cinfo);
|
|||
|
||||
void jpegli_destroy_decompress(j_decompress_ptr cinfo);
|
||||
|
||||
void jpegli_new_colormap(j_decompress_ptr cinfo);
|
||||
|
||||
//
|
||||
// New API functions that are not available in libjpeg
|
||||
//
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -9,9 +9,6 @@
|
|||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <array>
|
||||
#include <hwy/aligned_allocator.h>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
@ -20,21 +17,20 @@
|
|||
|
||||
namespace jpegli {
|
||||
|
||||
typedef int16_t coeff_t;
|
||||
static constexpr int kNeedMoreInput = 100;
|
||||
static constexpr int kHandleRestart = 101;
|
||||
static constexpr int kHandleMarkerProcessor = 102;
|
||||
static constexpr int kProcessNextMarker = 103;
|
||||
static constexpr size_t kAllHuffLutSize = NUM_HUFF_TBLS * kJpegHuffmanLutSize;
|
||||
|
||||
// Represents one component of a jpeg file.
|
||||
struct DecJPEGComponent {
|
||||
// The DCT coefficients of this component, laid out block-by-block, divided
|
||||
// through the quantization matrix values.
|
||||
hwy::AlignedFreeUniquePtr<coeff_t[]> coeffs;
|
||||
};
|
||||
typedef int16_t coeff_t;
|
||||
|
||||
// State of the decoder that has to be saved before decoding one MCU in case
|
||||
// we run out of the bitstream.
|
||||
struct MCUCodingState {
|
||||
coeff_t last_dc_coeff[kMaxComponents];
|
||||
int eobrun;
|
||||
std::vector<coeff_t> coeffs;
|
||||
coeff_t coeffs[D_MAX_BLOCKS_IN_MCU * DCTSIZE2];
|
||||
};
|
||||
|
||||
} // namespace jpegli
|
||||
|
@ -45,24 +41,29 @@ struct jpeg_decomp_master {
|
|||
//
|
||||
// Input handling state.
|
||||
//
|
||||
std::vector<uint8_t> input_buffer_;
|
||||
size_t input_buffer_pos_;
|
||||
// Number of bits after codestream_pos_ that were already processed.
|
||||
size_t codestream_bits_ahead_ = 0;
|
||||
size_t codestream_bits_ahead_;
|
||||
bool streaming_mode_;
|
||||
|
||||
// Coefficient buffers
|
||||
jvirt_barray_ptr* coef_arrays;
|
||||
JBLOCKARRAY coeff_rows[jpegli::kMaxComponents];
|
||||
|
||||
//
|
||||
// Marker data processing state.
|
||||
//
|
||||
bool found_soi_ = false;
|
||||
bool found_dri_ = false;
|
||||
bool found_sof_ = false;
|
||||
bool found_eoi_ = false;
|
||||
size_t icc_index_ = 0;
|
||||
size_t icc_total_ = 0;
|
||||
bool found_soi_;
|
||||
bool found_dri_;
|
||||
bool found_sof_;
|
||||
bool found_eoi_;
|
||||
size_t icc_index_;
|
||||
size_t icc_total_;
|
||||
std::vector<uint8_t> icc_profile_;
|
||||
std::vector<jpegli::DecJPEGComponent> components_;
|
||||
std::vector<jpegli::HuffmanTableEntry> dc_huff_lut_;
|
||||
std::vector<jpegli::HuffmanTableEntry> ac_huff_lut_;
|
||||
uint8_t huff_slot_defined_[256] = {};
|
||||
std::set<int> markers_to_save_;
|
||||
jpegli::HuffmanTableEntry dc_huff_lut_[jpegli::kAllHuffLutSize];
|
||||
jpegli::HuffmanTableEntry ac_huff_lut_[jpegli::kAllHuffLutSize];
|
||||
uint8_t markers_to_save_[32];
|
||||
jpeg_marker_parser_method app_marker_parsers[16];
|
||||
jpeg_marker_parser_method com_marker_parser;
|
||||
// Whether this jpeg has multiple scans (progressive or non-interleaved
|
||||
|
@ -71,6 +72,8 @@ struct jpeg_decomp_master {
|
|||
|
||||
// Fields defined by SOF marker.
|
||||
size_t iMCU_cols_;
|
||||
int h_factor[jpegli::kMaxComponents];
|
||||
int v_factor[jpegli::kMaxComponents];
|
||||
|
||||
// Initialized at strat of frame.
|
||||
uint16_t scan_progression_[jpegli::kMaxComponents][DCTSIZE2];
|
||||
|
@ -91,30 +94,57 @@ struct jpeg_decomp_master {
|
|||
//
|
||||
// Rendering state.
|
||||
//
|
||||
int output_passes_done_;
|
||||
JpegliDataType output_data_type_ = JPEGLI_TYPE_UINT8;
|
||||
bool swap_endianness_ = false;
|
||||
size_t xoffset_ = 0;
|
||||
size_t xoffset_;
|
||||
|
||||
int min_scaled_dct_size;
|
||||
int scaled_dct_size[jpegli::kMaxComponents];
|
||||
|
||||
size_t raw_height_[jpegli::kMaxComponents];
|
||||
jpegli::RowBuffer<float> raw_output_[jpegli::kMaxComponents];
|
||||
jpegli::RowBuffer<float> render_output_[jpegli::kMaxComponents];
|
||||
|
||||
void (*inverse_transform[jpegli::kMaxComponents])(
|
||||
const int16_t* JXL_RESTRICT qblock, const float* JXL_RESTRICT dequant,
|
||||
const float* JXL_RESTRICT biases, float* JXL_RESTRICT scratch_space,
|
||||
float* JXL_RESTRICT output, size_t output_stride, size_t dctsize);
|
||||
|
||||
void (*color_transform)(float* row[jpegli::kMaxComponents], size_t len);
|
||||
|
||||
float* idct_scratch_;
|
||||
float* upsample_scratch_;
|
||||
uint8_t* output_scratch_;
|
||||
int16_t* smoothing_scratch_;
|
||||
float* dequant_;
|
||||
// 1 = 1pass, 2 = 2pass, 3 = external
|
||||
int quant_mode_;
|
||||
int quant_pass_;
|
||||
int num_colors_[jpegli::kMaxComponents];
|
||||
uint8_t* colormap_lut_;
|
||||
uint8_t* pixels_;
|
||||
JSAMPARRAY scanlines_;
|
||||
JDIMENSION max_lines_;
|
||||
size_t num_output_rows_;
|
||||
|
||||
std::array<size_t, jpegli::kMaxComponents> raw_height_;
|
||||
std::array<jpegli::RowBuffer<float>, jpegli::kMaxComponents> raw_output_;
|
||||
std::array<jpegli::RowBuffer<float>, jpegli::kMaxComponents> render_output_;
|
||||
|
||||
hwy::AlignedFreeUniquePtr<float[]> idct_scratch_;
|
||||
hwy::AlignedFreeUniquePtr<float[]> upsample_scratch_;
|
||||
hwy::AlignedFreeUniquePtr<uint8_t[]> output_scratch_;
|
||||
hwy::AlignedFreeUniquePtr<float[]> dequant_;
|
||||
std::vector<std::vector<uint8_t>> candidate_lists_;
|
||||
bool regenerate_inverse_colormap_;
|
||||
float* dither_[jpegli::kMaxComponents];
|
||||
float* error_row_[2 * jpegli::kMaxComponents];
|
||||
size_t dither_size_;
|
||||
size_t dither_mask_;
|
||||
|
||||
// Per channel and per frequency statistics about the number of nonzeros and
|
||||
// the sum of coefficient absolute values, used in dequantization bias
|
||||
// computation.
|
||||
hwy::AlignedFreeUniquePtr<int[]> nonzeros_;
|
||||
hwy::AlignedFreeUniquePtr<int[]> sumabs_;
|
||||
std::vector<size_t> num_processed_blocks_;
|
||||
hwy::AlignedFreeUniquePtr<float[]> biases_;
|
||||
int* nonzeros_;
|
||||
int* sumabs_;
|
||||
size_t num_processed_blocks_[jpegli::kMaxComponents];
|
||||
float* biases_;
|
||||
#define SAVED_COEFS 10
|
||||
// This holds the coef_bits of the scan before the current scan,
|
||||
// i.e. the bottom half when rendering incomplete scans.
|
||||
int (*coef_bits_latch)[SAVED_COEFS];
|
||||
int (*prev_coef_bits_latch)[SAVED_COEFS];
|
||||
bool apply_smoothing;
|
||||
};
|
||||
|
||||
#endif // LIB_JPEGLI_DECODE_INTERNAL_H_
|
||||
|
|
|
@ -12,23 +12,21 @@
|
|||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jpegli/huffman.h"
|
||||
#include "lib/jpegli/memory_manager.h"
|
||||
#include "lib/jpegli/source_manager.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
|
||||
namespace jpegli {
|
||||
namespace {
|
||||
|
||||
constexpr int kMaxSampling = 2;
|
||||
constexpr int kMaxDimPixels = 65535;
|
||||
constexpr uint8_t kIccProfileTag[12] = "ICC_PROFILE";
|
||||
|
||||
// Macros for commonly used error conditions.
|
||||
|
||||
#define JPEG_VERIFY_LEN(n) \
|
||||
if (pos + (n) > len) { \
|
||||
return JPEGLI_ERROR("Unexpected end of input: pos=%" PRIuS \
|
||||
" need=%d len=%" PRIuS, \
|
||||
pos, static_cast<int>(n), len); \
|
||||
#define JPEG_VERIFY_LEN(n) \
|
||||
if (pos + (n) > len) { \
|
||||
return JPEGLI_ERROR("Unexpected end of marker: pos=%" PRIuS \
|
||||
" need=%d len=%" PRIuS, \
|
||||
pos, static_cast<int>(n), len); \
|
||||
}
|
||||
|
||||
#define JPEG_VERIFY_INPUT(var, low, high) \
|
||||
|
@ -62,9 +60,9 @@ void ProcessSOF(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
|||
JPEGLI_ERROR("Duplicate SOF marker.");
|
||||
}
|
||||
m->found_sof_ = true;
|
||||
cinfo->progressive_mode = (data[1] == 0xc2);
|
||||
cinfo->progressive_mode = (cinfo->unread_marker == 0xc2);
|
||||
cinfo->arith_code = 0;
|
||||
size_t pos = 4;
|
||||
size_t pos = 2;
|
||||
JPEG_VERIFY_LEN(6);
|
||||
cinfo->data_precision = ReadUint8(data, &pos);
|
||||
cinfo->image_height = ReadUint16(data, &pos);
|
||||
|
@ -75,12 +73,11 @@ void ProcessSOF(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
|||
JPEG_VERIFY_INPUT(cinfo->image_width, 1, kMaxDimPixels);
|
||||
JPEG_VERIFY_INPUT(cinfo->num_components, 1, kMaxComponents);
|
||||
JPEG_VERIFY_LEN(3 * cinfo->num_components);
|
||||
cinfo->comp_info =
|
||||
jpegli::Allocate<jpeg_component_info>(cinfo, cinfo->num_components);
|
||||
m->components_.resize(cinfo->num_components);
|
||||
cinfo->comp_info = jpegli::Allocate<jpeg_component_info>(
|
||||
cinfo, cinfo->num_components, JPOOL_IMAGE);
|
||||
|
||||
// Read sampling factors and quant table index for each component.
|
||||
std::vector<bool> ids_seen(256, false);
|
||||
uint8_t ids_seen[256] = {0};
|
||||
cinfo->max_h_samp_factor = 1;
|
||||
cinfo->max_v_samp_factor = 1;
|
||||
for (int i = 0; i < cinfo->num_components; ++i) {
|
||||
|
@ -90,25 +87,26 @@ void ProcessSOF(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
|||
if (ids_seen[id]) { // (cf. section B.2.2, syntax of Ci)
|
||||
JPEGLI_ERROR("Duplicate ID %d in SOF.", id);
|
||||
}
|
||||
ids_seen[id] = true;
|
||||
ids_seen[id] = 1;
|
||||
comp->component_id = id;
|
||||
int factor = ReadUint8(data, &pos);
|
||||
int h_samp_factor = factor >> 4;
|
||||
int v_samp_factor = factor & 0xf;
|
||||
JPEG_VERIFY_INPUT(h_samp_factor, 1, kMaxSampling);
|
||||
JPEG_VERIFY_INPUT(v_samp_factor, 1, kMaxSampling);
|
||||
JPEG_VERIFY_INPUT(h_samp_factor, 1, MAX_SAMP_FACTOR);
|
||||
JPEG_VERIFY_INPUT(v_samp_factor, 1, MAX_SAMP_FACTOR);
|
||||
comp->h_samp_factor = h_samp_factor;
|
||||
comp->v_samp_factor = v_samp_factor;
|
||||
cinfo->max_h_samp_factor =
|
||||
std::max(cinfo->max_h_samp_factor, h_samp_factor);
|
||||
cinfo->max_v_samp_factor =
|
||||
std::max(cinfo->max_v_samp_factor, v_samp_factor);
|
||||
uint8_t quant_tbl_idx = ReadUint8(data, &pos);
|
||||
int quant_tbl_idx = ReadUint8(data, &pos);
|
||||
JPEG_VERIFY_INPUT(quant_tbl_idx, 0, NUM_QUANT_TBLS - 1);
|
||||
comp->quant_tbl_no = quant_tbl_idx;
|
||||
comp->quant_table = cinfo->quant_tbl_ptrs[quant_tbl_idx];
|
||||
if (comp->quant_table == nullptr) {
|
||||
if (cinfo->quant_tbl_ptrs[quant_tbl_idx] == nullptr) {
|
||||
JPEGLI_ERROR("Quantization table with index %u not found", quant_tbl_idx);
|
||||
}
|
||||
comp->quant_table = nullptr; // will be allocated after SOS marker
|
||||
}
|
||||
JPEG_VERIFY_MARKER_END();
|
||||
|
||||
|
@ -141,7 +139,6 @@ void ProcessSOF(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
|||
}
|
||||
cinfo->out_color_space = JCS_CMYK;
|
||||
}
|
||||
cinfo->out_color_components = cinfo->num_components;
|
||||
|
||||
// We have checked above that none of the sampling factors are 0, so the max
|
||||
// sampling factors can not be 0.
|
||||
|
@ -151,18 +148,17 @@ void ProcessSOF(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
|||
DivCeil(cinfo->image_width, cinfo->max_h_samp_factor * DCTSIZE);
|
||||
// Compute the block dimensions for each component.
|
||||
for (int i = 0; i < cinfo->num_components; ++i) {
|
||||
DecJPEGComponent* c = &m->components_[i];
|
||||
jpeg_component_info* comp = &cinfo->comp_info[i];
|
||||
if (cinfo->max_h_samp_factor % comp->h_samp_factor != 0 ||
|
||||
cinfo->max_v_samp_factor % comp->v_samp_factor != 0) {
|
||||
JPEGLI_ERROR("Non-integral subsampling ratios.");
|
||||
}
|
||||
comp->width_in_blocks = m->iMCU_cols_ * comp->h_samp_factor;
|
||||
comp->height_in_blocks = cinfo->total_iMCU_rows * comp->v_samp_factor;
|
||||
const uint64_t num_blocks =
|
||||
static_cast<uint64_t>(comp->width_in_blocks) * comp->height_in_blocks;
|
||||
c->coeffs = hwy::AllocateAligned<coeff_t>(num_blocks * DCTSIZE2);
|
||||
memset(c->coeffs.get(), 0, num_blocks * DCTSIZE2 * sizeof(coeff_t));
|
||||
m->h_factor[i] = cinfo->max_h_samp_factor / comp->h_samp_factor;
|
||||
m->v_factor[i] = cinfo->max_v_samp_factor / comp->v_samp_factor;
|
||||
comp->downsampled_width = DivCeil(cinfo->image_width, m->h_factor[i]);
|
||||
comp->downsampled_height = DivCeil(cinfo->image_height, m->v_factor[i]);
|
||||
comp->width_in_blocks = DivCeil(comp->downsampled_width, DCTSIZE);
|
||||
comp->height_in_blocks = DivCeil(comp->downsampled_height, DCTSIZE);
|
||||
}
|
||||
memset(m->scan_progression_, 0, sizeof(m->scan_progression_));
|
||||
}
|
||||
|
@ -172,21 +168,22 @@ void ProcessSOS(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
|||
if (!m->found_sof_) {
|
||||
JPEGLI_ERROR("Unexpected SOS marker.");
|
||||
}
|
||||
size_t pos = 4;
|
||||
size_t pos = 2;
|
||||
JPEG_VERIFY_LEN(1);
|
||||
cinfo->comps_in_scan = ReadUint8(data, &pos);
|
||||
JPEG_VERIFY_INPUT(cinfo->comps_in_scan, 1, cinfo->num_components);
|
||||
JPEG_VERIFY_INPUT(cinfo->comps_in_scan, 1, MAX_COMPS_IN_SCAN);
|
||||
|
||||
JPEG_VERIFY_LEN(2 * cinfo->comps_in_scan);
|
||||
bool is_interleaved = (cinfo->comps_in_scan > 1);
|
||||
std::vector<bool> ids_seen(256, false);
|
||||
uint8_t ids_seen[256] = {0};
|
||||
cinfo->blocks_in_MCU = 0;
|
||||
for (int i = 0; i < cinfo->comps_in_scan; ++i) {
|
||||
int id = ReadUint8(data, &pos);
|
||||
if (ids_seen[id]) { // (cf. section B.2.3, regarding CSj)
|
||||
return JPEGLI_ERROR("Duplicate ID %d in SOS.", id);
|
||||
}
|
||||
ids_seen[id] = true;
|
||||
ids_seen[id] = 1;
|
||||
jpeg_component_info* comp = nullptr;
|
||||
for (int j = 0; j < cinfo->num_components; ++j) {
|
||||
if (cinfo->comp_info[j].component_id == id) {
|
||||
|
@ -242,6 +239,24 @@ void ProcessSOS(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
|||
const uint16_t scan_bitmask =
|
||||
cinfo->Ah == 0 ? (0xffff << cinfo->Al) : (1u << cinfo->Al);
|
||||
const uint16_t refinement_bitmask = (1 << cinfo->Al) - 1;
|
||||
if (!cinfo->coef_bits) {
|
||||
cinfo->coef_bits =
|
||||
Allocate<int[DCTSIZE2]>(cinfo, cinfo->num_components * 2, JPOOL_IMAGE);
|
||||
m->coef_bits_latch =
|
||||
Allocate<int[SAVED_COEFS]>(cinfo, cinfo->num_components, JPOOL_IMAGE);
|
||||
m->prev_coef_bits_latch =
|
||||
Allocate<int[SAVED_COEFS]>(cinfo, cinfo->num_components, JPOOL_IMAGE);
|
||||
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
for (int i = 0; i < DCTSIZE2; ++i) {
|
||||
cinfo->coef_bits[c][i] = -1;
|
||||
if (i < SAVED_COEFS) {
|
||||
m->coef_bits_latch[c][i] = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < cinfo->comps_in_scan; ++i) {
|
||||
int comp_idx = cinfo->cur_comp_info[i]->component_index;
|
||||
for (int k = cinfo->Ss; k <= cinfo->Se; ++k) {
|
||||
|
@ -262,53 +277,13 @@ void ProcessSOS(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
|||
if (cinfo->Al > 10) {
|
||||
return JPEGLI_ERROR("Scan parameter Al=%d is not supported.", cinfo->Al);
|
||||
}
|
||||
// Check that all the Huffman tables needed for this scan are defined.
|
||||
for (int i = 0; i < cinfo->comps_in_scan; ++i) {
|
||||
int dc_tbl_idx = cinfo->cur_comp_info[i]->dc_tbl_no;
|
||||
int ac_tbl_idx = cinfo->cur_comp_info[i]->ac_tbl_no;
|
||||
if (cinfo->Ss == 0 && !m->huff_slot_defined_[dc_tbl_idx]) {
|
||||
return JPEGLI_ERROR(
|
||||
"SOS marker: Could not find DC Huffman table with index %d",
|
||||
dc_tbl_idx);
|
||||
}
|
||||
if (cinfo->Se > 0 && !m->huff_slot_defined_[ac_tbl_idx + 16]) {
|
||||
return JPEGLI_ERROR(
|
||||
"SOS marker: Could not find AC Huffman table with index %d",
|
||||
ac_tbl_idx);
|
||||
}
|
||||
}
|
||||
cinfo->MCU_rows_in_scan = cinfo->total_iMCU_rows;
|
||||
cinfo->MCUs_per_row = m->iMCU_cols_;
|
||||
m->mcu_rows_per_iMCU_row_ = 1;
|
||||
if (!is_interleaved) {
|
||||
const auto& comp = *cinfo->cur_comp_info[0];
|
||||
cinfo->MCUs_per_row = DivCeil(cinfo->image_width * comp.h_samp_factor,
|
||||
cinfo->max_h_samp_factor * DCTSIZE);
|
||||
cinfo->MCU_rows_in_scan = DivCeil(cinfo->image_height * comp.v_samp_factor,
|
||||
cinfo->max_v_samp_factor * DCTSIZE);
|
||||
m->mcu_rows_per_iMCU_row_ = cinfo->cur_comp_info[0]->v_samp_factor;
|
||||
}
|
||||
memset(m->last_dc_coeff_, 0, sizeof(m->last_dc_coeff_));
|
||||
m->restarts_to_go_ = cinfo->restart_interval;
|
||||
m->next_restart_marker_ = 0;
|
||||
m->eobrun_ = -1;
|
||||
m->scan_mcu_row_ = 0;
|
||||
m->scan_mcu_col_ = 0;
|
||||
m->codestream_bits_ahead_ = 0;
|
||||
m->mcu_.coeffs.resize(cinfo->blocks_in_MCU * DCTSIZE2);
|
||||
++cinfo->input_scan_number;
|
||||
cinfo->input_iMCU_row = 0;
|
||||
}
|
||||
|
||||
// Reads the Define Huffman Table (DHT) marker segment and builds the Huffman
|
||||
// decoding table in either dc_huff_lut_ or ac_huff_lut_, depending on the type
|
||||
// and solt_id of Huffman code being read.
|
||||
void ProcessDHT(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
constexpr int kLutSize = NUM_HUFF_TBLS * kJpegHuffmanLutSize;
|
||||
m->dc_huff_lut_.resize(kLutSize);
|
||||
m->ac_huff_lut_.resize(kLutSize);
|
||||
size_t pos = 4;
|
||||
size_t pos = 2;
|
||||
if (pos == len) {
|
||||
return JPEGLI_ERROR("DHT marker: no Huffman table found");
|
||||
}
|
||||
|
@ -317,79 +292,46 @@ void ProcessDHT(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
|||
// The index of the Huffman code in the current set of Huffman codes. For AC
|
||||
// component Huffman codes, 0x10 is added to the index.
|
||||
int slot_id = ReadUint8(data, &pos);
|
||||
m->huff_slot_defined_[slot_id] = 1;
|
||||
int huffman_index = slot_id;
|
||||
int is_ac_table = (slot_id & 0x10) != 0;
|
||||
HuffmanTableEntry* huff_lut;
|
||||
JHUFF_TBL** table;
|
||||
if (is_ac_table) {
|
||||
huffman_index -= 0x10;
|
||||
JPEG_VERIFY_INPUT(huffman_index, 0, NUM_HUFF_TBLS - 1);
|
||||
huff_lut = &m->ac_huff_lut_[huffman_index * kJpegHuffmanLutSize];
|
||||
table = &cinfo->ac_huff_tbl_ptrs[huffman_index];
|
||||
} else {
|
||||
JPEG_VERIFY_INPUT(huffman_index, 0, NUM_HUFF_TBLS - 1);
|
||||
huff_lut = &m->dc_huff_lut_[huffman_index * kJpegHuffmanLutSize];
|
||||
table = &cinfo->dc_huff_tbl_ptrs[huffman_index];
|
||||
}
|
||||
if (*table == nullptr) {
|
||||
*table = jpegli_alloc_huff_table(reinterpret_cast<j_common_ptr>(cinfo));
|
||||
}
|
||||
// Bit length histogram
|
||||
std::array<uint32_t, kJpegHuffmanMaxBitLength + 1> counts = {};
|
||||
counts[0] = 0;
|
||||
int total_count = 0;
|
||||
int space = 1 << kJpegHuffmanMaxBitLength;
|
||||
int max_depth = 1;
|
||||
for (size_t i = 1; i <= kJpegHuffmanMaxBitLength; ++i) {
|
||||
int count = ReadUint8(data, &pos);
|
||||
if (count != 0) {
|
||||
max_depth = i;
|
||||
}
|
||||
counts[i] = count;
|
||||
(*table)->bits[i] = count;
|
||||
total_count += count;
|
||||
space -= count * (1 << (kJpegHuffmanMaxBitLength - i));
|
||||
}
|
||||
if (is_ac_table) {
|
||||
JPEG_VERIFY_INPUT(total_count, 0, kJpegHuffmanAlphabetSize);
|
||||
} else {
|
||||
JPEG_VERIFY_INPUT(total_count, 0, kJpegDCAlphabetSize);
|
||||
// Allow symbols up to 15 here, we check later whether any invalid symbols
|
||||
// are actually decoded.
|
||||
// TODO(szabadka) Make sure decoder works (does not crash) with up to
|
||||
// 15-nbits DC symbols and then increase kJpegDCAlphabetSize.
|
||||
JPEG_VERIFY_INPUT(total_count, 0, 16);
|
||||
}
|
||||
JPEG_VERIFY_LEN(total_count);
|
||||
// Symbol values sorted by increasing bit lengths.
|
||||
std::array<uint32_t, kJpegHuffmanAlphabetSize + 1> values = {};
|
||||
std::vector<bool> values_seen(256, false);
|
||||
for (int i = 0; i < total_count; ++i) {
|
||||
int value = ReadUint8(data, &pos);
|
||||
if (!is_ac_table) {
|
||||
JPEG_VERIFY_INPUT(value, 0, kJpegDCAlphabetSize - 1);
|
||||
JPEG_VERIFY_INPUT(value, 0, 15);
|
||||
}
|
||||
if (values_seen[value]) {
|
||||
return JPEGLI_ERROR("Duplicate Huffman code value %d", value);
|
||||
}
|
||||
values_seen[value] = true;
|
||||
values[i] = value;
|
||||
(*table)->huffval[i] = value;
|
||||
}
|
||||
for (int i = total_count; i < kJpegHuffmanAlphabetSize; ++i) {
|
||||
(*table)->huffval[i] = 0;
|
||||
}
|
||||
// Add an invalid symbol that will have the all 1 code.
|
||||
++counts[max_depth];
|
||||
values[total_count] = kJpegHuffmanAlphabetSize;
|
||||
space -= (1 << (kJpegHuffmanMaxBitLength - max_depth));
|
||||
if (space < 0) {
|
||||
JPEGLI_ERROR("Invalid Huffman code lengths.");
|
||||
} else if (space > 0 && huff_lut[0].value != 0xffff) {
|
||||
// Re-initialize the values to an invalid symbol so that we can recognize
|
||||
// it when reading the bit stream using a Huffman code with space > 0.
|
||||
for (int i = 0; i < kJpegHuffmanLutSize; ++i) {
|
||||
huff_lut[i].bits = 0;
|
||||
huff_lut[i].value = 0xffff;
|
||||
}
|
||||
}
|
||||
BuildJpegHuffmanTable(&counts[0], &values[0], huff_lut);
|
||||
}
|
||||
JPEG_VERIFY_MARKER_END();
|
||||
}
|
||||
|
@ -399,7 +341,7 @@ void ProcessDQT(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
|||
if (m->found_sof_) {
|
||||
JPEGLI_ERROR("Updating quant tables between scans is not supported.");
|
||||
}
|
||||
size_t pos = 4;
|
||||
size_t pos = 2;
|
||||
if (pos == len) {
|
||||
return JPEGLI_ERROR("DQT marker: no quantization table found");
|
||||
}
|
||||
|
@ -428,13 +370,17 @@ void ProcessDQT(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
|||
JPEG_VERIFY_MARKER_END();
|
||||
}
|
||||
|
||||
void ProcessDNL(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
// Ignore marker.
|
||||
}
|
||||
|
||||
void ProcessDRI(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (m->found_dri_) {
|
||||
return JPEGLI_ERROR("Duplicate DRI marker.");
|
||||
}
|
||||
m->found_dri_ = true;
|
||||
size_t pos = 4;
|
||||
size_t pos = 2;
|
||||
JPEG_VERIFY_LEN(2);
|
||||
cinfo->restart_interval = ReadUint16(data, &pos);
|
||||
JPEG_VERIFY_MARKER_END();
|
||||
|
@ -442,13 +388,9 @@ void ProcessDRI(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
|||
|
||||
void ProcessAPP(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
const uint8_t marker = data[1];
|
||||
const uint8_t* payload = data + 4;
|
||||
size_t payload_size = len - 4;
|
||||
if (m->app_marker_parsers[marker - 0xe0] != nullptr) {
|
||||
(*m->app_marker_parsers[marker - 0xe0])(cinfo);
|
||||
return;
|
||||
}
|
||||
const uint8_t marker = cinfo->unread_marker;
|
||||
const uint8_t* payload = data + 2;
|
||||
size_t payload_size = len - 2;
|
||||
if (marker == 0xE0) {
|
||||
if (payload_size >= 14 && memcmp(payload, "JFIF", 4) == 0) {
|
||||
cinfo->saw_JFIF_marker = TRUE;
|
||||
|
@ -495,10 +437,7 @@ void ProcessAPP(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
|||
}
|
||||
|
||||
void ProcessCOM(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (m->com_marker_parser != nullptr) {
|
||||
(*m->com_marker_parser)(cinfo);
|
||||
}
|
||||
// Ignore marker.
|
||||
}
|
||||
|
||||
void ProcessSOI(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
|
@ -514,63 +453,78 @@ void ProcessEOI(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
|||
}
|
||||
|
||||
void SaveMarker(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
const uint8_t marker = data[1];
|
||||
const uint8_t* payload = data + 4;
|
||||
size_t payload_size = len - 4;
|
||||
const uint8_t marker = cinfo->unread_marker;
|
||||
const uint8_t* payload = data + 2;
|
||||
size_t payload_size = len - 2;
|
||||
|
||||
// Insert new saved marker to the head of the list.
|
||||
jpeg_saved_marker_ptr next = cinfo->marker_list;
|
||||
cinfo->marker_list = jpegli::Allocate<jpeg_marker_struct>(cinfo, 1);
|
||||
cinfo->marker_list =
|
||||
jpegli::Allocate<jpeg_marker_struct>(cinfo, 1, JPOOL_IMAGE);
|
||||
cinfo->marker_list->next = next;
|
||||
cinfo->marker_list->marker = marker;
|
||||
cinfo->marker_list->original_length = payload_size;
|
||||
cinfo->marker_list->data_length = payload_size;
|
||||
cinfo->marker_list->data = jpegli::Allocate<uint8_t>(cinfo, payload_size);
|
||||
cinfo->marker_list->data =
|
||||
jpegli::Allocate<uint8_t>(cinfo, payload_size, JPOOL_IMAGE);
|
||||
memcpy(cinfo->marker_list->data, payload, payload_size);
|
||||
}
|
||||
|
||||
uint8_t ProcessNextMarker(j_decompress_ptr cinfo) {
|
||||
const uint8_t* data = cinfo->src->next_input_byte;
|
||||
size_t len = cinfo->src->bytes_in_buffer;
|
||||
size_t pos = 0;
|
||||
uint8_t ProcessNextMarker(j_decompress_ptr cinfo, const uint8_t* const data,
|
||||
const size_t len, size_t* pos) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
// kIsValidMarker[i] == 1 means (0xc0 + i) is a valid marker.
|
||||
static const uint8_t kIsValidMarker[] = {
|
||||
1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
|
||||
};
|
||||
// Skip bytes between markers.
|
||||
size_t num_skipped = 0;
|
||||
while (pos + 1 < len && (data[pos] != 0xff || data[pos + 1] < 0xc0 ||
|
||||
!kIsValidMarker[data[pos + 1] - 0xc0])) {
|
||||
++(pos);
|
||||
++num_skipped;
|
||||
uint8_t marker = cinfo->unread_marker;
|
||||
if (marker == 0) {
|
||||
// kIsValidMarker[i] == 1 means (0xc0 + i) is a valid marker.
|
||||
static const uint8_t kIsValidMarker[] = {
|
||||
1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
|
||||
};
|
||||
// Skip bytes between markers.
|
||||
while (*pos + 1 < len && (data[*pos] != 0xff || data[*pos + 1] < 0xc0 ||
|
||||
!kIsValidMarker[data[*pos + 1] - 0xc0])) {
|
||||
++(*pos);
|
||||
++num_skipped;
|
||||
}
|
||||
if (*pos + 2 > len) {
|
||||
return kNeedMoreInput;
|
||||
}
|
||||
marker = data[*pos + 1];
|
||||
if (num_skipped > 0) {
|
||||
if (m->found_soi_) {
|
||||
JPEGLI_WARN("Skipped %d bytes before marker 0x%02x", (int)num_skipped,
|
||||
marker);
|
||||
} else {
|
||||
JPEGLI_ERROR("Did not find SOI marker.");
|
||||
}
|
||||
}
|
||||
*pos += 2;
|
||||
cinfo->unread_marker = marker;
|
||||
}
|
||||
if (pos + 2 > len) {
|
||||
return 0;
|
||||
}
|
||||
if (num_skipped > 0) {
|
||||
AdvanceInput(cinfo, num_skipped);
|
||||
}
|
||||
uint8_t marker = data[pos + 1];
|
||||
if (!m->found_soi_ && (num_skipped > 0 || marker != 0xd8)) {
|
||||
if (!m->found_soi_ && marker != 0xd8) {
|
||||
JPEGLI_ERROR("Did not find SOI marker.");
|
||||
}
|
||||
const uint8_t* marker_data = &data[pos];
|
||||
size_t marker_len = 2;
|
||||
if (GetMarkerProcessor(cinfo)) {
|
||||
return kHandleMarkerProcessor;
|
||||
}
|
||||
const uint8_t* marker_data = &data[*pos];
|
||||
size_t marker_len = 0;
|
||||
if (marker != 0xd8 && marker != 0xd9) {
|
||||
if (pos + 4 > len) {
|
||||
return 0;
|
||||
if (*pos + 2 > len) {
|
||||
return kNeedMoreInput;
|
||||
}
|
||||
marker_len += (data[pos + 2] << 8) + data[pos + 3];
|
||||
if (marker_len < 4) {
|
||||
marker_len += (data[*pos] << 8) + data[*pos + 1];
|
||||
if (marker_len < 2) {
|
||||
JPEGLI_ERROR("Invalid marker length");
|
||||
}
|
||||
if (pos + marker_len > len) {
|
||||
return 0;
|
||||
if (*pos + marker_len > len) {
|
||||
// TODO(szabadka) Limit our memory usage by using the skip_input_data
|
||||
// source manager callback on APP markers that are not saved.
|
||||
return kNeedMoreInput;
|
||||
}
|
||||
if (m->markers_to_save_.find(marker) != m->markers_to_save_.end()) {
|
||||
if (marker >= 0xe0 && m->markers_to_save_[marker - 0xe0]) {
|
||||
SaveMarker(cinfo, marker_data, marker_len);
|
||||
}
|
||||
}
|
||||
|
@ -582,6 +536,8 @@ uint8_t ProcessNextMarker(j_decompress_ptr cinfo) {
|
|||
ProcessSOS(cinfo, marker_data, marker_len);
|
||||
} else if (marker == 0xdb) {
|
||||
ProcessDQT(cinfo, marker_data, marker_len);
|
||||
} else if (marker == 0xdc) {
|
||||
ProcessDNL(cinfo, marker_data, marker_len);
|
||||
} else if (marker == 0xdd) {
|
||||
ProcessDRI(cinfo, marker_data, marker_len);
|
||||
} else if (marker >= 0xe0 && marker <= 0xef) {
|
||||
|
@ -595,25 +551,38 @@ uint8_t ProcessNextMarker(j_decompress_ptr cinfo) {
|
|||
} else {
|
||||
JPEGLI_ERROR("Unexpected marker 0x%x", marker);
|
||||
}
|
||||
pos += marker_len;
|
||||
AdvanceInput(cinfo, marker_len);
|
||||
return marker;
|
||||
*pos += marker_len;
|
||||
cinfo->unread_marker = 0;
|
||||
if (marker == 0xda) {
|
||||
return JPEG_REACHED_SOS;
|
||||
} else if (marker == 0xd9) {
|
||||
return JPEG_REACHED_EOI;
|
||||
}
|
||||
return kProcessNextMarker;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int ProcessMarkers(j_decompress_ptr cinfo) {
|
||||
jpeg_marker_parser_method GetMarkerProcessor(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
uint8_t marker = cinfo->unread_marker;
|
||||
jpeg_marker_parser_method callback = nullptr;
|
||||
if (marker >= 0xe0 && marker <= 0xef) {
|
||||
callback = m->app_marker_parsers[marker - 0xe0];
|
||||
} else if (marker == 0xfe) {
|
||||
callback = m->com_marker_parser;
|
||||
}
|
||||
return callback;
|
||||
}
|
||||
|
||||
int ProcessMarkers(j_decompress_ptr cinfo, const uint8_t* const data,
|
||||
const size_t len, size_t* pos) {
|
||||
for (;;) {
|
||||
uint8_t marker = ProcessNextMarker(cinfo);
|
||||
if (marker == 0) {
|
||||
break;
|
||||
} else if (marker == 0xd9) {
|
||||
return JPEG_REACHED_EOI;
|
||||
} else if (marker == 0xda) {
|
||||
return JPEG_REACHED_SOS;
|
||||
int status = ProcessNextMarker(cinfo, data, len, pos);
|
||||
if (status != kProcessNextMarker) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return JPEG_SUSPENDED;
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
|
|
|
@ -24,7 +24,10 @@ namespace jpegli {
|
|||
// EOI marker. Input buffer refill is handled by the caller;
|
||||
// * JPEG_REACHED_SOS, if the the next SOS marker is found;
|
||||
// * JPEG_REACHED_EOR, if the end of the input is found.
|
||||
int ProcessMarkers(j_decompress_ptr cinfo);
|
||||
int ProcessMarkers(j_decompress_ptr cinfo, const uint8_t* const data,
|
||||
const size_t len, size_t* pos);
|
||||
|
||||
jpeg_marker_parser_method GetMarkerProcessor(j_decompress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
#include <hwy/base.h>
|
||||
|
||||
#include "lib/jpegli/decode_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jpegli/source_manager.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
@ -84,16 +85,18 @@ struct BitReaderState {
|
|||
--pos_;
|
||||
// If we give back a 0 byte, we need to check if it was a 0xff/0x00 escape
|
||||
// sequence, and if yes, we need to give back one more byte.
|
||||
if (((pos_ == len_) ||
|
||||
if (((pos_ == len_ && pos_ == next_marker_pos_) ||
|
||||
(pos_ > 0 && pos_ < next_marker_pos_ && data_[pos_] == 0)) &&
|
||||
(data_[pos_ - 1] == 0xff)) {
|
||||
--pos_;
|
||||
}
|
||||
}
|
||||
if (pos_ > next_marker_pos_) {
|
||||
if (pos_ >= next_marker_pos_) {
|
||||
*pos = next_marker_pos_;
|
||||
// Data ran out before the scan was complete.
|
||||
return false;
|
||||
if (pos_ > next_marker_pos_ || *bit_pos > 0) {
|
||||
// Data ran out before the scan was complete.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*pos = pos_;
|
||||
return true;
|
||||
|
@ -343,13 +346,18 @@ void SaveMCUCodingState(j_decompress_ptr cinfo) {
|
|||
size_t offset = 0;
|
||||
for (int i = 0; i < cinfo->comps_in_scan; ++i) {
|
||||
const jpeg_component_info* comp = cinfo->cur_comp_info[i];
|
||||
DecJPEGComponent* c = &m->components_[comp->component_index];
|
||||
int block_x = m->scan_mcu_col_ * comp->MCU_width;
|
||||
int c = comp->component_index;
|
||||
size_t block_x = m->scan_mcu_col_ * comp->MCU_width;
|
||||
for (int iy = 0; iy < comp->MCU_height; ++iy) {
|
||||
int block_y = m->scan_mcu_row_ * comp->MCU_height + iy;
|
||||
size_t ncoeffs = comp->MCU_width * DCTSIZE2;
|
||||
int block_idx = (block_y * comp->width_in_blocks + block_x) * DCTSIZE2;
|
||||
coeff_t* coeffs = &c->coeffs[block_idx];
|
||||
size_t block_y = m->scan_mcu_row_ * comp->MCU_height + iy;
|
||||
size_t biy = block_y % comp->v_samp_factor;
|
||||
if (block_y >= comp->height_in_blocks) {
|
||||
continue;
|
||||
}
|
||||
size_t nblocks =
|
||||
std::min<size_t>(comp->MCU_width, comp->width_in_blocks - block_x);
|
||||
size_t ncoeffs = nblocks * DCTSIZE2;
|
||||
coeff_t* coeffs = &m->coeff_rows[c][biy][block_x][0];
|
||||
memcpy(&m->mcu_.coeffs[offset], coeffs, ncoeffs * sizeof(coeffs[0]));
|
||||
offset += ncoeffs;
|
||||
}
|
||||
|
@ -363,83 +371,131 @@ void RestoreMCUCodingState(j_decompress_ptr cinfo) {
|
|||
size_t offset = 0;
|
||||
for (int i = 0; i < cinfo->comps_in_scan; ++i) {
|
||||
const jpeg_component_info* comp = cinfo->cur_comp_info[i];
|
||||
DecJPEGComponent* c = &m->components_[comp->component_index];
|
||||
int block_x = m->scan_mcu_col_ * comp->MCU_width;
|
||||
int c = comp->component_index;
|
||||
size_t block_x = m->scan_mcu_col_ * comp->MCU_width;
|
||||
for (int iy = 0; iy < comp->MCU_height; ++iy) {
|
||||
int block_y = m->scan_mcu_row_ * comp->MCU_height + iy;
|
||||
size_t ncoeffs = comp->MCU_width * DCTSIZE2;
|
||||
int block_idx = (block_y * comp->width_in_blocks + block_x) * DCTSIZE2;
|
||||
coeff_t* coeffs = &c->coeffs[block_idx];
|
||||
size_t block_y = m->scan_mcu_row_ * comp->MCU_height + iy;
|
||||
size_t biy = block_y % comp->v_samp_factor;
|
||||
if (block_y >= comp->height_in_blocks) {
|
||||
continue;
|
||||
}
|
||||
size_t nblocks =
|
||||
std::min<size_t>(comp->MCU_width, comp->width_in_blocks - block_x);
|
||||
size_t ncoeffs = nblocks * DCTSIZE2;
|
||||
coeff_t* coeffs = &m->coeff_rows[c][biy][block_x][0];
|
||||
memcpy(coeffs, &m->mcu_.coeffs[offset], ncoeffs * sizeof(coeffs[0]));
|
||||
offset += ncoeffs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FinishScan(j_decompress_ptr cinfo, const uint8_t* data, const size_t len,
|
||||
size_t* pos, size_t* bit_pos) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (m->eobrun_ > 0) {
|
||||
JPEGLI_ERROR("End-of-block run too long.");
|
||||
}
|
||||
m->eobrun_ = -1;
|
||||
memset(m->last_dc_coeff_, 0, sizeof(m->last_dc_coeff_));
|
||||
if (*bit_pos == 0) {
|
||||
return true;
|
||||
}
|
||||
if (data[*pos] == 0xff) {
|
||||
// After last br.FinishStream we checked that there is at least 2 bytes
|
||||
// in the buffer.
|
||||
JXL_DASSERT(*pos + 1 < len);
|
||||
// br.FinishStream would have detected an early marker.
|
||||
JXL_DASSERT(data[*pos + 1] == 0);
|
||||
*pos += 2;
|
||||
} else {
|
||||
*pos += 1;
|
||||
}
|
||||
*bit_pos = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int ProcessScan(j_decompress_ptr cinfo) {
|
||||
const uint8_t* data = cinfo->src->next_input_byte;
|
||||
size_t len = cinfo->src->bytes_in_buffer;
|
||||
if (len == 0) {
|
||||
return JPEG_SUSPENDED;
|
||||
void PrepareForiMCURow(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
for (int i = 0; i < cinfo->comps_in_scan; ++i) {
|
||||
const jpeg_component_info* comp = cinfo->cur_comp_info[i];
|
||||
int c = comp->component_index;
|
||||
int by0 = cinfo->input_iMCU_row * comp->v_samp_factor;
|
||||
int block_rows_left = comp->height_in_blocks - by0;
|
||||
int max_block_rows = std::min(comp->v_samp_factor, block_rows_left);
|
||||
int offset = m->streaming_mode_ ? 0 : by0;
|
||||
m->coeff_rows[c] = (*cinfo->mem->access_virt_barray)(
|
||||
reinterpret_cast<j_common_ptr>(cinfo), m->coef_arrays[c], offset,
|
||||
max_block_rows, true);
|
||||
}
|
||||
}
|
||||
|
||||
int ProcessScan(j_decompress_ptr cinfo, const uint8_t* const data,
|
||||
const size_t len, size_t* pos, size_t* bit_pos) {
|
||||
if (len == 0) {
|
||||
return kNeedMoreInput;
|
||||
}
|
||||
size_t pos = 0;
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
for (;;) {
|
||||
// Handle the restart intervals.
|
||||
if (cinfo->restart_interval > 0 && m->restarts_to_go_ == 0) {
|
||||
if (m->eobrun_ > 0) {
|
||||
JPEGLI_ERROR("End-of-block run too long.");
|
||||
if (!FinishScan(cinfo, data, len, pos, bit_pos)) {
|
||||
return kNeedMoreInput;
|
||||
}
|
||||
if (m->codestream_bits_ahead_ > 0) {
|
||||
++pos;
|
||||
AdvanceInput(cinfo, 1);
|
||||
m->codestream_bits_ahead_ = 0;
|
||||
// Go to the next marker, warn if we had to skip any data.
|
||||
size_t num_skipped = 0;
|
||||
while (*pos + 1 < len && (data[*pos] != 0xff || data[*pos + 1] == 0 ||
|
||||
data[*pos + 1] == 0xff)) {
|
||||
++(*pos);
|
||||
++num_skipped;
|
||||
}
|
||||
if (pos + 2 > len) {
|
||||
return JPEG_SUSPENDED;
|
||||
if (num_skipped > 0) {
|
||||
JPEGLI_WARN("Skipped %d bytes before restart marker", (int)num_skipped);
|
||||
}
|
||||
int expected_marker = 0xd0 + m->next_restart_marker_;
|
||||
int marker = data[pos + 1];
|
||||
if (marker != expected_marker) {
|
||||
JPEGLI_ERROR("Did not find expected restart marker %d actual %d",
|
||||
expected_marker, marker);
|
||||
// TODO(szabadka) Use source manager's resync_to_restart callback here.
|
||||
if (*pos + 2 > len) {
|
||||
return kNeedMoreInput;
|
||||
}
|
||||
m->next_restart_marker_ += 1;
|
||||
m->next_restart_marker_ &= 0x7;
|
||||
m->restarts_to_go_ = cinfo->restart_interval;
|
||||
memset(m->last_dc_coeff_, 0, sizeof(m->last_dc_coeff_));
|
||||
m->eobrun_ = -1; // fresh start
|
||||
pos += 2;
|
||||
AdvanceInput(cinfo, 2);
|
||||
cinfo->unread_marker = data[*pos + 1];
|
||||
*pos += 2;
|
||||
return kHandleRestart;
|
||||
}
|
||||
|
||||
size_t start_pos = pos;
|
||||
size_t start_pos = *pos;
|
||||
BitReaderState br(data, len, start_pos);
|
||||
if (m->codestream_bits_ahead_ > 0) {
|
||||
br.ReadBits(m->codestream_bits_ahead_);
|
||||
if (*bit_pos > 0) {
|
||||
br.ReadBits(*bit_pos);
|
||||
}
|
||||
if (start_pos + kMaxMCUByteSize > len) {
|
||||
SaveMCUCodingState(cinfo);
|
||||
}
|
||||
|
||||
// Decode one MCU.
|
||||
HWY_ALIGN_MAX coeff_t dummy_block[DCTSIZE2];
|
||||
bool scan_ok = true;
|
||||
for (int i = 0; i < cinfo->comps_in_scan; ++i) {
|
||||
const jpeg_component_info* comp = cinfo->cur_comp_info[i];
|
||||
DecJPEGComponent* c = &m->components_[comp->component_index];
|
||||
int c = comp->component_index;
|
||||
const HuffmanTableEntry* dc_lut =
|
||||
&m->dc_huff_lut_[comp->dc_tbl_no * kJpegHuffmanLutSize];
|
||||
const HuffmanTableEntry* ac_lut =
|
||||
&m->ac_huff_lut_[comp->ac_tbl_no * kJpegHuffmanLutSize];
|
||||
for (int iy = 0; iy < comp->MCU_height; ++iy) {
|
||||
int block_y = m->scan_mcu_row_ * comp->MCU_height + iy;
|
||||
size_t block_y = m->scan_mcu_row_ * comp->MCU_height + iy;
|
||||
int biy = block_y % comp->v_samp_factor;
|
||||
for (int ix = 0; ix < comp->MCU_width; ++ix) {
|
||||
int block_x = m->scan_mcu_col_ * comp->MCU_width + ix;
|
||||
int block_idx = block_y * comp->width_in_blocks + block_x;
|
||||
coeff_t* coeffs = &c->coeffs[block_idx * DCTSIZE2];
|
||||
size_t block_x = m->scan_mcu_col_ * comp->MCU_width + ix;
|
||||
coeff_t* coeffs;
|
||||
if (block_x >= comp->width_in_blocks ||
|
||||
block_y >= comp->height_in_blocks) {
|
||||
// Note that it is OK that dummy_block is uninitialized because
|
||||
// it will never be used in any branches, even in the RefineDCTBlock
|
||||
// case, because only DC scans can be interleaved and we don't use
|
||||
// the zero-ness of the DC coeff in the DC refinement code-path.
|
||||
coeffs = dummy_block;
|
||||
} else {
|
||||
coeffs = &m->coeff_rows[c][biy][block_x][0];
|
||||
}
|
||||
if (cinfo->Ah == 0) {
|
||||
if (!DecodeDCTBlock(dc_lut, ac_lut, cinfo->Ss, cinfo->Se, cinfo->Al,
|
||||
&m->eobrun_, &br,
|
||||
|
@ -456,30 +512,31 @@ int ProcessScan(j_decompress_ptr cinfo) {
|
|||
}
|
||||
}
|
||||
}
|
||||
size_t bit_pos;
|
||||
size_t stream_pos;
|
||||
bool stream_ok = br.FinishStream(&stream_pos, &bit_pos);
|
||||
if (stream_pos + 2 > len) {
|
||||
size_t new_pos;
|
||||
size_t new_bit_pos;
|
||||
bool stream_ok = br.FinishStream(&new_pos, &new_bit_pos);
|
||||
if (new_pos + 2 > len) {
|
||||
// If reading stopped within the last two bytes, we have to request more
|
||||
// input even if FinishStream() returned true, since the Huffman code
|
||||
// reader could have peaked ahead some bits past the current input chunk
|
||||
// and thus the last prefix code length could have been wrong. We can do
|
||||
// this because a valid JPEG bit stream has two extra bytes at the end.
|
||||
RestoreMCUCodingState(cinfo);
|
||||
return JPEG_SUSPENDED;
|
||||
return kNeedMoreInput;
|
||||
}
|
||||
*pos = new_pos;
|
||||
*bit_pos = new_bit_pos;
|
||||
if (!stream_ok) {
|
||||
// We hit a marker during parsing.
|
||||
JXL_DASSERT(data[*pos] == 0xff);
|
||||
JXL_DASSERT(data[*pos + 1] != 0);
|
||||
RestoreMCUCodingState(cinfo);
|
||||
JPEGLI_WARN("Incomplete scan detected.");
|
||||
return JPEG_SCAN_COMPLETED;
|
||||
}
|
||||
if (!scan_ok) {
|
||||
JPEGLI_ERROR("Failed to decode DCT block");
|
||||
}
|
||||
if (!stream_ok) {
|
||||
// We hit a marker during parsing.
|
||||
JXL_DASSERT(data[stream_pos] == 0xff);
|
||||
JXL_DASSERT(data[stream_pos + 1] != 0);
|
||||
JPEGLI_ERROR("Unexpected end of scan.");
|
||||
}
|
||||
m->codestream_bits_ahead_ = bit_pos;
|
||||
pos = stream_pos;
|
||||
AdvanceInput(cinfo, pos - start_pos);
|
||||
if (m->restarts_to_go_ > 0) {
|
||||
--m->restarts_to_go_;
|
||||
}
|
||||
|
@ -488,14 +545,8 @@ int ProcessScan(j_decompress_ptr cinfo) {
|
|||
++m->scan_mcu_row_;
|
||||
m->scan_mcu_col_ = 0;
|
||||
if (m->scan_mcu_row_ == cinfo->MCU_rows_in_scan) {
|
||||
// Current scan is done, skip any remaining bits in the last byte.
|
||||
if (m->codestream_bits_ahead_ > 0) {
|
||||
++pos;
|
||||
AdvanceInput(cinfo, 1);
|
||||
m->codestream_bits_ahead_ = 0;
|
||||
}
|
||||
if (m->eobrun_ > 0) {
|
||||
JPEGLI_ERROR("End-of-block run too long.");
|
||||
if (!FinishScan(cinfo, data, len, pos, bit_pos)) {
|
||||
return kNeedMoreInput;
|
||||
}
|
||||
break;
|
||||
} else if ((m->scan_mcu_row_ % m->mcu_rows_per_iMCU_row_) == 0) {
|
||||
|
@ -505,8 +556,11 @@ int ProcessScan(j_decompress_ptr cinfo) {
|
|||
}
|
||||
}
|
||||
++cinfo->input_iMCU_row;
|
||||
return (m->scan_mcu_row_ == cinfo->MCU_rows_in_scan ? JPEG_SCAN_COMPLETED
|
||||
: JPEG_ROW_COMPLETED);
|
||||
if (cinfo->input_iMCU_row < cinfo->total_iMCU_rows) {
|
||||
PrepareForiMCURow(cinfo);
|
||||
return JPEG_ROW_COMPLETED;
|
||||
}
|
||||
return JPEG_SCAN_COMPLETED;
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
|
|
|
@ -23,7 +23,10 @@ namespace jpegli {
|
|||
// * JPEG_SUSPENDED, if the input buffer ends before the end of an iMCU row;
|
||||
// * JPEG_ROW_COMPLETED, if the next iMCU row (but not the scan) is reached;
|
||||
// * JPEG_SCAN_COMPLETED, if the end of the scan is reached.
|
||||
int ProcessScan(j_decompress_ptr cinfo);
|
||||
int ProcessScan(j_decompress_ptr cinfo, const uint8_t* const data,
|
||||
const size_t len, size_t* pos, size_t* bit_pos);
|
||||
|
||||
void PrepareForiMCURow(j_decompress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
|
|
|
@ -11,8 +11,6 @@
|
|||
|
||||
namespace jpegli {
|
||||
|
||||
void init_destination(j_compress_ptr cinfo) {}
|
||||
|
||||
constexpr size_t kDestBufferSize = 64 << 10;
|
||||
|
||||
struct StdioDestinationManager {
|
||||
|
@ -20,6 +18,12 @@ struct StdioDestinationManager {
|
|||
FILE* f;
|
||||
uint8_t* buffer;
|
||||
|
||||
static void init_destination(j_compress_ptr cinfo) {
|
||||
auto dest = reinterpret_cast<StdioDestinationManager*>(cinfo->dest);
|
||||
dest->pub.next_output_byte = dest->buffer;
|
||||
dest->pub.free_in_buffer = kDestBufferSize;
|
||||
}
|
||||
|
||||
static boolean empty_output_buffer(j_compress_ptr cinfo) {
|
||||
auto dest = reinterpret_cast<StdioDestinationManager*>(cinfo->dest);
|
||||
if (fwrite(dest->buffer, 1, kDestBufferSize, dest->f) != kDestBufferSize) {
|
||||
|
@ -55,6 +59,8 @@ struct MemoryDestinationManager {
|
|||
uint8_t* current_buffer;
|
||||
size_t buffer_size;
|
||||
|
||||
static void init_destination(j_compress_ptr cinfo) {}
|
||||
|
||||
static boolean empty_output_buffer(j_compress_ptr cinfo) {
|
||||
auto dest = reinterpret_cast<MemoryDestinationManager*>(cinfo->dest);
|
||||
uint8_t* next_buffer =
|
||||
|
@ -82,32 +88,53 @@ struct MemoryDestinationManager {
|
|||
} // namespace jpegli
|
||||
|
||||
void jpegli_stdio_dest(j_compress_ptr cinfo, FILE* outfile) {
|
||||
jpegli::StdioDestinationManager* dest =
|
||||
jpegli::Allocate<jpegli::StdioDestinationManager>(cinfo, 1);
|
||||
if (outfile == nullptr) {
|
||||
JPEGLI_ERROR("jpegli_stdio_dest: Invalid destination.");
|
||||
}
|
||||
if (cinfo->dest && cinfo->dest->init_destination !=
|
||||
jpegli::StdioDestinationManager::init_destination) {
|
||||
JPEGLI_ERROR("jpegli_stdio_dest: a different dest manager was already set");
|
||||
}
|
||||
if (!cinfo->dest) {
|
||||
cinfo->dest = reinterpret_cast<jpeg_destination_mgr*>(
|
||||
jpegli::Allocate<jpegli::StdioDestinationManager>(cinfo, 1));
|
||||
}
|
||||
auto dest = reinterpret_cast<jpegli::StdioDestinationManager*>(cinfo->dest);
|
||||
dest->f = outfile;
|
||||
dest->buffer = jpegli::Allocate<uint8_t>(cinfo, jpegli::kDestBufferSize);
|
||||
dest->pub.next_output_byte = dest->buffer;
|
||||
dest->pub.free_in_buffer = jpegli::kDestBufferSize;
|
||||
dest->pub.init_destination = jpegli::init_destination;
|
||||
dest->pub.init_destination =
|
||||
jpegli::StdioDestinationManager::init_destination;
|
||||
dest->pub.empty_output_buffer =
|
||||
jpegli::StdioDestinationManager::empty_output_buffer;
|
||||
dest->pub.term_destination =
|
||||
jpegli::StdioDestinationManager::term_destination;
|
||||
cinfo->dest = reinterpret_cast<jpeg_destination_mgr*>(dest);
|
||||
}
|
||||
|
||||
void jpegli_mem_dest(j_compress_ptr cinfo, unsigned char** outbuffer,
|
||||
unsigned long* outsize) {
|
||||
jpegli::MemoryDestinationManager* dest =
|
||||
jpegli::Allocate<jpegli::MemoryDestinationManager>(cinfo, 1);
|
||||
dest->pub.init_destination = jpegli::init_destination;
|
||||
if (outbuffer == nullptr || outsize == nullptr) {
|
||||
JPEGLI_ERROR("jpegli_mem_dest: Invalid destination.");
|
||||
}
|
||||
if (cinfo->dest && cinfo->dest->init_destination !=
|
||||
jpegli::MemoryDestinationManager::init_destination) {
|
||||
JPEGLI_ERROR("jpegli_mem_dest: a different dest manager was already set");
|
||||
}
|
||||
if (!cinfo->dest) {
|
||||
auto dest = jpegli::Allocate<jpegli::MemoryDestinationManager>(cinfo, 1);
|
||||
dest->temp_buffer = nullptr;
|
||||
cinfo->dest = reinterpret_cast<jpeg_destination_mgr*>(dest);
|
||||
}
|
||||
auto dest = reinterpret_cast<jpegli::MemoryDestinationManager*>(cinfo->dest);
|
||||
dest->pub.init_destination =
|
||||
jpegli::MemoryDestinationManager::init_destination;
|
||||
dest->pub.empty_output_buffer =
|
||||
jpegli::MemoryDestinationManager::empty_output_buffer;
|
||||
dest->pub.term_destination =
|
||||
jpegli::MemoryDestinationManager::term_destination;
|
||||
dest->output = outbuffer;
|
||||
dest->output_size = outsize;
|
||||
dest->temp_buffer = nullptr;
|
||||
if (*outbuffer == nullptr || *outsize == 0) {
|
||||
dest->temp_buffer =
|
||||
reinterpret_cast<uint8_t*>(malloc(jpegli::kDestBufferSize));
|
||||
|
@ -118,5 +145,4 @@ void jpegli_mem_dest(j_compress_ptr cinfo, unsigned char** outbuffer,
|
|||
dest->buffer_size = *outsize;
|
||||
dest->pub.next_output_byte = dest->current_buffer;
|
||||
dest->pub.free_in_buffer = dest->buffer_size;
|
||||
cinfo->dest = reinterpret_cast<jpeg_destination_mgr*>(dest);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,356 @@
|
|||
// 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/jpegli/downsample.h"
|
||||
|
||||
#undef HWY_TARGET_INCLUDE
|
||||
#define HWY_TARGET_INCLUDE "lib/jpegli/downsample.cc"
|
||||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jpegli {
|
||||
namespace HWY_NAMESPACE {
|
||||
|
||||
// These templates are not found via ADL.
|
||||
using hwy::HWY_NAMESPACE::Add;
|
||||
using hwy::HWY_NAMESPACE::Mul;
|
||||
using hwy::HWY_NAMESPACE::Vec;
|
||||
|
||||
using D = HWY_CAPPED(float, 8);
|
||||
constexpr D d;
|
||||
|
||||
void DownsampleRow2x1(const float* row_in, size_t len, float* row_out) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t len_out = len / 2;
|
||||
const auto mul = Set(d, 0.5f);
|
||||
Vec<D> v0, v1;
|
||||
for (size_t x = 0; x < len_out; x += N) {
|
||||
LoadInterleaved2(d, row_in + 2 * x, v0, v1);
|
||||
Store(Mul(mul, Add(v0, v1)), d, row_out + x);
|
||||
}
|
||||
}
|
||||
|
||||
void DownsampleRow3x1(const float* row_in, size_t len, float* row_out) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t len_out = len / 3;
|
||||
const auto mul = Set(d, 1.0f / 3);
|
||||
Vec<D> v0, v1, v2;
|
||||
for (size_t x = 0; x < len_out; x += N) {
|
||||
LoadInterleaved3(d, row_in + 3 * x, v0, v1, v2);
|
||||
Store(Mul(mul, Add(Add(v0, v1), v2)), d, row_out + x);
|
||||
}
|
||||
}
|
||||
|
||||
void DownsampleRow4x1(const float* row_in, size_t len, float* row_out) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t len_out = len / 4;
|
||||
const auto mul = Set(d, 0.25f);
|
||||
Vec<D> v0, v1, v2, v3;
|
||||
for (size_t x = 0; x < len_out; x += N) {
|
||||
LoadInterleaved4(d, row_in + 4 * x, v0, v1, v2, v3);
|
||||
Store(Mul(mul, Add(Add(v0, v1), Add(v2, v3))), d, row_out + x);
|
||||
}
|
||||
}
|
||||
|
||||
void Downsample2x1(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
DownsampleRow2x1(rows_in[0], len, row_out);
|
||||
}
|
||||
|
||||
void Downsample3x1(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
DownsampleRow3x1(rows_in[0], len, row_out);
|
||||
}
|
||||
|
||||
void Downsample4x1(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
DownsampleRow4x1(rows_in[0], len, row_out);
|
||||
}
|
||||
|
||||
void Downsample1x2(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
const size_t N = Lanes(d);
|
||||
const auto mul = Set(d, 0.5f);
|
||||
float* row0 = rows_in[0];
|
||||
float* row1 = rows_in[1];
|
||||
for (size_t x = 0; x < len; x += N) {
|
||||
Store(Mul(mul, Add(Load(d, row0 + x), Load(d, row1 + x))), d, row_out + x);
|
||||
}
|
||||
}
|
||||
|
||||
void Downsample2x2(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t len_out = len / 2;
|
||||
const auto mul = Set(d, 0.25f);
|
||||
float* row0 = rows_in[0];
|
||||
float* row1 = rows_in[1];
|
||||
Vec<D> v0, v1, v2, v3;
|
||||
for (size_t x = 0; x < len_out; x += N) {
|
||||
LoadInterleaved2(d, row0 + 2 * x, v0, v1);
|
||||
LoadInterleaved2(d, row1 + 2 * x, v2, v3);
|
||||
Store(Mul(mul, Add(Add(v0, v1), Add(v2, v3))), d, row_out + x);
|
||||
}
|
||||
}
|
||||
|
||||
void Downsample3x2(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
DownsampleRow3x1(rows_in[0], len, rows_in[0]);
|
||||
DownsampleRow3x1(rows_in[1], len, rows_in[1]);
|
||||
Downsample1x2(rows_in, len / 3, row_out);
|
||||
}
|
||||
|
||||
void Downsample4x2(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
DownsampleRow4x1(rows_in[0], len, rows_in[0]);
|
||||
DownsampleRow4x1(rows_in[1], len, rows_in[1]);
|
||||
Downsample1x2(rows_in, len / 4, row_out);
|
||||
}
|
||||
|
||||
void Downsample1x3(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
const size_t N = Lanes(d);
|
||||
const auto mul = Set(d, 1.0f / 3);
|
||||
float* row0 = rows_in[0];
|
||||
float* row1 = rows_in[1];
|
||||
float* row2 = rows_in[2];
|
||||
for (size_t x = 0; x < len; x += N) {
|
||||
const auto in0 = Load(d, row0 + x);
|
||||
const auto in1 = Load(d, row1 + x);
|
||||
const auto in2 = Load(d, row2 + x);
|
||||
Store(Mul(mul, Add(Add(in0, in1), in2)), d, row_out + x);
|
||||
}
|
||||
}
|
||||
|
||||
void Downsample2x3(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
DownsampleRow2x1(rows_in[0], len, rows_in[0]);
|
||||
DownsampleRow2x1(rows_in[1], len, rows_in[1]);
|
||||
DownsampleRow2x1(rows_in[2], len, rows_in[2]);
|
||||
Downsample1x3(rows_in, len / 2, row_out);
|
||||
}
|
||||
|
||||
void Downsample3x3(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
DownsampleRow3x1(rows_in[0], len, rows_in[0]);
|
||||
DownsampleRow3x1(rows_in[1], len, rows_in[1]);
|
||||
DownsampleRow3x1(rows_in[2], len, rows_in[2]);
|
||||
Downsample1x3(rows_in, len / 3, row_out);
|
||||
}
|
||||
|
||||
void Downsample4x3(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
DownsampleRow4x1(rows_in[0], len, rows_in[0]);
|
||||
DownsampleRow4x1(rows_in[1], len, rows_in[1]);
|
||||
DownsampleRow4x1(rows_in[2], len, rows_in[2]);
|
||||
Downsample1x3(rows_in, len / 4, row_out);
|
||||
}
|
||||
|
||||
void Downsample1x4(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
const size_t N = Lanes(d);
|
||||
const auto mul = Set(d, 0.25f);
|
||||
float* row0 = rows_in[0];
|
||||
float* row1 = rows_in[1];
|
||||
float* row2 = rows_in[2];
|
||||
float* row3 = rows_in[3];
|
||||
for (size_t x = 0; x < len; x += N) {
|
||||
const auto in0 = Load(d, row0 + x);
|
||||
const auto in1 = Load(d, row1 + x);
|
||||
const auto in2 = Load(d, row2 + x);
|
||||
const auto in3 = Load(d, row3 + x);
|
||||
Store(Mul(mul, Add(Add(in0, in1), Add(in2, in3))), d, row_out + x);
|
||||
}
|
||||
}
|
||||
|
||||
void Downsample2x4(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
DownsampleRow2x1(rows_in[0], len, rows_in[0]);
|
||||
DownsampleRow2x1(rows_in[1], len, rows_in[1]);
|
||||
DownsampleRow2x1(rows_in[2], len, rows_in[2]);
|
||||
DownsampleRow2x1(rows_in[3], len, rows_in[3]);
|
||||
Downsample1x4(rows_in, len / 2, row_out);
|
||||
}
|
||||
|
||||
void Downsample3x4(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
DownsampleRow3x1(rows_in[0], len, rows_in[0]);
|
||||
DownsampleRow3x1(rows_in[1], len, rows_in[1]);
|
||||
DownsampleRow3x1(rows_in[2], len, rows_in[2]);
|
||||
DownsampleRow3x1(rows_in[3], len, rows_in[3]);
|
||||
Downsample1x4(rows_in, len / 3, row_out);
|
||||
}
|
||||
|
||||
void Downsample4x4(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {
|
||||
DownsampleRow4x1(rows_in[0], len, rows_in[0]);
|
||||
DownsampleRow4x1(rows_in[1], len, rows_in[1]);
|
||||
DownsampleRow4x1(rows_in[2], len, rows_in[2]);
|
||||
DownsampleRow4x1(rows_in[3], len, rows_in[3]);
|
||||
Downsample1x4(rows_in, len / 4, row_out);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
|
||||
#if HWY_ONCE
|
||||
namespace jpegli {
|
||||
|
||||
HWY_EXPORT(Downsample1x2);
|
||||
HWY_EXPORT(Downsample1x3);
|
||||
HWY_EXPORT(Downsample1x4);
|
||||
HWY_EXPORT(Downsample2x1);
|
||||
HWY_EXPORT(Downsample2x2);
|
||||
HWY_EXPORT(Downsample2x3);
|
||||
HWY_EXPORT(Downsample2x4);
|
||||
HWY_EXPORT(Downsample3x1);
|
||||
HWY_EXPORT(Downsample3x2);
|
||||
HWY_EXPORT(Downsample3x3);
|
||||
HWY_EXPORT(Downsample3x4);
|
||||
HWY_EXPORT(Downsample4x1);
|
||||
HWY_EXPORT(Downsample4x2);
|
||||
HWY_EXPORT(Downsample4x3);
|
||||
HWY_EXPORT(Downsample4x4);
|
||||
|
||||
void NullDownsample(float* rows_in[MAX_SAMP_FACTOR], size_t len,
|
||||
float* row_out) {}
|
||||
|
||||
void ChooseDownsampleMethods(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
for (int c = 0; c < cinfo->num_components; c++) {
|
||||
m->downsample_method[c] = nullptr;
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
const int h_factor = cinfo->max_h_samp_factor / comp->h_samp_factor;
|
||||
const int v_factor = cinfo->max_v_samp_factor / comp->v_samp_factor;
|
||||
if (v_factor == 1) {
|
||||
if (h_factor == 1) {
|
||||
m->downsample_method[c] = NullDownsample;
|
||||
} else if (h_factor == 2) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample2x1);
|
||||
} else if (h_factor == 3) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample3x1);
|
||||
} else if (h_factor == 4) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample4x1);
|
||||
}
|
||||
} else if (v_factor == 2) {
|
||||
if (h_factor == 1) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample1x2);
|
||||
} else if (h_factor == 2) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample2x2);
|
||||
} else if (h_factor == 3) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample3x2);
|
||||
} else if (h_factor == 4) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample4x2);
|
||||
}
|
||||
} else if (v_factor == 3) {
|
||||
if (h_factor == 1) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample1x2);
|
||||
} else if (h_factor == 2) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample2x2);
|
||||
} else if (h_factor == 3) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample3x2);
|
||||
} else if (h_factor == 4) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample4x2);
|
||||
}
|
||||
} else if (v_factor == 4) {
|
||||
if (h_factor == 1) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample1x4);
|
||||
} else if (h_factor == 2) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample2x4);
|
||||
} else if (h_factor == 3) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample3x4);
|
||||
} else if (h_factor == 4) {
|
||||
m->downsample_method[c] = HWY_DYNAMIC_DISPATCH(Downsample4x4);
|
||||
}
|
||||
}
|
||||
if (m->downsample_method[c] == nullptr) {
|
||||
JPEGLI_ERROR("Unsupported downsampling ratio %dx%d", h_factor, v_factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DownsampleInputBuffer(j_compress_ptr cinfo) {
|
||||
if (cinfo->max_h_samp_factor == 1 && cinfo->max_v_samp_factor == 1) {
|
||||
return;
|
||||
}
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
const size_t iMCU_height = DCTSIZE * cinfo->max_v_samp_factor;
|
||||
const size_t y0 = m->next_iMCU_row * iMCU_height;
|
||||
const size_t y1 = y0 + iMCU_height;
|
||||
const size_t xsize_padded = m->xsize_blocks * DCTSIZE;
|
||||
for (int c = 0; c < cinfo->num_components; c++) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
const int h_factor = cinfo->max_h_samp_factor / comp->h_samp_factor;
|
||||
const int v_factor = cinfo->max_v_samp_factor / comp->v_samp_factor;
|
||||
if (h_factor == 1 && v_factor == 1) {
|
||||
continue;
|
||||
}
|
||||
auto& input = *m->smooth_input[c];
|
||||
auto& output = *m->raw_data[c];
|
||||
const size_t yout0 = y0 / v_factor;
|
||||
float* rows_in[MAX_SAMP_FACTOR];
|
||||
for (size_t yin = y0, yout = yout0; yin < y1; yin += v_factor, ++yout) {
|
||||
for (int iy = 0; iy < v_factor; ++iy) {
|
||||
rows_in[iy] = input.Row(yin + iy);
|
||||
}
|
||||
float* row_out = output.Row(yout);
|
||||
(*m->downsample_method[c])(rows_in, xsize_padded, row_out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyInputSmoothing(j_compress_ptr cinfo) {
|
||||
if (!cinfo->smoothing_factor) {
|
||||
return;
|
||||
}
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
const float kW1 = cinfo->smoothing_factor / 1024.0;
|
||||
const float kW0 = 1.0f - 8.0f * kW1;
|
||||
const size_t iMCU_height = DCTSIZE * cinfo->max_v_samp_factor;
|
||||
const ssize_t y0 = m->next_iMCU_row * iMCU_height;
|
||||
const ssize_t y1 = y0 + iMCU_height;
|
||||
const ssize_t xsize_padded = m->xsize_blocks * DCTSIZE;
|
||||
for (int c = 0; c < cinfo->num_components; c++) {
|
||||
auto& input = m->input_buffer[c];
|
||||
auto& output = *m->smooth_input[c];
|
||||
if (m->next_iMCU_row == 0) {
|
||||
input.CopyRow(-1, 0, 1);
|
||||
}
|
||||
if (m->next_iMCU_row + 1 == cinfo->total_iMCU_rows) {
|
||||
size_t last_row = m->ysize_blocks * DCTSIZE - 1;
|
||||
input.CopyRow(last_row + 1, last_row, 1);
|
||||
}
|
||||
// TODO(szabadka) SIMDify this.
|
||||
for (ssize_t y = y0; y < y1; ++y) {
|
||||
const float* row_t = input.Row(y - 1);
|
||||
const float* row_m = input.Row(y);
|
||||
const float* row_b = input.Row(y + 1);
|
||||
float* row_out = output.Row(y);
|
||||
for (ssize_t x = 0; x < xsize_padded; ++x) {
|
||||
float val_tl = row_t[x - 1];
|
||||
float val_tm = row_t[x];
|
||||
float val_tr = row_t[x + 1];
|
||||
float val_ml = row_m[x - 1];
|
||||
float val_mm = row_m[x];
|
||||
float val_mr = row_m[x + 1];
|
||||
float val_bl = row_b[x - 1];
|
||||
float val_bm = row_b[x];
|
||||
float val_br = row_b[x + 1];
|
||||
float val1 = (val_tl + val_tm + val_tr + val_ml + val_mr + val_bl +
|
||||
val_bm + val_br);
|
||||
row_out[x] = val_mm * kW0 + val1 * kW1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
#endif // HWY_ONCE
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_DOWNSAMPLE_H_
|
||||
#define LIB_JPEGLI_DOWNSAMPLE_H_
|
||||
|
||||
/* clang-format off */
|
||||
#include <stdio.h>
|
||||
#include <jpeglib.h>
|
||||
/* clang-format on */
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void ChooseDownsampleMethods(j_compress_ptr cinfo);
|
||||
|
||||
void DownsampleInputBuffer(j_compress_ptr cinfo);
|
||||
|
||||
void ApplyInputSmoothing(j_compress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_DOWNSAMPLE_H_
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -54,6 +54,10 @@ void jpegli_set_quality(j_compress_ptr cinfo, int quality,
|
|||
void jpegli_set_linear_quality(j_compress_ptr cinfo, int scale_factor,
|
||||
boolean force_baseline);
|
||||
|
||||
#if JPEG_LIB_VERSION >= 70
|
||||
void jpegli_default_qtables(j_compress_ptr cinfo, boolean force_baseline);
|
||||
#endif
|
||||
|
||||
int jpegli_quality_scaling(int quality);
|
||||
|
||||
void jpegli_add_quant_table(j_compress_ptr cinfo, int which_tbl,
|
||||
|
@ -64,6 +68,10 @@ void jpegli_simple_progression(j_compress_ptr cinfo);
|
|||
|
||||
void jpegli_suppress_tables(j_compress_ptr cinfo, boolean suppress);
|
||||
|
||||
#if JPEG_LIB_VERSION >= 70
|
||||
void jpegli_calc_jpeg_dimensions(j_compress_ptr cinfo);
|
||||
#endif
|
||||
|
||||
void jpegli_copy_critical_parameters(j_decompress_ptr srcinfo,
|
||||
j_compress_ptr dstinfo);
|
||||
|
||||
|
@ -104,8 +112,12 @@ void jpegli_destroy_compress(j_compress_ptr cinfo);
|
|||
// the future.
|
||||
//
|
||||
|
||||
// Sets the butteraugli target distance for the compressor.
|
||||
void jpegli_set_distance(j_compress_ptr cinfo, float distance);
|
||||
// Sets the butteraugli target distance for the compressor. This may override
|
||||
// the default quantization table indexes based on jpeg colorspace, therefore
|
||||
// it must be called after jpegli_set_defaults() or after the last
|
||||
// jpegli_set_colorspace() or jpegli_default_colorspace() calls.
|
||||
void jpegli_set_distance(j_compress_ptr cinfo, float distance,
|
||||
boolean force_baseline);
|
||||
|
||||
// Returns the butteraugli target distance for the given quality parameter.
|
||||
float jpegli_quality_to_distance(int quality);
|
||||
|
@ -115,6 +127,12 @@ float jpegli_quality_to_distance(int quality);
|
|||
// because some default setting depend on the XYB mode.
|
||||
void jpegli_set_xyb_mode(j_compress_ptr cinfo);
|
||||
|
||||
// Signals to the encoder that the pixel data that will be provided later
|
||||
// through jpegli_write_scanlines() has this transfer function. This must be
|
||||
// called before jpegli_set_defaults() because it changes the default
|
||||
// quantization tables.
|
||||
void jpegli_set_cicp_transfer_function(j_compress_ptr cinfo, int code);
|
||||
|
||||
void jpegli_set_input_format(j_compress_ptr cinfo, JpegliDataType data_type,
|
||||
JpegliEndianness endianness);
|
||||
|
||||
|
@ -131,7 +149,7 @@ void jpegli_set_progressive_level(j_compress_ptr cinfo, int level);
|
|||
// linear quality parameters will be used to scale the standard quantization
|
||||
// tables from Annex K of the JPEG standard. By default jpegli uses a different
|
||||
// set of quantization tables and used different scaling parameters for DC and
|
||||
// AC coefficients.
|
||||
// AC coefficients. Must be called before jpegli_set_defaults().
|
||||
void jpegli_use_standard_quant_tables(j_compress_ptr cinfo);
|
||||
|
||||
#if defined(__cplusplus) || defined(c_plusplus)
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -12,9 +12,7 @@
|
|||
#include <jpeglib.h>
|
||||
/* clang-format on */
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/bit_writer.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/encode.h"
|
||||
|
||||
|
@ -26,20 +24,19 @@ constexpr int kICCMarker = JPEG_APP0 + 2;
|
|||
|
||||
struct JPEGHuffmanCode {
|
||||
// Bit length histogram.
|
||||
std::array<uint32_t, kJpegHuffmanMaxBitLength + 1> counts = {};
|
||||
uint32_t counts[kJpegHuffmanMaxBitLength + 1];
|
||||
// Symbol values sorted by increasing bit lengths.
|
||||
std::array<uint32_t, kJpegHuffmanAlphabetSize + 1> values = {};
|
||||
uint32_t values[kJpegHuffmanAlphabetSize + 1];
|
||||
// The index of the Huffman code in the current set of Huffman codes. For AC
|
||||
// component Huffman codes, 0x10 is added to the index.
|
||||
int slot_id = 0;
|
||||
boolean sent_table = FALSE;
|
||||
int slot_id;
|
||||
boolean sent_table;
|
||||
};
|
||||
|
||||
// DCTCodingState: maximum number of correction bits to buffer
|
||||
const int kJPEGMaxCorrectionBits = 1u << 16;
|
||||
|
||||
constexpr int kDefaultProgressiveLevel = 2;
|
||||
constexpr float kDefaultQuantFieldMax = 0.575f;
|
||||
constexpr int kDefaultProgressiveLevel = 0;
|
||||
|
||||
struct HuffmanCodeTable {
|
||||
int depth[256];
|
||||
|
@ -50,7 +47,7 @@ struct ScanCodingInfo {
|
|||
uint32_t dc_tbl_idx[MAX_COMPS_IN_SCAN];
|
||||
uint32_t ac_tbl_idx[MAX_COMPS_IN_SCAN];
|
||||
// Number of Huffman codes defined in the DHT segment preceding this scan.
|
||||
size_t num_huffman_codes = 0;
|
||||
size_t num_huffman_codes;
|
||||
};
|
||||
|
||||
typedef int16_t coeff_t;
|
||||
|
@ -58,26 +55,47 @@ typedef int16_t coeff_t;
|
|||
} // namespace jpegli
|
||||
|
||||
struct jpeg_comp_master {
|
||||
std::array<jpegli::RowBuffer<float>, jpegli::kMaxComponents> input_buffer;
|
||||
float distance = 1.0;
|
||||
bool force_baseline = true;
|
||||
bool xyb_mode = false;
|
||||
bool use_std_tables = false;
|
||||
bool use_adaptive_quantization = true;
|
||||
int progressive_level = jpegli::kDefaultProgressiveLevel;
|
||||
size_t xsize_blocks = 0;
|
||||
size_t ysize_blocks = 0;
|
||||
std::vector<jpegli::ScanCodingInfo> scan_coding_info;
|
||||
std::vector<std::vector<uint8_t>> special_markers;
|
||||
uint8_t* next_marker_byte = nullptr;
|
||||
JpegliDataType data_type = JPEGLI_TYPE_UINT8;
|
||||
JpegliEndianness endianness = JPEGLI_NATIVE_ENDIAN;
|
||||
jpegli::RowBuffer<float> input_buffer[jpegli::kMaxComponents];
|
||||
jpegli::RowBuffer<float>* smooth_input[jpegli::kMaxComponents];
|
||||
jpegli::RowBuffer<float>* raw_data[jpegli::kMaxComponents];
|
||||
bool force_baseline;
|
||||
bool xyb_mode;
|
||||
uint8_t cicp_transfer_function;
|
||||
bool use_std_tables;
|
||||
bool use_adaptive_quantization;
|
||||
int progressive_level;
|
||||
size_t xsize_blocks;
|
||||
size_t ysize_blocks;
|
||||
size_t blocks_per_iMCU_row;
|
||||
jpegli::ScanCodingInfo* scan_coding_info;
|
||||
JpegliDataType data_type;
|
||||
JpegliEndianness endianness;
|
||||
void (*input_method)(const uint8_t* row_in, size_t len,
|
||||
float* row_out[jpegli::kMaxComponents]);
|
||||
void (*color_transform)(float* row[jpegli::kMaxComponents], size_t len);
|
||||
void (*downsample_method[jpegli::kMaxComponents])(
|
||||
float* rows_in[MAX_SAMP_FACTOR], size_t len, float* row_out);
|
||||
float* quant_mul[jpegli::kMaxComponents];
|
||||
float* zero_bias_offset[jpegli::kMaxComponents];
|
||||
float* zero_bias_mul[jpegli::kMaxComponents];
|
||||
int h_factor[jpegli::kMaxComponents];
|
||||
int v_factor[jpegli::kMaxComponents];
|
||||
jpegli::JPEGHuffmanCode* huffman_codes;
|
||||
size_t num_huffman_codes;
|
||||
jpegli::HuffmanCodeTable huff_tables[8];
|
||||
std::array<jpegli::HuffmanCodeTable, jpegli::kMaxHuffmanTables> dc_huff_table;
|
||||
std::array<jpegli::HuffmanCodeTable, jpegli::kMaxHuffmanTables> ac_huff_table;
|
||||
float* diff_buffer;
|
||||
jpegli::RowBuffer<float> fuzzy_erosion_tmp;
|
||||
jpegli::RowBuffer<float> pre_erosion;
|
||||
jpegli::RowBuffer<float> quant_field;
|
||||
float quant_field_max = jpegli::kDefaultQuantFieldMax;
|
||||
jvirt_barray_ptr* coeff_buffers = nullptr;
|
||||
jvirt_barray_ptr* coeff_buffers;
|
||||
size_t next_input_row;
|
||||
size_t next_iMCU_row;
|
||||
size_t last_dht_index;
|
||||
size_t last_restart_interval;
|
||||
JCOEF last_dc_coeff[MAX_COMPS_IN_SCAN];
|
||||
jpegli::JpegBitWriter bw;
|
||||
float* dct_buffer;
|
||||
int32_t* block_tmp;
|
||||
};
|
||||
|
||||
#endif // LIB_JPEGLI_ENCODE_INTERNAL_H_
|
||||
|
|
|
@ -7,21 +7,21 @@
|
|||
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jxl/enc_cluster.h"
|
||||
#include "lib/jxl/enc_huffman_tree.h"
|
||||
#include "lib/jpegli/huffman.h"
|
||||
#include "lib/jxl/base/bits.h"
|
||||
|
||||
namespace jpegli {
|
||||
namespace {
|
||||
|
||||
float HistogramCost(const jxl::Histogram& histo) {
|
||||
float HistogramCost(const Histogram& histo) {
|
||||
std::vector<uint32_t> counts(kJpegHuffmanAlphabetSize + 1);
|
||||
std::vector<uint8_t> depths(kJpegHuffmanAlphabetSize + 1);
|
||||
for (size_t i = 0; i < kJpegHuffmanAlphabetSize; ++i) {
|
||||
counts[i] = histo.data_[i];
|
||||
counts[i] = histo.count[i];
|
||||
}
|
||||
counts[kJpegHuffmanAlphabetSize] = 1;
|
||||
jxl::CreateHuffmanTree(counts.data(), counts.size(), kJpegHuffmanMaxBitLength,
|
||||
&depths[0]);
|
||||
CreateHuffmanTree(counts.data(), counts.size(), kJpegHuffmanMaxBitLength,
|
||||
&depths[0]);
|
||||
size_t header_bits = (1 + kJpegHuffmanMaxBitLength) * 8;
|
||||
size_t data_bits = 0;
|
||||
for (size_t i = 0; i < kJpegHuffmanAlphabetSize; ++i) {
|
||||
|
@ -32,36 +32,39 @@ float HistogramCost(const jxl::Histogram& histo) {
|
|||
}
|
||||
return header_bits + data_bits;
|
||||
}
|
||||
|
||||
void AddHistograms(const Histogram& a, const Histogram& b, Histogram* c) {
|
||||
for (size_t i = 0; i < kJpegHuffmanAlphabetSize; ++i) {
|
||||
c->count[i] = a.count[i] + b.count[i];
|
||||
}
|
||||
}
|
||||
|
||||
bool IsEmptyHistogram(const Histogram& histo) {
|
||||
for (size_t i = 0; i < kJpegHuffmanAlphabetSize; ++i) {
|
||||
if (histo.count[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ClusterJpegHistograms(const Histogram* histo_data, size_t num,
|
||||
void ClusterJpegHistograms(const Histogram* histograms, size_t num,
|
||||
JpegClusteredHistograms* clusters) {
|
||||
std::vector<jxl::Histogram> histograms;
|
||||
for (size_t idx = 0; idx < num; ++idx) {
|
||||
jxl::Histogram histo;
|
||||
histo.data_.resize(kJpegHuffmanAlphabetSize);
|
||||
for (size_t i = 0; i < histo.data_.size(); ++i) {
|
||||
histo.data_[i] = histo_data[idx].count[i];
|
||||
histo.total_count_ += histo.data_[i];
|
||||
}
|
||||
histograms.push_back(histo);
|
||||
}
|
||||
clusters->histogram_indexes.resize(histograms.size());
|
||||
clusters->histogram_indexes.resize(num);
|
||||
std::vector<uint32_t> slot_histograms;
|
||||
std::vector<float> slot_costs;
|
||||
for (size_t i = 0; i < histograms.size(); ++i) {
|
||||
const jxl::Histogram& cur = histograms[i];
|
||||
if (cur.total_count_ == 0) {
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
const Histogram& cur = histograms[i];
|
||||
if (IsEmptyHistogram(cur)) {
|
||||
continue;
|
||||
}
|
||||
float best_cost = HistogramCost(cur);
|
||||
size_t best_slot = slot_histograms.size();
|
||||
for (size_t j = 0; j < slot_histograms.size(); ++j) {
|
||||
size_t prev_idx = slot_histograms[j];
|
||||
const jxl::Histogram& prev = clusters->histograms[prev_idx];
|
||||
jxl::Histogram combined;
|
||||
combined.AddHistogram(prev);
|
||||
combined.AddHistogram(cur);
|
||||
const Histogram& prev = clusters->histograms[prev_idx];
|
||||
Histogram combined;
|
||||
AddHistograms(prev, cur, &combined);
|
||||
float combined_cost = HistogramCost(combined);
|
||||
float cost = combined_cost - slot_costs[j];
|
||||
if (cost < best_cost) {
|
||||
|
@ -88,7 +91,8 @@ void ClusterJpegHistograms(const Histogram* histo_data, size_t num,
|
|||
} else {
|
||||
// Merge this histogram with a previous one.
|
||||
size_t histogram_index = slot_histograms[best_slot];
|
||||
clusters->histograms[histogram_index].AddHistogram(cur);
|
||||
const Histogram& prev = clusters->histograms[histogram_index];
|
||||
AddHistograms(prev, cur, &clusters->histograms[histogram_index]);
|
||||
clusters->histogram_indexes[i] = histogram_index;
|
||||
JXL_ASSERT(clusters->slot_ids[histogram_index] == best_slot);
|
||||
slot_costs[best_slot] += best_cost;
|
||||
|
@ -96,15 +100,15 @@ void ClusterJpegHistograms(const Histogram* histo_data, size_t num,
|
|||
}
|
||||
}
|
||||
|
||||
void BuildJpegHuffmanCode(const jxl::Histogram& histo, JPEGHuffmanCode* huff) {
|
||||
void BuildJpegHuffmanCode(const Histogram& histo, JPEGHuffmanCode* huff) {
|
||||
std::vector<uint32_t> counts(kJpegHuffmanAlphabetSize + 1);
|
||||
std::vector<uint8_t> depths(kJpegHuffmanAlphabetSize + 1);
|
||||
for (size_t j = 0; j < kJpegHuffmanAlphabetSize; ++j) {
|
||||
counts[j] = histo.data_[j];
|
||||
counts[j] = histo.count[j];
|
||||
}
|
||||
counts[kJpegHuffmanAlphabetSize] = 1;
|
||||
jxl::CreateHuffmanTree(counts.data(), counts.size(), kJpegHuffmanMaxBitLength,
|
||||
&depths[0]);
|
||||
CreateHuffmanTree(counts.data(), counts.size(), kJpegHuffmanMaxBitLength,
|
||||
&depths[0]);
|
||||
std::fill(std::begin(huff->counts), std::end(huff->counts), 0);
|
||||
std::fill(std::begin(huff->values), std::end(huff->values), 0);
|
||||
for (size_t i = 0; i <= kJpegHuffmanAlphabetSize; ++i) {
|
||||
|
@ -123,27 +127,31 @@ void BuildJpegHuffmanCode(const jxl::Histogram& histo, JPEGHuffmanCode* huff) {
|
|||
}
|
||||
}
|
||||
|
||||
void AddJpegHuffmanCode(const jxl::Histogram& histogram, size_t slot_id,
|
||||
std::vector<JPEGHuffmanCode>* huff_codes) {
|
||||
JPEGHuffmanCode huff_code;
|
||||
void AddJpegHuffmanCode(const Histogram& histogram, size_t slot_id,
|
||||
JPEGHuffmanCode* huff_codes, size_t* num_huff_codes) {
|
||||
JPEGHuffmanCode huff_code = {};
|
||||
huff_code.slot_id = slot_id;
|
||||
BuildJpegHuffmanCode(histogram, &huff_code);
|
||||
huff_codes->emplace_back(std::move(huff_code));
|
||||
memcpy(&huff_codes[*num_huff_codes], &huff_code, sizeof(huff_code));
|
||||
++(*num_huff_codes);
|
||||
}
|
||||
|
||||
namespace {
|
||||
void SetJpegHuffmanCode(const JpegClusteredHistograms& clusters,
|
||||
size_t histogram_id, size_t slot_id_offset,
|
||||
std::vector<uint32_t>& slot_histograms,
|
||||
uint32_t* slot_id,
|
||||
std::vector<JPEGHuffmanCode>* huff_codes) {
|
||||
uint32_t* slot_id, bool* is_baseline,
|
||||
JPEGHuffmanCode* huff_codes, size_t* num_huff_codes) {
|
||||
JXL_ASSERT(histogram_id < clusters.histogram_indexes.size());
|
||||
uint32_t histogram_index = clusters.histogram_indexes[histogram_id];
|
||||
uint32_t id = clusters.slot_ids[histogram_index];
|
||||
if (id > 1) {
|
||||
*is_baseline = false;
|
||||
}
|
||||
*slot_id = id + (slot_id_offset / 4);
|
||||
if (slot_histograms[id] != histogram_index) {
|
||||
AddJpegHuffmanCode(clusters.histograms[histogram_index],
|
||||
slot_id_offset + id, huff_codes);
|
||||
slot_id_offset + id, huff_codes, num_huff_codes);
|
||||
slot_histograms[id] = histogram_index;
|
||||
}
|
||||
}
|
||||
|
@ -178,8 +186,7 @@ static JXL_INLINE void ProcessEndOfBand(DCTState* s, size_t new_refinement_bits,
|
|||
}
|
||||
|
||||
bool ProcessDCTBlockSequential(const coeff_t* coeffs, Histogram* dc_histo,
|
||||
Histogram* ac_histo, int num_zero_runs,
|
||||
coeff_t* last_dc_coeff) {
|
||||
Histogram* ac_histo, coeff_t* last_dc_coeff) {
|
||||
coeff_t temp2;
|
||||
coeff_t temp;
|
||||
temp2 = coeffs[0];
|
||||
|
@ -217,10 +224,6 @@ bool ProcessDCTBlockSequential(const coeff_t* coeffs, Histogram* dc_histo,
|
|||
++ac_histo->count[symbol];
|
||||
r = 0;
|
||||
}
|
||||
for (int i = 0; i < num_zero_runs; ++i) {
|
||||
++ac_histo->count[0xf0];
|
||||
r -= 16;
|
||||
}
|
||||
if (r > 0) {
|
||||
++ac_histo->count[0];
|
||||
}
|
||||
|
@ -229,8 +232,7 @@ bool ProcessDCTBlockSequential(const coeff_t* coeffs, Histogram* dc_histo,
|
|||
|
||||
bool ProcessDCTBlockProgressive(const coeff_t* coeffs, Histogram* dc_histo,
|
||||
Histogram* ac_histo, int Ss, int Se, int Al,
|
||||
int num_zero_runs, DCTState* s,
|
||||
coeff_t* last_dc_coeff) {
|
||||
DCTState* s, coeff_t* last_dc_coeff) {
|
||||
bool eob_run_allowed = Ss > 0;
|
||||
coeff_t temp2;
|
||||
coeff_t temp;
|
||||
|
@ -280,13 +282,6 @@ bool ProcessDCTBlockProgressive(const coeff_t* coeffs, Histogram* dc_histo,
|
|||
++ac_histo->count[symbol];
|
||||
r = 0;
|
||||
}
|
||||
if (num_zero_runs > 0) {
|
||||
ProcessFlush(s);
|
||||
for (int i = 0; i < num_zero_runs; ++i) {
|
||||
++ac_histo->count[0xf0];
|
||||
r -= 16;
|
||||
}
|
||||
}
|
||||
if (r > 0) {
|
||||
ProcessEndOfBand(s, 0, ac_histo);
|
||||
if (!eob_run_allowed) {
|
||||
|
@ -346,10 +341,21 @@ bool ProcessRefinementBits(const coeff_t* coeffs, Histogram* ac_histo, int Ss,
|
|||
return true;
|
||||
}
|
||||
|
||||
void ProgressMonitorHistogramPass(j_compress_ptr cinfo, size_t scan_index,
|
||||
size_t mcu_y) {
|
||||
if (cinfo->progress == nullptr) {
|
||||
return;
|
||||
}
|
||||
cinfo->progress->completed_passes = 1 + scan_index;
|
||||
cinfo->progress->pass_counter = mcu_y;
|
||||
cinfo->progress->pass_limit = cinfo->total_iMCU_rows;
|
||||
(*cinfo->progress->progress_monitor)(reinterpret_cast<j_common_ptr>(cinfo));
|
||||
}
|
||||
|
||||
bool ProcessScan(j_compress_ptr cinfo,
|
||||
const std::vector<std::vector<jpegli::coeff_t> >& coeffs,
|
||||
size_t scan_index, int* histo_index, Histogram* dc_histograms,
|
||||
Histogram* ac_histograms) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
size_t restart_interval = RestartIntervalForScan(cinfo, scan_index);
|
||||
int restarts_to_go = restart_interval;
|
||||
coeff_t last_dc_coeff[MAX_COMPS_IN_SCAN] = {0};
|
||||
|
@ -379,7 +385,20 @@ bool ProcessScan(j_compress_ptr cinfo,
|
|||
const int Se = scan_info->Se;
|
||||
constexpr coeff_t kDummyBlock[DCTSIZE2] = {0};
|
||||
|
||||
JBLOCKARRAY ba[MAX_COMPS_IN_SCAN];
|
||||
for (int mcu_y = 0; mcu_y < MCU_rows; ++mcu_y) {
|
||||
ProgressMonitorHistogramPass(cinfo, scan_index, mcu_y);
|
||||
for (int i = 0; i < scan_info->comps_in_scan; ++i) {
|
||||
int comp_idx = scan_info->component_index[i];
|
||||
jpeg_component_info* comp = &cinfo->comp_info[comp_idx];
|
||||
int n_blocks_y = is_interleaved ? comp->v_samp_factor : 1;
|
||||
int by0 = mcu_y * n_blocks_y;
|
||||
int block_rows_left = comp->height_in_blocks - by0;
|
||||
int max_block_rows = std::min(n_blocks_y, block_rows_left);
|
||||
ba[i] = (*cinfo->mem->access_virt_barray)(
|
||||
reinterpret_cast<j_common_ptr>(cinfo), m->coeff_buffers[comp_idx],
|
||||
by0, max_block_rows, false);
|
||||
}
|
||||
for (int mcu_x = 0; mcu_x < MCUs_per_row; ++mcu_x) {
|
||||
// Possibly emit a restart marker.
|
||||
if (restart_interval > 0 && restarts_to_go == 0) {
|
||||
|
@ -400,21 +419,20 @@ bool ProcessScan(j_compress_ptr cinfo,
|
|||
for (int ix = 0; ix < n_blocks_x; ++ix) {
|
||||
size_t block_y = mcu_y * n_blocks_y + iy;
|
||||
size_t block_x = mcu_x * n_blocks_x + ix;
|
||||
size_t block_idx = block_y * comp->width_in_blocks + block_x;
|
||||
size_t num_zero_runs = 0;
|
||||
const coeff_t* block = &coeffs[comp_idx][block_idx << 6];
|
||||
const coeff_t* block;
|
||||
if (block_x >= comp->width_in_blocks ||
|
||||
block_y >= comp->height_in_blocks) {
|
||||
block = kDummyBlock;
|
||||
} else {
|
||||
block = &ba[i][iy][block_x][0];
|
||||
}
|
||||
bool ok;
|
||||
if (!is_progressive) {
|
||||
ok = ProcessDCTBlockSequential(block, dc_histo, ac_histo,
|
||||
num_zero_runs, last_dc_coeff + i);
|
||||
last_dc_coeff + i);
|
||||
} else if (Ah == 0) {
|
||||
ok = ProcessDCTBlockProgressive(block, dc_histo, ac_histo, Ss, Se,
|
||||
Al, num_zero_runs, &s,
|
||||
last_dc_coeff + i);
|
||||
Al, &s, last_dc_coeff + i);
|
||||
} else {
|
||||
ok = ProcessRefinementBits(block, ac_histo, Ss, Se, Al, &s);
|
||||
}
|
||||
|
@ -431,53 +449,20 @@ bool ProcessScan(j_compress_ptr cinfo,
|
|||
}
|
||||
|
||||
void ProcessJpeg(j_compress_ptr cinfo,
|
||||
const std::vector<std::vector<jpegli::coeff_t> >& coeffs,
|
||||
std::vector<Histogram>* dc_histograms,
|
||||
std::vector<Histogram>* ac_histograms) {
|
||||
int histo_index = 0;
|
||||
for (int i = 0; i < cinfo->num_scans; ++i) {
|
||||
if (!ProcessScan(cinfo, coeffs, i, &histo_index, &(*dc_histograms)[0],
|
||||
if (!ProcessScan(cinfo, i, &histo_index, &(*dc_histograms)[0],
|
||||
&(*ac_histograms)[0])) {
|
||||
JPEGLI_ERROR("Invalid scan.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ValidateHuffmanTable(j_compress_ptr cinfo, const JHUFF_TBL* table,
|
||||
bool is_dc) {
|
||||
size_t total_symbols = 0;
|
||||
size_t total_p = 0;
|
||||
size_t max_depth = 0;
|
||||
for (size_t d = 1; d <= kJpegHuffmanMaxBitLength; ++d) {
|
||||
uint8_t count = table->bits[d];
|
||||
if (count) {
|
||||
total_symbols += count;
|
||||
total_p += (1u << (kJpegHuffmanMaxBitLength - d)) * count;
|
||||
max_depth = d;
|
||||
}
|
||||
}
|
||||
total_p += 1u << (kJpegHuffmanMaxBitLength - max_depth); // sentinel symbol
|
||||
if (total_symbols == 0) {
|
||||
JPEGLI_ERROR("Empty Huffman table");
|
||||
}
|
||||
if (total_symbols > kJpegHuffmanAlphabetSize) {
|
||||
JPEGLI_ERROR("Too many symbols in Huffman table");
|
||||
}
|
||||
if (total_p != (1u << kJpegHuffmanMaxBitLength)) {
|
||||
JPEGLI_ERROR("Invalid bit length distribution");
|
||||
}
|
||||
uint8_t symbol_seen[kJpegHuffmanAlphabetSize] = {};
|
||||
for (size_t i = 0; i < total_symbols; ++i) {
|
||||
uint8_t symbol = table->huffval[i];
|
||||
if (symbol_seen[symbol]) {
|
||||
JPEGLI_ERROR("Duplicate symbol %d in Huffman table", symbol);
|
||||
}
|
||||
symbol_seen[symbol] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void CopyHuffmanTable(j_compress_ptr cinfo, int index, bool is_dc,
|
||||
std::vector<JPEGHuffmanCode>* huffman_codes) {
|
||||
JPEGHuffmanCode* huffman_codes,
|
||||
size_t* num_huffman_codes) {
|
||||
const char* type = is_dc ? "DC" : "AC";
|
||||
if (index < 0 || index >= NUM_HUFF_TBLS) {
|
||||
JPEGLI_ERROR("Invalid %s Huffman table index %d", type, index);
|
||||
|
@ -485,10 +470,10 @@ void CopyHuffmanTable(j_compress_ptr cinfo, int index, bool is_dc,
|
|||
JHUFF_TBL* table =
|
||||
is_dc ? cinfo->dc_huff_tbl_ptrs[index] : cinfo->ac_huff_tbl_ptrs[index];
|
||||
if (table == nullptr) {
|
||||
JPEGLI_ERROR("Missing %s Huffman table %d", table, index);
|
||||
JPEGLI_ERROR("Missing %s Huffman table %d", type, index);
|
||||
}
|
||||
ValidateHuffmanTable(cinfo, table, is_dc);
|
||||
JPEGHuffmanCode huff;
|
||||
ValidateHuffmanTable(reinterpret_cast<j_common_ptr>(cinfo), table, is_dc);
|
||||
JPEGHuffmanCode huff = {};
|
||||
size_t max_depth = 0;
|
||||
for (size_t i = 1; i <= kJpegHuffmanMaxBitLength; ++i) {
|
||||
if (table->bits[i] != 0) max_depth = i;
|
||||
|
@ -501,94 +486,43 @@ void CopyHuffmanTable(j_compress_ptr cinfo, int index, bool is_dc,
|
|||
huff.slot_id = index + (is_dc ? 0 : 0x10);
|
||||
huff.sent_table = table->sent_table;
|
||||
bool have_slot = false;
|
||||
for (size_t i = 0; i < huffman_codes->size(); ++i) {
|
||||
if ((*huffman_codes)[i].slot_id == huff.slot_id) have_slot = true;
|
||||
for (size_t i = 0; i < *num_huffman_codes; ++i) {
|
||||
if (huffman_codes[i].slot_id == huff.slot_id) have_slot = true;
|
||||
}
|
||||
if (!have_slot) {
|
||||
huffman_codes->emplace_back(std::move(huff));
|
||||
memcpy(&huffman_codes[*num_huffman_codes], &huff, sizeof(huff));
|
||||
++(*num_huffman_codes);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void AddStandardHuffmanTables(j_compress_ptr cinfo, bool is_dc) {
|
||||
// Huffman tables from the JPEG standard.
|
||||
static constexpr JHUFF_TBL kStandardDCTables[2] = {
|
||||
// DC luma
|
||||
{{0, 0, 1, 5, 1, 1, 1, 1, 1, 1},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
|
||||
FALSE},
|
||||
// DC chroma
|
||||
{{0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1},
|
||||
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
|
||||
FALSE}};
|
||||
static constexpr JHUFF_TBL kStandardACTables[2] = {
|
||||
// AC luma
|
||||
{{0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125},
|
||||
{0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
|
||||
0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
|
||||
0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72,
|
||||
0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
|
||||
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45,
|
||||
0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
|
||||
0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75,
|
||||
0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
|
||||
0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3,
|
||||
0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
|
||||
0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9,
|
||||
0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
|
||||
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4,
|
||||
0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa},
|
||||
FALSE},
|
||||
// AC chroma
|
||||
{{0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119},
|
||||
{0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41,
|
||||
0x51, 0x07, 0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
|
||||
0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1,
|
||||
0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
|
||||
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44,
|
||||
0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
|
||||
0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74,
|
||||
0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
|
||||
0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a,
|
||||
0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
|
||||
0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
|
||||
0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
|
||||
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4,
|
||||
0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa},
|
||||
FALSE}};
|
||||
const JHUFF_TBL* std_tables = is_dc ? kStandardDCTables : kStandardACTables;
|
||||
JHUFF_TBL** tables =
|
||||
is_dc ? cinfo->dc_huff_tbl_ptrs : cinfo->ac_huff_tbl_ptrs;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
if (tables[i] == nullptr) {
|
||||
tables[i] =
|
||||
jpegli_alloc_huff_table(reinterpret_cast<j_common_ptr>(cinfo));
|
||||
memcpy(tables[i], &std_tables[i], sizeof(JHUFF_TBL));
|
||||
ValidateHuffmanTable(cinfo, tables[i], is_dc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CopyHuffmanCodes(j_compress_ptr cinfo,
|
||||
std::vector<JPEGHuffmanCode>* huffman_codes) {
|
||||
void CopyHuffmanCodes(j_compress_ptr cinfo, bool* is_baseline) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
m->huffman_codes =
|
||||
Allocate<JPEGHuffmanCode>(cinfo, 2 * cinfo->num_components, JPOOL_IMAGE);
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
CopyHuffmanTable(cinfo, comp->dc_tbl_no, /*is_dc=*/true, huffman_codes);
|
||||
CopyHuffmanTable(cinfo, comp->ac_tbl_no, /*is_dc=*/false, huffman_codes);
|
||||
if (comp->dc_tbl_no > 1 || comp->ac_tbl_no > 1) {
|
||||
*is_baseline = false;
|
||||
}
|
||||
CopyHuffmanTable(cinfo, comp->dc_tbl_no, /*is_dc=*/true, m->huffman_codes,
|
||||
&m->num_huffman_codes);
|
||||
CopyHuffmanTable(cinfo, comp->ac_tbl_no, /*is_dc=*/false, m->huffman_codes,
|
||||
&m->num_huffman_codes);
|
||||
}
|
||||
for (int i = 0; i < cinfo->num_scans; ++i) {
|
||||
const jpeg_scan_info* si = &cinfo->scan_info[i];
|
||||
ScanCodingInfo sci;
|
||||
ScanCodingInfo sci = {};
|
||||
for (int j = 0; j < si->comps_in_scan; ++j) {
|
||||
int ci = si->component_index[j];
|
||||
sci.dc_tbl_idx[j] = cinfo->comp_info[ci].dc_tbl_no;
|
||||
sci.ac_tbl_idx[j] = cinfo->comp_info[ci].ac_tbl_no + 4;
|
||||
}
|
||||
if (i == 0) {
|
||||
sci.num_huffman_codes = huffman_codes->size();
|
||||
sci.num_huffman_codes = m->num_huffman_codes;
|
||||
}
|
||||
cinfo->master->scan_coding_info.emplace_back(std::move(sci));
|
||||
memcpy(&m->scan_coding_info[i], &sci, sizeof(sci));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -607,10 +541,8 @@ size_t RestartIntervalForScan(j_compress_ptr cinfo, size_t scan_index) {
|
|||
}
|
||||
}
|
||||
|
||||
void OptimizeHuffmanCodes(
|
||||
j_compress_ptr cinfo,
|
||||
const std::vector<std::vector<jpegli::coeff_t> >& coeffs,
|
||||
std::vector<JPEGHuffmanCode>* huffman_codes) {
|
||||
void OptimizeHuffmanCodes(j_compress_ptr cinfo, bool* is_baseline) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
// Gather histograms.
|
||||
size_t num_histo = 0;
|
||||
for (int i = 0; i < cinfo->num_scans; ++i) {
|
||||
|
@ -618,7 +550,7 @@ void OptimizeHuffmanCodes(
|
|||
}
|
||||
std::vector<Histogram> dc_histograms(num_histo);
|
||||
std::vector<Histogram> ac_histograms(num_histo);
|
||||
ProcessJpeg(cinfo, coeffs, &dc_histograms, &ac_histograms);
|
||||
ProcessJpeg(cinfo, &dc_histograms, &ac_histograms);
|
||||
|
||||
// Cluster DC histograms.
|
||||
JpegClusteredHistograms dc_clusters;
|
||||
|
@ -633,16 +565,19 @@ void OptimizeHuffmanCodes(
|
|||
// Add the first 4 DC and AC histograms in the first DHT segment.
|
||||
std::vector<uint32_t> dc_slot_histograms;
|
||||
std::vector<uint32_t> ac_slot_histograms;
|
||||
m->huffman_codes = Allocate<JPEGHuffmanCode>(cinfo, num_histo, JPOOL_IMAGE);
|
||||
for (size_t i = 0; i < dc_clusters.histograms.size(); ++i) {
|
||||
if (i >= 4) break;
|
||||
JXL_ASSERT(dc_clusters.slot_ids[i] == i);
|
||||
AddJpegHuffmanCode(dc_clusters.histograms[i], i, huffman_codes);
|
||||
AddJpegHuffmanCode(dc_clusters.histograms[i], i, m->huffman_codes,
|
||||
&m->num_huffman_codes);
|
||||
dc_slot_histograms.push_back(i);
|
||||
}
|
||||
for (size_t i = 0; i < ac_clusters.histograms.size(); ++i) {
|
||||
if (i >= 4) break;
|
||||
JXL_ASSERT(ac_clusters.slot_ids[i] == i);
|
||||
AddJpegHuffmanCode(ac_clusters.histograms[i], 0x10 + i, huffman_codes);
|
||||
AddJpegHuffmanCode(ac_clusters.histograms[i], 0x10 + i, m->huffman_codes,
|
||||
&m->num_huffman_codes);
|
||||
ac_slot_histograms.push_back(i);
|
||||
}
|
||||
|
||||
|
@ -651,17 +586,19 @@ void OptimizeHuffmanCodes(
|
|||
size_t histogram_id = 0;
|
||||
size_t num_huffman_codes_sent = 0;
|
||||
for (int i = 0; i < cinfo->num_scans; ++i) {
|
||||
ScanCodingInfo sci;
|
||||
ScanCodingInfo sci = {};
|
||||
for (int c = 0; c < cinfo->scan_info[i].comps_in_scan; ++c) {
|
||||
SetJpegHuffmanCode(dc_clusters, histogram_id, 0, dc_slot_histograms,
|
||||
&sci.dc_tbl_idx[c], huffman_codes);
|
||||
&sci.dc_tbl_idx[c], is_baseline, m->huffman_codes,
|
||||
&m->num_huffman_codes);
|
||||
SetJpegHuffmanCode(ac_clusters, histogram_id, 0x10, ac_slot_histograms,
|
||||
&sci.ac_tbl_idx[c], huffman_codes);
|
||||
&sci.ac_tbl_idx[c], is_baseline, m->huffman_codes,
|
||||
&m->num_huffman_codes);
|
||||
++histogram_id;
|
||||
}
|
||||
sci.num_huffman_codes = huffman_codes->size() - num_huffman_codes_sent;
|
||||
num_huffman_codes_sent = huffman_codes->size();
|
||||
cinfo->master->scan_coding_info.emplace_back(std::move(sci));
|
||||
sci.num_huffman_codes = m->num_huffman_codes - num_huffman_codes_sent;
|
||||
num_huffman_codes_sent = m->num_huffman_codes;
|
||||
memcpy(&m->scan_coding_info[i], &sci, sizeof(sci));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче