mirror of
https://github.com/libjxl/libjxl.git
synced 2026-10-28 10:15:22 +08:00
Remove jpegli (#4657)
This commit is contained in:
1 parent
32dec5a5d3
commit
852c44e806
108 files changed
+70
-24711
No files matched your search
@@ -40,14 +40,11 @@ jobs:
|
||||
test_in_pr: true
|
||||
cmake_args: >-
|
||||
-DJPEGXL_TEST_TOOLS=ON
|
||||
-DJPEGLI_LIBJPEG_LIBRARY_VERSION="8.2.2"
|
||||
-DJPEGLI_LIBJPEG_LIBRARY_SOVERSION="8"
|
||||
# Track static stack size on build and check it doesn't exceed 3 kB.
|
||||
emit_stack_sizes: 1
|
||||
max_stack: 2400
|
||||
# Conformance tooling test requires numpy.
|
||||
# jpegli tests require imagemagick and libjpeg-turbo-progs
|
||||
apt_pkgs: imagemagick libjpeg-turbo-progs python3-numpy
|
||||
apt_pkgs: python3-numpy
|
||||
- name: lowprecision
|
||||
mode: release
|
||||
run_bench: true
|
||||
@@ -73,8 +70,6 @@ jobs:
|
||||
skip_install: true
|
||||
- name: coverage
|
||||
skip_install: true
|
||||
disable_tests:
|
||||
- test_jpegli_jni_wrapper # TODO: why this does not work?
|
||||
# Build with support for decoding to JPEG bytes disabled. Produces a
|
||||
# smaller build if only decoding to pixels is needed.
|
||||
- name: release-nojpeg
|
||||
|
||||
@@ -207,7 +207,7 @@ jobs:
|
||||
BUILD_DIR=build \
|
||||
${EMULATOR} ./ci.sh test \
|
||||
-I ${{ matrix.shard_number }},,${{ env.LAST_SHARD }} \
|
||||
-E '(bash_test|conformance_tooling_test|test_jxl_jni_wrapper|test_jpegli_jni_wrapper)'
|
||||
-E '(bash_test|conformance_tooling_test|test_jxl_jni_wrapper)'
|
||||
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
name: Upload test cost
|
||||
|
||||
@@ -84,7 +84,6 @@ jobs:
|
||||
-DJPEGXL_DEP_LICENSE_DIR=/usr/share/doc \
|
||||
-DJPEGXL_STATIC=ON \
|
||||
-DBUILD_TESTING=OFF \
|
||||
-DJPEGXL_ENABLE_JPEGLI_LIBJPEG=OFF \
|
||||
-DJPEGXL_ENABLE_VIEWERS=OFF \
|
||||
-DJPEGXL_ENABLE_PLUGINS=OFF \
|
||||
-DJPEGXL_ENABLE_OPENEXR=OFF \
|
||||
@@ -95,7 +94,7 @@ jobs:
|
||||
ROOT=`pwd`
|
||||
cd build
|
||||
tar -zcvf ${ROOT}/jxl-linux-x86_64-static.tar.gz \
|
||||
LICENSE* tools/{cjxl,djxl,benchmark_xl,cjpegli,djpegli,jxlinfo,butteraugli_main,ssimulacra2}
|
||||
LICENSE* tools/{cjxl,djxl,benchmark_xl,jxlinfo,butteraugli_main,ssimulacra2}
|
||||
|
||||
- name: Upload release tarball
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
@@ -318,7 +317,6 @@ jobs:
|
||||
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
|
||||
-DCMAKE_INSTALL_PREFIX=`pwd`/${PREFIX} \
|
||||
-DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake \
|
||||
-DJPEGXL_ENABLE_JPEGLI_LIBJPEG=OFF \
|
||||
-DJPEGXL_ENABLE_OPENEXR=OFF \
|
||||
-DJPEGXL_ENABLE_PLUGINS=OFF \
|
||||
-DJPEGXL_ENABLE_TCMALLOC=OFF \
|
||||
|
||||
@@ -116,13 +116,6 @@ else()
|
||||
endif()
|
||||
|
||||
|
||||
if(EXISTS "${PROJECT_SOURCE_DIR}/third_party/libjpeg-turbo/jconfig.h.in")
|
||||
set(ENABLE_JPEGLI_DEFAULT YES)
|
||||
else()
|
||||
set(ENABLE_JPEGLI_DEFAULT NO)
|
||||
message(STATUS "libjpeg-turbo submodule is absent; not enabling jpegli")
|
||||
endif()
|
||||
|
||||
include(TestBigEndian)
|
||||
test_big_endian(ARCH_IS_BIG_ENDIAN)
|
||||
if(ARCH_IS_BIG_ENDIAN)
|
||||
@@ -148,16 +141,6 @@ set(JPEGXL_ENABLE_DEVTOOLS false CACHE BOOL
|
||||
"Build JPEGXL developer tools.")
|
||||
set(JPEGXL_ENABLE_TOOLS true CACHE BOOL
|
||||
"Build JPEGXL user tools: cjxl and djxl.")
|
||||
set(JPEGXL_ENABLE_JPEGLI ${ENABLE_JPEGLI_DEFAULT} CACHE BOOL
|
||||
"Build jpegli library.")
|
||||
set(JPEGXL_ENABLE_JPEGLI_LIBJPEG true CACHE BOOL
|
||||
"Build libjpeg.so shared library based on jpegli.")
|
||||
set(JPEGXL_INSTALL_JPEGLI_LIBJPEG false CACHE BOOL
|
||||
"Install jpegli version of libjpeg.so system-wide.")
|
||||
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
|
||||
|
||||
Vendored
-4
@@ -8,14 +8,10 @@ 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
|
||||
@@ -14,7 +14,6 @@ load(
|
||||
"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",
|
||||
@@ -27,11 +26,6 @@ load(
|
||||
"libjxl_extras_for_tools_sources",
|
||||
"libjxl_extras_sources",
|
||||
# "libjxl_gbench_sources",
|
||||
# "libjxl_jpegli_lib_version",
|
||||
"libjxl_jpegli_libjpeg_helper_files",
|
||||
"libjxl_jpegli_sources",
|
||||
"libjxl_jpegli_testlib_files",
|
||||
"libjxl_jpegli_tests",
|
||||
"libjxl_major_version",
|
||||
"libjxl_minor_version",
|
||||
"libjxl_patch_version",
|
||||
@@ -137,33 +131,6 @@ cc_library(
|
||||
strip_include_prefix = INCLUDES_DIR,
|
||||
)
|
||||
|
||||
JPEGLI_JCONFIG_H = INCLUDES_DIR + "/jpegli/jconfig.h"
|
||||
|
||||
JPEGLI_JMORECFG_H = INCLUDES_DIR + "/jpegli/jmorecfg.h"
|
||||
|
||||
JPEGLI_JPEGLIB_H = INCLUDES_DIR + "/jpegli/jpeglib.h"
|
||||
|
||||
copy_file(
|
||||
name = "expand_jconfig",
|
||||
src = "@libjpeg_turbo//:jconfig.h",
|
||||
out = JPEGLI_JCONFIG_H,
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
)
|
||||
|
||||
copy_file(
|
||||
name = "copy_jmorecfg",
|
||||
src = "@libjpeg_turbo//:jmorecfg.h",
|
||||
out = JPEGLI_JMORECFG_H,
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
)
|
||||
|
||||
copy_file(
|
||||
name = "copy_jpeglib",
|
||||
src = "@libjpeg_turbo//:jpeglib.h",
|
||||
out = JPEGLI_JPEGLIB_H,
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "includes",
|
||||
hdrs = libjxl_public_headers + [
|
||||
@@ -175,17 +142,6 @@ cc_library(
|
||||
deps = [":jxl_version"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "libjpeg_includes",
|
||||
hdrs = [
|
||||
JPEGLI_JCONFIG_H,
|
||||
JPEGLI_JMORECFG_H,
|
||||
JPEGLI_JPEGLIB_H,
|
||||
],
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
strip_include_prefix = INCLUDES_DIR + "/jpegli",
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "base",
|
||||
srcs = [path for path in libjxl_base_sources if path.endswith(".cc")],
|
||||
@@ -232,25 +188,12 @@ cc_library(
|
||||
],
|
||||
)
|
||||
|
||||
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_FILES = libjxl_codec_apng_sources + libjxl_codec_exr_sources + libjxl_codec_gif_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",
|
||||
srcs = libjxl_jpegli_sources,
|
||||
hdrs = [
|
||||
"jpegli/common_internal.h", # TODO(eustas): should not be here
|
||||
],
|
||||
compatible_with = DEFAULT_COMPATIBILITY,
|
||||
deps = [
|
||||
":jpegxl_private",
|
||||
":libjpeg_includes",
|
||||
] + libjxl_deps_hwy,
|
||||
)
|
||||
|
||||
# TODO(eustas): build codecs separately?
|
||||
cc_library(
|
||||
name = "jpegxl_extras",
|
||||
@@ -262,17 +205,15 @@ cc_library(
|
||||
"JPEGXL_ENABLE_EXR=1",
|
||||
"JPEGXL_ENABLE_GIF=1",
|
||||
"JPEGXL_ENABLE_JPEG=1",
|
||||
"JPEGXL_ENABLE_JPEGLI=1",
|
||||
],
|
||||
deps = [
|
||||
":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 + libjxl_jpegli_libjpeg_helper_files
|
||||
TESTLIB_FILES = libjxl_testlib_files
|
||||
|
||||
cc_library(
|
||||
name = "test_utils",
|
||||
@@ -284,13 +225,12 @@ cc_library(
|
||||
'JPEGXL_ROOT_PACKAGE=\'"' + libjxl_root_package + '"\'',
|
||||
],
|
||||
deps = [
|
||||
":jpegli",
|
||||
":jpegxl_extras",
|
||||
":jpegxl_private",
|
||||
] + libjxl_deps_runfiles,
|
||||
)
|
||||
|
||||
TESTS = [path.partition(".")[0] for path in libjxl_tests + libjxl_jpegli_tests]
|
||||
TESTS = [path.partition(".")[0] for path in libjxl_tests]
|
||||
|
||||
[
|
||||
cc_test(
|
||||
@@ -298,7 +238,6 @@ TESTS = [path.partition(".")[0] for path in libjxl_tests + libjxl_jpegli_tests]
|
||||
timeout = libjxl_test_timeouts.get(test, "moderate"),
|
||||
srcs = [
|
||||
test + ".cc",
|
||||
"jpegli/testing.h",
|
||||
"jxl/testing.h",
|
||||
],
|
||||
data = ["//:testdata"],
|
||||
|
||||
@@ -188,9 +188,6 @@ if(JPEGXL_ENABLE_TOOLS OR BUILD_TESTING)
|
||||
include(jxl_extras.cmake)
|
||||
endif()
|
||||
include(jxl_threads.cmake)
|
||||
if (JPEGXL_ENABLE_JPEGLI)
|
||||
include(jpegli.cmake)
|
||||
endif()
|
||||
|
||||
# For simplicity all the library headers, both source and generated ones, are
|
||||
# gathered in the binary folder. There is no distinction on which libraries use
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/dec/jpegli.h"
|
||||
|
||||
#include <jxl/codestream_header.h>
|
||||
#include <jxl/color_encoding.h>
|
||||
#include <jxl/types.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/decode.h"
|
||||
#include "lib/jpegli/types.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/sanitizers.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
|
||||
0x66, 0x00, 0x00};
|
||||
constexpr int kExifMarker = JPEG_APP0 + 1;
|
||||
constexpr int kICCMarker = JPEG_APP0 + 2;
|
||||
|
||||
inline bool IsJPG(const std::vector<uint8_t>& bytes) {
|
||||
if (bytes.size() < 2) return false;
|
||||
if (bytes[0] != 0xFF || bytes[1] != 0xD8) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MarkerIsExif(const jpeg_saved_marker_ptr marker) {
|
||||
return marker->marker == kExifMarker &&
|
||||
marker->data_length >= sizeof kExifSignature + 2 &&
|
||||
std::equal(std::begin(kExifSignature), std::end(kExifSignature),
|
||||
marker->data);
|
||||
}
|
||||
|
||||
Status ReadICCProfile(jpeg_decompress_struct* const cinfo,
|
||||
std::vector<uint8_t>* const icc) {
|
||||
uint8_t* icc_data_ptr;
|
||||
unsigned int icc_data_len;
|
||||
if (jpegli_read_icc_profile(cinfo, &icc_data_ptr, &icc_data_len)) {
|
||||
icc->assign(icc_data_ptr, icc_data_ptr + icc_data_len);
|
||||
free(icc_data_ptr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ReadExif(jpeg_decompress_struct* const cinfo,
|
||||
std::vector<uint8_t>* const exif) {
|
||||
constexpr size_t kExifSignatureSize = sizeof kExifSignature;
|
||||
for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr;
|
||||
marker = marker->next) {
|
||||
// marker is initialized by libjpeg, which we are not instrumenting with
|
||||
// msan.
|
||||
msan::UnpoisonMemory(marker, sizeof(*marker));
|
||||
msan::UnpoisonMemory(marker->data, marker->data_length);
|
||||
if (!MarkerIsExif(marker)) continue;
|
||||
size_t marker_length = marker->data_length - kExifSignatureSize;
|
||||
exif->resize(marker_length);
|
||||
std::copy_n(marker->data + kExifSignatureSize, marker_length, exif->data());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
JpegliDataType ConvertDataType(JxlDataType type) {
|
||||
switch (type) {
|
||||
case JXL_TYPE_UINT8:
|
||||
return JPEGLI_TYPE_UINT8;
|
||||
case JXL_TYPE_UINT16:
|
||||
return JPEGLI_TYPE_UINT16;
|
||||
case JXL_TYPE_FLOAT:
|
||||
return JPEGLI_TYPE_FLOAT;
|
||||
default:
|
||||
return JPEGLI_TYPE_UINT8;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
jpegli_destroy_decompress(reinterpret_cast<j_decompress_ptr>(cinfo));
|
||||
longjmp(*env, 1);
|
||||
}
|
||||
|
||||
void MyOutputMessage(j_common_ptr cinfo) {
|
||||
if (JXL_IS_DEBUG_BUILD) {
|
||||
char buf[JMSG_LENGTH_MAX + 1];
|
||||
(*cinfo->err->format_message)(cinfo, buf);
|
||||
buf[JMSG_LENGTH_MAX] = 0;
|
||||
JXL_WARNING("%s", buf);
|
||||
}
|
||||
}
|
||||
|
||||
Status UnmapColors(uint8_t* row, size_t xsize, int components,
|
||||
JSAMPARRAY colormap, size_t num_colors) {
|
||||
JXL_ENSURE(colormap != nullptr);
|
||||
std::vector<uint8_t> tmp(xsize * components);
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
JXL_ENSURE(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());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeJpeg(const std::vector<uint8_t>& compressed,
|
||||
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;
|
||||
|
||||
// TODO(veluca): use JPEGData also for pixels?
|
||||
|
||||
// We need to declare all the non-trivial destructor local variables before
|
||||
// the call to setjmp().
|
||||
std::unique_ptr<JSAMPLE[]> row;
|
||||
|
||||
jpeg_decompress_struct cinfo;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
// Setup error handling in jpeg library so we can deal with broken jpegs in
|
||||
// the fuzzer.
|
||||
jpeg_error_mgr jerr;
|
||||
jmp_buf env;
|
||||
cinfo.err = jpegli_std_error(&jerr);
|
||||
jerr.error_exit = &MyErrorExit;
|
||||
jerr.output_message = &MyOutputMessage;
|
||||
if (setjmp(env)) {
|
||||
return false;
|
||||
}
|
||||
cinfo.client_data = static_cast<void*>(&env);
|
||||
|
||||
jpegli_create_decompress(&cinfo);
|
||||
jpegli_mem_src(&cinfo,
|
||||
reinterpret_cast<const unsigned char*>(compressed.data()),
|
||||
compressed.size());
|
||||
jpegli_save_markers(&cinfo, kICCMarker, 0xFFFF);
|
||||
jpegli_save_markers(&cinfo, kExifMarker, 0xFFFF);
|
||||
const auto failure = [&cinfo](const char* str) -> Status {
|
||||
jpegli_abort_decompress(&cinfo);
|
||||
jpegli_destroy_decompress(&cinfo);
|
||||
return JXL_FAILURE("%s", str);
|
||||
};
|
||||
jpegli_read_header(&cinfo, TRUE);
|
||||
// Might cause CPU-zip bomb.
|
||||
if (cinfo.arith_code) {
|
||||
return failure("arithmetic code JPEGs are not supported");
|
||||
}
|
||||
int nbcomp = cinfo.num_components;
|
||||
if (nbcomp != 1 && nbcomp != 3) {
|
||||
std::string msg =
|
||||
"unsupported number of components in JPEG: " + std::to_string(nbcomp);
|
||||
return failure(msg.c_str());
|
||||
}
|
||||
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->primary_color_representation = PackedPixelFile::kIccIsPrimary;
|
||||
} else {
|
||||
ppf->primary_color_representation =
|
||||
PackedPixelFile::kColorEncodingIsPrimary;
|
||||
ppf->icc.clear();
|
||||
// Default to SRGB
|
||||
ppf->color_encoding.color_space =
|
||||
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;
|
||||
ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL;
|
||||
}
|
||||
ReadExif(&cinfo, &ppf->metadata.exif);
|
||||
|
||||
ppf->info.xsize = cinfo.image_width;
|
||||
ppf->info.ysize = cinfo.image_height;
|
||||
if (dparams.output_data_type == JXL_TYPE_UINT8) {
|
||||
ppf->info.bits_per_sample = 8;
|
||||
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.uses_original_profile = JXL_TRUE;
|
||||
|
||||
// No alpha in JPG
|
||||
ppf->info.alpha_bits = 0;
|
||||
ppf->info.alpha_exponent_bits = 0;
|
||||
ppf->info.orientation = JXL_ORIENT_IDENTITY;
|
||||
|
||||
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 = static_cast<boolean>(dparams.two_pass_quant);
|
||||
cinfo.dither_mode = static_cast<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>(cinfo.out_color_components),
|
||||
dparams.output_data_type,
|
||||
dparams.output_endianness,
|
||||
/*align=*/0,
|
||||
};
|
||||
ppf->frames.clear();
|
||||
// Allocates the frame buffer.
|
||||
{
|
||||
JXL_ASSIGN_OR_RETURN(
|
||||
PackedFrame frame,
|
||||
PackedFrame::Create(cinfo.image_width, cinfo.image_height, format));
|
||||
ppf->frames.emplace_back(std::move(frame));
|
||||
}
|
||||
const auto& frame = ppf->frames.back();
|
||||
JXL_ENSURE(sizeof(JSAMPLE) * cinfo.out_color_components *
|
||||
cinfo.image_width <=
|
||||
frame.color.stride);
|
||||
if (dparams.num_colors > 0) JXL_ENSURE(cinfo.colormap != nullptr);
|
||||
|
||||
for (size_t y = 0; y < cinfo.image_height; ++y) {
|
||||
JSAMPROW rows[] = {reinterpret_cast<JSAMPLE*>(
|
||||
static_cast<uint8_t*>(frame.color.pixels()) +
|
||||
frame.color.stride * y)};
|
||||
jpegli_read_scanlines(&cinfo, rows, 1);
|
||||
if (dparams.num_colors > 0) {
|
||||
JXL_RETURN_IF_ERROR(
|
||||
UnmapColors(rows[0], cinfo.output_width, cinfo.out_color_components,
|
||||
cinfo.colormap, cinfo.actual_number_of_colors));
|
||||
}
|
||||
}
|
||||
|
||||
jpegli_finish_decompress(&cinfo);
|
||||
return true;
|
||||
};
|
||||
bool success = try_catch_block();
|
||||
jpegli_destroy_decompress(&cinfo);
|
||||
return success;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_DEC_JPEGLI_H_
|
||||
#define LIB_EXTRAS_DEC_JPEGLI_H_
|
||||
|
||||
// Decodes JPG pixels and metadata in memory using the libjpegli library.
|
||||
|
||||
#include <jxl/types.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
class PackedPixelFile;
|
||||
|
||||
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,
|
||||
const JpegDecompressParams& dparams, ThreadPool* pool,
|
||||
PackedPixelFile* ppf);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_DEC_JPEGLI_H_
|
||||
@@ -1,569 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/enc/jpegli.h"
|
||||
|
||||
#include <jxl/cms.h>
|
||||
#include <jxl/codestream_header.h>
|
||||
#include <jxl/types.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <hwy/aligned_allocator.h>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/extras/enc/encode.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/encode.h"
|
||||
#include "lib/jpegli/types.h"
|
||||
#include "lib/jxl/base/byte_order.h"
|
||||
#include "lib/jxl/base/common.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/enc_xyb.h"
|
||||
#include "lib/jxl/simd_util.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
namespace {
|
||||
|
||||
void MyErrorExit(j_common_ptr cinfo) {
|
||||
jmp_buf* env = static_cast<jmp_buf*>(cinfo->client_data);
|
||||
(*cinfo->err->output_message)(cinfo);
|
||||
jpegli_destroy_compress(reinterpret_cast<j_compress_ptr>(cinfo));
|
||||
longjmp(*env, 1);
|
||||
}
|
||||
|
||||
Status VerifyInput(const PackedPixelFile& ppf) {
|
||||
const JxlBasicInfo& info = ppf.info;
|
||||
JXL_RETURN_IF_ERROR(Encoder::VerifyBasicInfo(info));
|
||||
if (ppf.frames.size() != 1) {
|
||||
return JXL_FAILURE("JPEG input must have exactly one frame.");
|
||||
}
|
||||
if (info.num_color_channels != 1 && info.num_color_channels != 3) {
|
||||
return JXL_FAILURE("Invalid number of color channels %d",
|
||||
info.num_color_channels);
|
||||
}
|
||||
const PackedImage& image = ppf.frames[0].color;
|
||||
JXL_RETURN_IF_ERROR(Encoder::VerifyImageSize(image, info));
|
||||
if (image.format.data_type == JXL_TYPE_FLOAT16) {
|
||||
return JXL_FAILURE("FLOAT16 input is not supported.");
|
||||
}
|
||||
JXL_RETURN_IF_ERROR(Encoder::VerifyBitDepth(image.format.data_type,
|
||||
info.bits_per_sample,
|
||||
info.exponent_bits_per_sample));
|
||||
if ((image.format.data_type == JXL_TYPE_UINT8 && info.bits_per_sample != 8) ||
|
||||
(image.format.data_type == JXL_TYPE_UINT16 &&
|
||||
info.bits_per_sample != 16)) {
|
||||
return JXL_FAILURE("Only full bit depth unsigned types are supported.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Status GetColorEncoding(const PackedPixelFile& ppf,
|
||||
ColorEncoding* color_encoding) {
|
||||
if (ppf.primary_color_representation == PackedPixelFile::kIccIsPrimary) {
|
||||
IccBytes icc = ppf.icc;
|
||||
JXL_RETURN_IF_ERROR(
|
||||
color_encoding->SetICC(std::move(icc), JxlGetDefaultCms()));
|
||||
} else {
|
||||
JXL_RETURN_IF_ERROR(color_encoding->FromExternal(ppf.color_encoding));
|
||||
}
|
||||
if (color_encoding->ICC().empty()) {
|
||||
return JXL_FAILURE("Invalid color encoding.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasICCProfile(const std::vector<uint8_t>& app_data) {
|
||||
size_t pos = 0;
|
||||
while (pos < app_data.size()) {
|
||||
if (pos + 16 > app_data.size()) return false;
|
||||
uint8_t marker = app_data[pos + 1];
|
||||
size_t marker_len = (app_data[pos + 2] << 8) + app_data[pos + 3] + 2;
|
||||
if (marker == 0xe2 && memcmp(&app_data[pos + 4], "ICC_PROFILE", 12) == 0) {
|
||||
return true;
|
||||
}
|
||||
pos += marker_len;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Status WriteAppData(j_compress_ptr cinfo,
|
||||
const std::vector<uint8_t>& app_data) {
|
||||
size_t pos = 0;
|
||||
while (pos < app_data.size()) {
|
||||
if (pos + 4 > app_data.size()) {
|
||||
return JXL_FAILURE("Incomplete APP header.");
|
||||
}
|
||||
uint8_t marker = app_data[pos + 1];
|
||||
size_t marker_len = (app_data[pos + 2] << 8) + app_data[pos + 3] + 2;
|
||||
if (app_data[pos] != 0xff || marker < 0xe0 || marker > 0xef) {
|
||||
return JXL_FAILURE("Invalid APP marker %02x %02x", app_data[pos], marker);
|
||||
}
|
||||
if (marker_len <= 4) {
|
||||
return JXL_FAILURE("Invalid APP marker length.");
|
||||
}
|
||||
if (pos + marker_len > app_data.size()) {
|
||||
return JXL_FAILURE("Incomplete APP data");
|
||||
}
|
||||
jpegli_write_marker(cinfo, marker, &app_data[pos + 4], marker_len - 4);
|
||||
pos += marker_len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr int kICCMarker = 0xe2;
|
||||
constexpr unsigned char kICCSignature[12] = {
|
||||
0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00};
|
||||
constexpr uint8_t kUnknownTf = 2;
|
||||
constexpr unsigned char kCICPTagSignature[4] = {0x63, 0x69, 0x63, 0x70};
|
||||
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:
|
||||
return JPEGLI_TYPE_UINT8;
|
||||
case JXL_TYPE_UINT16:
|
||||
return JPEGLI_TYPE_UINT16;
|
||||
case JXL_TYPE_FLOAT:
|
||||
return JPEGLI_TYPE_FLOAT;
|
||||
default:
|
||||
return JPEGLI_TYPE_UINT8;
|
||||
}
|
||||
}
|
||||
|
||||
JpegliEndianness ConvertEndianness(JxlEndianness endianness) {
|
||||
switch (endianness) {
|
||||
case JXL_NATIVE_ENDIAN:
|
||||
return JPEGLI_NATIVE_ENDIAN;
|
||||
case JXL_LITTLE_ENDIAN:
|
||||
return JPEGLI_LITTLE_ENDIAN;
|
||||
case JXL_BIG_ENDIAN:
|
||||
return JPEGLI_BIG_ENDIAN;
|
||||
default:
|
||||
return JPEGLI_NATIVE_ENDIAN;
|
||||
}
|
||||
}
|
||||
|
||||
void ToFloatRow(const uint8_t* row_in, JxlPixelFormat format, size_t xsize,
|
||||
size_t c_out, float* row_out) {
|
||||
bool is_little_endian =
|
||||
(format.endianness == JXL_LITTLE_ENDIAN ||
|
||||
(format.endianness == JXL_NATIVE_ENDIAN && IsLittleEndian()));
|
||||
static constexpr double kMul8 = 1.0 / 255.0;
|
||||
static constexpr double kMul16 = 1.0 / 65535.0;
|
||||
const size_t c_in = format.num_channels;
|
||||
if (format.data_type == JXL_TYPE_UINT8) {
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
for (size_t c = 0; c < c_out; ++c) {
|
||||
const size_t ix = c_in * x + c;
|
||||
row_out[c_out * x + c] = row_in[ix] * kMul8;
|
||||
}
|
||||
}
|
||||
} else if (format.data_type == JXL_TYPE_UINT16 && is_little_endian) {
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
for (size_t c = 0; c < c_out; ++c) {
|
||||
const size_t ix = c_in * x + c;
|
||||
row_out[c_out * x + c] = LoadLE16(&row_in[2 * ix]) * kMul16;
|
||||
}
|
||||
}
|
||||
} else if (format.data_type == JXL_TYPE_UINT16 && !is_little_endian) {
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
for (size_t c = 0; c < c_out; ++c) {
|
||||
const size_t ix = c_in * x + c;
|
||||
row_out[c_out * x + c] = LoadBE16(&row_in[2 * ix]) * kMul16;
|
||||
}
|
||||
}
|
||||
} else if (format.data_type == JXL_TYPE_FLOAT && is_little_endian) {
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
for (size_t c = 0; c < c_out; ++c) {
|
||||
const size_t ix = c_in * x + c;
|
||||
row_out[c_out * x + c] = LoadLEFloat(&row_in[4 * ix]);
|
||||
}
|
||||
}
|
||||
} else if (format.data_type == JXL_TYPE_FLOAT && !is_little_endian) {
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
for (size_t c = 0; c < c_out; ++c) {
|
||||
const size_t ix = c_in * x + c;
|
||||
row_out[c_out * x + c] = LoadBEFloat(&row_in[4 * ix]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status EncodeJpegToTargetSize(const PackedPixelFile& ppf,
|
||||
const JpegSettings& jpeg_settings,
|
||||
size_t target_size, ThreadPool* pool,
|
||||
std::vector<uint8_t>* output) {
|
||||
output->clear();
|
||||
size_t best_error = std::numeric_limits<size_t>::max();
|
||||
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();
|
||||
// prefer being under the target size to being over it
|
||||
size_t error = size < target_size
|
||||
? target_size - size
|
||||
: static_cast<size_t>(1.2f * (size - target_size));
|
||||
if (error < best_error) {
|
||||
best_error = error;
|
||||
std::swap(*output, compressed);
|
||||
}
|
||||
float rel_error = size * 1.0f / target_size;
|
||||
if (std::abs(rel_error - 1.0f) < 0.002f) {
|
||||
break;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings,
|
||||
ThreadPool* pool, std::vector<uint8_t>* compressed) {
|
||||
if (jpeg_settings.libjpeg_quality > 0) {
|
||||
auto encoder = Encoder::FromExtension(".jpg");
|
||||
encoder->SetOption("q", std::to_string(jpeg_settings.libjpeg_quality));
|
||||
if (!jpeg_settings.libjpeg_chroma_subsampling.empty()) {
|
||||
encoder->SetOption("chroma_subsampling",
|
||||
jpeg_settings.libjpeg_chroma_subsampling);
|
||||
}
|
||||
EncodedImage encoded;
|
||||
JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded, pool));
|
||||
size_t target_size = encoded.bitstreams[0].size();
|
||||
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;
|
||||
JXL_RETURN_IF_ERROR(GetColorEncoding(ppf, &color_encoding));
|
||||
|
||||
ColorSpaceTransform c_transform(*JxlGetDefaultCms());
|
||||
ColorEncoding xyb_encoding;
|
||||
if (jpeg_settings.xyb) {
|
||||
if (HasICCProfile(jpeg_settings.app_data)) {
|
||||
return JXL_FAILURE("APP data ICC profile is not supported in XYB mode.");
|
||||
}
|
||||
const ColorEncoding& c_desired = ColorEncoding::LinearSRGB(false);
|
||||
JXL_RETURN_IF_ERROR(
|
||||
c_transform.Init(color_encoding, c_desired, 255.0f, ppf.info.xsize, 1));
|
||||
xyb_encoding.SetColorSpace(jxl::ColorSpace::kXYB);
|
||||
xyb_encoding.SetRenderingIntent(jxl::RenderingIntent::kPerceptual);
|
||||
JXL_RETURN_IF_ERROR(xyb_encoding.CreateICC());
|
||||
}
|
||||
const ColorEncoding& output_encoding =
|
||||
jpeg_settings.xyb ? xyb_encoding : color_encoding;
|
||||
|
||||
// We need to declare all the non-trivial destructor local variables
|
||||
// before the call to setjmp().
|
||||
unsigned char* output_buffer = nullptr;
|
||||
unsigned long output_size = 0; // NOLINT
|
||||
std::vector<uint8_t> row_bytes;
|
||||
const size_t max_vector_size = MaxVectorSize();
|
||||
size_t rowlen = RoundUpTo(ppf.info.xsize, max_vector_size);
|
||||
hwy::AlignedFreeUniquePtr<float[]> xyb_tmp =
|
||||
hwy::AllocateAligned<float>(6 * rowlen);
|
||||
hwy::AlignedFreeUniquePtr<float[]> premul_absorb =
|
||||
hwy::AllocateAligned<float>(max_vector_size * 12);
|
||||
ComputePremulAbsorb(255.0f, premul_absorb.get());
|
||||
|
||||
jpeg_compress_struct cinfo;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
jpeg_error_mgr jerr;
|
||||
jmp_buf env;
|
||||
cinfo.err = jpegli_std_error(&jerr);
|
||||
jerr.error_exit = &MyErrorExit;
|
||||
if (setjmp(env)) {
|
||||
return false;
|
||||
}
|
||||
cinfo.client_data = static_cast<void*>(&env);
|
||||
jpegli_create_compress(&cinfo);
|
||||
jpegli_mem_dest(&cinfo, &output_buffer, &output_size);
|
||||
const JxlBasicInfo& info = ppf.info;
|
||||
cinfo.image_width = info.xsize;
|
||||
cinfo.image_height = info.ysize;
|
||||
cinfo.input_components = info.num_color_channels;
|
||||
cinfo.in_color_space =
|
||||
cinfo.input_components == 1 ? JCS_GRAYSCALE : JCS_RGB;
|
||||
if (jpeg_settings.xyb) {
|
||||
jpegli_set_xyb_mode(&cinfo);
|
||||
cinfo.input_components = 3;
|
||||
cinfo.in_color_space = JCS_RGB;
|
||||
} 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") {
|
||||
cinfo.comp_info[0].h_samp_factor = 1;
|
||||
cinfo.comp_info[0].v_samp_factor = 1;
|
||||
} else if (jpeg_settings.chroma_subsampling == "440") {
|
||||
cinfo.comp_info[0].h_samp_factor = 1;
|
||||
cinfo.comp_info[0].v_samp_factor = 2;
|
||||
} else if (jpeg_settings.chroma_subsampling == "422") {
|
||||
cinfo.comp_info[0].h_samp_factor = 2;
|
||||
cinfo.comp_info[0].v_samp_factor = 1;
|
||||
} else if (jpeg_settings.chroma_subsampling == "420") {
|
||||
cinfo.comp_info[0].h_samp_factor = 2;
|
||||
cinfo.comp_info[0].v_samp_factor = 2;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
for (int i = 1; i < cinfo.num_components; ++i) {
|
||||
cinfo.comp_info[i].h_samp_factor = 1;
|
||||
cinfo.comp_info[i].v_samp_factor = 1;
|
||||
}
|
||||
} else if (!jpeg_settings.xyb) {
|
||||
// Default is no chroma subsampling.
|
||||
cinfo.comp_info[0].h_samp_factor = 1;
|
||||
cinfo.comp_info[0].v_samp_factor = 1;
|
||||
}
|
||||
jpegli_enable_adaptive_quantization(
|
||||
&cinfo, TO_JXL_BOOL(jpeg_settings.use_adaptive_quantization));
|
||||
if (jpeg_settings.psnr_target > 0.0) {
|
||||
jpegli_set_psnr(&cinfo, jpeg_settings.psnr_target,
|
||||
jpeg_settings.search_tolerance,
|
||||
jpeg_settings.min_distance, jpeg_settings.max_distance);
|
||||
} else if (jpeg_settings.quality > 0.0) {
|
||||
float distance = jpegli_quality_to_distance(jpeg_settings.quality);
|
||||
jpegli_set_distance(&cinfo, distance, TRUE);
|
||||
} else {
|
||||
jpegli_set_distance(&cinfo, jpeg_settings.distance, TRUE);
|
||||
}
|
||||
jpegli_set_progressive_level(&cinfo, jpeg_settings.progressive_level);
|
||||
cinfo.optimize_coding = TO_JXL_BOOL(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 = JXL_FALSE;
|
||||
cinfo.write_Adobe_marker = JXL_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));
|
||||
}
|
||||
if ((jpeg_settings.app_data.empty() && !output_encoding.IsSRGB()) ||
|
||||
jpeg_settings.xyb) {
|
||||
jpegli_write_icc_profile(&cinfo, output_encoding.ICC().data(),
|
||||
output_encoding.ICC().size());
|
||||
}
|
||||
const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels());
|
||||
if (jpeg_settings.xyb) {
|
||||
float* src_buf = c_transform.BufSrc(0);
|
||||
float* dst_buf = c_transform.BufDst(0);
|
||||
for (size_t y = 0; y < image.ysize; ++y) {
|
||||
// convert to float
|
||||
ToFloatRow(&pixels[y * image.stride], image.format, image.xsize,
|
||||
info.num_color_channels, src_buf);
|
||||
// convert to linear srgb
|
||||
if (!c_transform.Run(0, src_buf, dst_buf, image.xsize)) {
|
||||
return false;
|
||||
}
|
||||
// deinterleave channels
|
||||
float* row0 = &xyb_tmp[0];
|
||||
float* row1 = &xyb_tmp[rowlen];
|
||||
float* row2 = &xyb_tmp[2 * rowlen];
|
||||
for (size_t x = 0; x < image.xsize; ++x) {
|
||||
row0[x] = dst_buf[3 * x + 0];
|
||||
row1[x] = dst_buf[3 * x + 1];
|
||||
row2[x] = dst_buf[3 * x + 2];
|
||||
}
|
||||
// convert to xyb
|
||||
LinearRGBRowToXYB(row0, row1, row2, premul_absorb.get(), image.xsize);
|
||||
// scale xyb
|
||||
ScaleXYBRow(row0, row1, row2, image.xsize);
|
||||
// interleave channels
|
||||
float* row_out = &xyb_tmp[3 * rowlen];
|
||||
for (size_t x = 0; x < image.xsize; ++x) {
|
||||
row_out[3 * x + 0] = row0[x];
|
||||
row_out[3 * x + 1] = row1[x];
|
||||
row_out[3 * x + 2] = row2[x];
|
||||
}
|
||||
// feed to jpegli as native endian floats
|
||||
JSAMPROW row[] = {reinterpret_cast<uint8_t*>(row_out)};
|
||||
jpegli_write_scanlines(&cinfo, row, 1);
|
||||
}
|
||||
} else {
|
||||
row_bytes.resize(image.stride);
|
||||
if (cinfo.num_components == static_cast<int>(image.format.num_channels)) {
|
||||
for (size_t y = 0; y < info.ysize; ++y) {
|
||||
memcpy(row_bytes.data(), pixels + y * image.stride, image.stride);
|
||||
JSAMPROW row[] = {row_bytes.data()};
|
||||
jpegli_write_scanlines(&cinfo, row, 1);
|
||||
}
|
||||
} else {
|
||||
for (size_t y = 0; y < info.ysize; ++y) {
|
||||
JXL_RETURN_IF_ERROR(
|
||||
PackedImage::ValidateDataType(image.format.data_type));
|
||||
int bytes_per_channel =
|
||||
PackedImage::BitsPerChannel(image.format.data_type) / 8;
|
||||
int bytes_per_pixel = cinfo.num_components * bytes_per_channel;
|
||||
for (size_t x = 0; x < info.xsize; ++x) {
|
||||
memcpy(&row_bytes[x * bytes_per_pixel],
|
||||
&pixels[y * image.stride + x * image.pixel_stride()],
|
||||
bytes_per_pixel);
|
||||
}
|
||||
JSAMPROW row[] = {row_bytes.data()};
|
||||
jpegli_write_scanlines(&cinfo, row, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
jpegli_finish_compress(&cinfo);
|
||||
compressed->resize(output_size);
|
||||
std::copy_n(output_buffer, output_size, compressed->data());
|
||||
return true;
|
||||
};
|
||||
bool success = try_catch_block();
|
||||
jpegli_destroy_compress(&cinfo);
|
||||
if (output_buffer) free(output_buffer);
|
||||
return success;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_ENC_JPEGLI_H_
|
||||
#define LIB_EXTRAS_ENC_JPEGLI_H_
|
||||
|
||||
// Encodes JPG pixels and metadata in memory using the libjpegli library.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
class PackedPixelFile;
|
||||
|
||||
struct JpegSettings {
|
||||
bool xyb = false;
|
||||
size_t target_size = 0;
|
||||
float quality = 0.0f;
|
||||
float distance = 1.f;
|
||||
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;
|
||||
// Parameters for selecting distance based on PSNR target.
|
||||
float psnr_target = 0.0f;
|
||||
float search_tolerance = 0.01;
|
||||
float min_distance = 0.1f;
|
||||
float max_distance = 25.0f;
|
||||
// If not empty, must contain concatenated APP marker segments. In this case,
|
||||
// these and only these APP marker segments will be written to the JPEG
|
||||
// output. In xyb mode app_data must not contain an ICC profile, in this
|
||||
// case an additional APP2 ICC profile for the XYB colorspace will be emitted.
|
||||
std::vector<uint8_t> app_data;
|
||||
};
|
||||
|
||||
Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings,
|
||||
ThreadPool* pool, std::vector<uint8_t>* compressed);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_ENC_JPEGLI_H_
|
||||
@@ -1,422 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#if JPEGXL_ENABLE_JPEGLI
|
||||
|
||||
#include "lib/extras/dec/jpegli.h"
|
||||
|
||||
#include <jxl/color_encoding.h>
|
||||
#include <jxl/types.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/extras/dec/decode.h"
|
||||
#include "lib/extras/dec/jpg.h"
|
||||
#include "lib/extras/enc/encode.h"
|
||||
#include "lib/extras/enc/jpegli.h"
|
||||
#include "lib/extras/enc/jpg.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/test_image.h"
|
||||
#include "lib/jxl/test_utils.h"
|
||||
#include "lib/jxl/testing.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
namespace {
|
||||
|
||||
using ::jxl::test::Butteraugli3Norm;
|
||||
using ::jxl::test::ButteraugliDistance;
|
||||
using ::jxl::test::TestImage;
|
||||
|
||||
Status ReadTestImage(const std::string& pathname, PackedPixelFile* ppf) {
|
||||
const std::vector<uint8_t> encoded = jxl::test::ReadTestData(pathname);
|
||||
ColorHints color_hints;
|
||||
if (pathname.find(".ppm") != std::string::npos) {
|
||||
color_hints.Add("color_space", "RGB_D65_SRG_Rel_SRG");
|
||||
} else if (pathname.find(".pgm") != std::string::npos) {
|
||||
color_hints.Add("color_space", "Gra_D65_Rel_SRG");
|
||||
}
|
||||
return DecodeBytes(Bytes(encoded), color_hints, ppf);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GetAppData(const std::vector<uint8_t>& compressed) {
|
||||
std::vector<uint8_t> result;
|
||||
size_t pos = 2; // After SOI
|
||||
while (pos + 4 < compressed.size()) {
|
||||
if (compressed[pos] != 0xff || compressed[pos + 1] < 0xe0 ||
|
||||
compressed[pos + 1] > 0xf0) {
|
||||
break;
|
||||
}
|
||||
size_t len = (compressed[pos + 2] << 8) + compressed[pos + 3] + 2;
|
||||
if (pos + len > compressed.size()) {
|
||||
break;
|
||||
}
|
||||
result.insert(result.end(), &compressed[pos], &compressed[pos] + len);
|
||||
pos += len;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Status DecodeWithLibjpeg(const std::vector<uint8_t>& compressed,
|
||||
PackedPixelFile* ppf,
|
||||
const JPGDecompressParams* dparams = nullptr) {
|
||||
return DecodeImageJPG(Bytes(compressed), ColorHints(), ppf,
|
||||
/*constraints=*/nullptr, dparams);
|
||||
}
|
||||
|
||||
Status EncodeWithLibjpeg(const PackedPixelFile& ppf, int quality,
|
||||
std::vector<uint8_t>* compressed) {
|
||||
std::unique_ptr<Encoder> encoder = GetJPEGEncoder();
|
||||
encoder->SetOption("q", std::to_string(quality));
|
||||
EncodedImage encoded;
|
||||
JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded, nullptr));
|
||||
JXL_RETURN_IF_ERROR(!encoded.bitstreams.empty());
|
||||
*compressed = std::move(encoded.bitstreams[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Description(const JxlColorEncoding& color_encoding) {
|
||||
ColorEncoding c_enc;
|
||||
EXPECT_TRUE(c_enc.FromExternal(color_encoding));
|
||||
return Description(c_enc);
|
||||
}
|
||||
|
||||
float BitsPerPixel(const PackedPixelFile& ppf,
|
||||
const std::vector<uint8_t>& compressed) {
|
||||
const size_t num_pixels = ppf.info.xsize * ppf.info.ysize;
|
||||
return compressed.size() * 8.0 / num_pixels;
|
||||
}
|
||||
|
||||
TEST(JpegliTest, JpegliSRGBDecodeTest) {
|
||||
TEST_LIBJPEG_SUPPORT();
|
||||
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(8u, ppf0.info.bits_per_sample);
|
||||
|
||||
std::vector<uint8_t> compressed;
|
||||
ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed));
|
||||
|
||||
PackedPixelFile ppf1;
|
||||
ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf1));
|
||||
PackedPixelFile ppf2;
|
||||
JpegDecompressParams dparams;
|
||||
ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf2));
|
||||
EXPECT_LT(ButteraugliDistance(ppf0, ppf2), ButteraugliDistance(ppf0, ppf1));
|
||||
}
|
||||
|
||||
TEST(JpegliTest, JpegliGrayscaleDecodeTest) {
|
||||
TEST_LIBJPEG_SUPPORT();
|
||||
std::string testimage = "jxl/flower/flower_small.g.depth8.pgm";
|
||||
PackedPixelFile ppf0;
|
||||
ASSERT_TRUE(ReadTestImage(testimage, &ppf0));
|
||||
EXPECT_EQ("Gra_D65_Rel_SRG", Description(ppf0.color_encoding));
|
||||
EXPECT_EQ(8u, ppf0.info.bits_per_sample);
|
||||
|
||||
std::vector<uint8_t> compressed;
|
||||
ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed));
|
||||
|
||||
PackedPixelFile ppf1;
|
||||
ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf1));
|
||||
PackedPixelFile ppf2;
|
||||
JpegDecompressParams dparams;
|
||||
ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf2));
|
||||
EXPECT_LT(ButteraugliDistance(ppf0, ppf2), ButteraugliDistance(ppf0, ppf1));
|
||||
}
|
||||
|
||||
TEST(JpegliTest, JpegliXYBEncodeTest) {
|
||||
TEST_LIBJPEG_SUPPORT();
|
||||
std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
|
||||
PackedPixelFile ppf_in;
|
||||
ASSERT_TRUE(ReadTestImage(testimage, &ppf_in));
|
||||
EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding));
|
||||
EXPECT_EQ(8u, ppf_in.info.bits_per_sample);
|
||||
|
||||
std::vector<uint8_t> compressed;
|
||||
JpegSettings settings;
|
||||
settings.xyb = true;
|
||||
ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
|
||||
PackedPixelFile ppf_out;
|
||||
ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out));
|
||||
EXPECT_SLIGHTLY_BELOW(BitsPerPixel(ppf_in, compressed), 1.45f);
|
||||
EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(ppf_in, ppf_out), 1.32f);
|
||||
}
|
||||
|
||||
TEST(JpegliTest, JpegliDecodeTestLargeSmoothArea) {
|
||||
TEST_LIBJPEG_SUPPORT();
|
||||
TestImage t;
|
||||
const size_t xsize = 2070;
|
||||
const size_t ysize = 1063;
|
||||
ASSERT_TRUE(t.SetDimensions(xsize, ysize));
|
||||
ASSERT_TRUE(t.SetChannels(3));
|
||||
t.SetAllBitDepths(8).SetEndianness(JXL_NATIVE_ENDIAN);
|
||||
JXL_TEST_ASSIGN_OR_DIE(TestImage::Frame frame, t.AddFrame());
|
||||
frame.RandomFill();
|
||||
// Create a large smooth area in the top half of the image. This is to test
|
||||
// that the bias statistics calculation can handle many blocks with all-zero
|
||||
// AC coefficients.
|
||||
for (size_t y = 0; y < ysize / 2; ++y) {
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
for (size_t c = 0; c < 3; ++c) {
|
||||
ASSERT_TRUE(frame.SetValue(y, x, c, 0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
const PackedPixelFile& ppf0 = t.ppf();
|
||||
|
||||
std::vector<uint8_t> compressed;
|
||||
ASSERT_TRUE(EncodeWithLibjpeg(ppf0, 90, &compressed));
|
||||
|
||||
PackedPixelFile ppf1;
|
||||
JpegDecompressParams dparams;
|
||||
ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf1));
|
||||
EXPECT_LT(ButteraugliDistance(ppf0, ppf1), 3.0f);
|
||||
}
|
||||
|
||||
TEST(JpegliTest, JpegliYUVEncodeTest) {
|
||||
TEST_LIBJPEG_SUPPORT();
|
||||
std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
|
||||
PackedPixelFile ppf_in;
|
||||
ASSERT_TRUE(ReadTestImage(testimage, &ppf_in));
|
||||
EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding));
|
||||
EXPECT_EQ(8u, ppf_in.info.bits_per_sample);
|
||||
|
||||
std::vector<uint8_t> compressed;
|
||||
JpegSettings settings;
|
||||
settings.xyb = false;
|
||||
ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
|
||||
PackedPixelFile ppf_out;
|
||||
ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out));
|
||||
EXPECT_SLIGHTLY_BELOW(BitsPerPixel(ppf_in, compressed), 1.7f);
|
||||
EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(ppf_in, ppf_out), 1.32f);
|
||||
}
|
||||
|
||||
TEST(JpegliTest, JpegliYUVChromaSubsamplingEncodeTest) {
|
||||
TEST_LIBJPEG_SUPPORT();
|
||||
std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
|
||||
PackedPixelFile ppf_in;
|
||||
ASSERT_TRUE(ReadTestImage(testimage, &ppf_in));
|
||||
EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding));
|
||||
EXPECT_EQ(8u, ppf_in.info.bits_per_sample);
|
||||
|
||||
std::vector<uint8_t> compressed;
|
||||
JpegSettings settings;
|
||||
for (const char* sampling : {"440", "422", "420"}) {
|
||||
settings.xyb = false;
|
||||
settings.chroma_subsampling = std::string(sampling);
|
||||
ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
|
||||
PackedPixelFile ppf_out;
|
||||
ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out));
|
||||
EXPECT_LE(BitsPerPixel(ppf_in, compressed), 1.55f);
|
||||
EXPECT_LE(ButteraugliDistance(ppf_in, ppf_out), 1.82f);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(JpegliTest, JpegliYUVEncodeTestNoAq) {
|
||||
TEST_LIBJPEG_SUPPORT();
|
||||
std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
|
||||
PackedPixelFile ppf_in;
|
||||
ASSERT_TRUE(ReadTestImage(testimage, &ppf_in));
|
||||
EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding));
|
||||
EXPECT_EQ(8u, ppf_in.info.bits_per_sample);
|
||||
|
||||
std::vector<uint8_t> compressed;
|
||||
JpegSettings settings;
|
||||
settings.xyb = false;
|
||||
settings.use_adaptive_quantization = false;
|
||||
ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
|
||||
PackedPixelFile ppf_out;
|
||||
ASSERT_TRUE(DecodeWithLibjpeg(compressed, &ppf_out));
|
||||
EXPECT_SLIGHTLY_BELOW(BitsPerPixel(ppf_in, compressed), 1.85f);
|
||||
EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(ppf_in, ppf_out), 1.25f);
|
||||
}
|
||||
|
||||
TEST(JpegliTest, JpegliHDRRoundtripTest) {
|
||||
std::string testimage = "jxl/hdr_room.png";
|
||||
PackedPixelFile ppf_in;
|
||||
ASSERT_TRUE(ReadTestImage(testimage, &ppf_in));
|
||||
EXPECT_EQ("Rec2100HLG", Description(ppf_in.color_encoding));
|
||||
EXPECT_EQ(16u, ppf_in.info.bits_per_sample);
|
||||
|
||||
std::vector<uint8_t> compressed;
|
||||
JpegSettings settings;
|
||||
settings.xyb = false;
|
||||
ASSERT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
|
||||
PackedPixelFile ppf_out;
|
||||
JpegDecompressParams dparams;
|
||||
dparams.output_data_type = JXL_TYPE_UINT16;
|
||||
ASSERT_TRUE(DecodeJpeg(compressed, dparams, nullptr, &ppf_out));
|
||||
EXPECT_SLIGHTLY_BELOW(BitsPerPixel(ppf_in, compressed), 2.95f);
|
||||
EXPECT_SLIGHTLY_BELOW(ButteraugliDistance(ppf_in, ppf_out), 1.05f);
|
||||
}
|
||||
|
||||
TEST(JpegliTest, JpegliSetAppData) {
|
||||
std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
|
||||
PackedPixelFile ppf_in;
|
||||
ASSERT_TRUE(ReadTestImage(testimage, &ppf_in));
|
||||
EXPECT_EQ("RGB_D65_SRG_Rel_SRG", Description(ppf_in.color_encoding));
|
||||
EXPECT_EQ(8u, ppf_in.info.bits_per_sample);
|
||||
|
||||
std::vector<uint8_t> compressed;
|
||||
JpegSettings settings;
|
||||
settings.app_data = {0xff, 0xe3, 0, 4, 0, 1};
|
||||
EXPECT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
EXPECT_EQ(settings.app_data, GetAppData(compressed));
|
||||
|
||||
settings.app_data = {0xff, 0xe3, 0, 6, 0, 1, 2, 3, 0xff, 0xef, 0, 4, 0, 1};
|
||||
EXPECT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
EXPECT_EQ(settings.app_data, GetAppData(compressed));
|
||||
|
||||
settings.xyb = true;
|
||||
EXPECT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
EXPECT_EQ(0, memcmp(settings.app_data.data(), GetAppData(compressed).data(),
|
||||
settings.app_data.size()));
|
||||
|
||||
settings.xyb = false;
|
||||
settings.app_data = {0};
|
||||
EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
|
||||
settings.app_data = {0xff, 0xe0};
|
||||
EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
|
||||
settings.app_data = {0xff, 0xe0, 0, 2};
|
||||
EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
|
||||
settings.app_data = {0xff, 0xeb, 0, 4, 0};
|
||||
EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
|
||||
settings.app_data = {0xff, 0xeb, 0, 4, 0, 1, 2, 3};
|
||||
EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
|
||||
settings.app_data = {0xff, 0xab, 0, 4, 0, 1};
|
||||
EXPECT_FALSE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
|
||||
settings.xyb = false;
|
||||
settings.app_data = {
|
||||
0xff, 0xeb, 0, 4, 0, 1, //
|
||||
0xff, 0xe2, 0, 20, 0x49, 0x43, 0x43, 0x5F, 0x50, //
|
||||
0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00, 0, 1, //
|
||||
0, 0, 0, 0, //
|
||||
};
|
||||
EXPECT_TRUE(EncodeJpeg(ppf_in, settings, nullptr, &compressed));
|
||||
EXPECT_EQ(settings.app_data, GetAppData(compressed));
|
||||
|
||||
settings.xyb = true;
|
||||
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) {
|
||||
TEST_LIBJPEG_SUPPORT();
|
||||
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(8u, 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
|
||||
#endif // JPEGXL_ENABLE_JPEGLI
|
||||
@@ -1,158 +0,0 @@
|
||||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
include(jxl_lists.cmake)
|
||||
|
||||
set(JPEGLI_INTERNAL_LIBS
|
||||
hwy
|
||||
Threads::Threads
|
||||
${ATOMICS_LIBRARIES}
|
||||
)
|
||||
|
||||
# JPEGLIB setup
|
||||
set(BITS_IN_JSAMPLE 8)
|
||||
set(MEM_SRCDST_SUPPORTED 1)
|
||||
|
||||
if(JPEGLI_LIBJPEG_LIBRARY_SOVERSION STREQUAL "62")
|
||||
set(JPEG_LIB_VERSION 62)
|
||||
elseif(JPEGLI_LIBJPEG_LIBRARY_SOVERSION STREQUAL "7")
|
||||
set(JPEG_LIB_VERSION 70)
|
||||
elseif(JPEGLI_LIBJPEG_LIBRARY_SOVERSION STREQUAL "8")
|
||||
set(JPEG_LIB_VERSION 80)
|
||||
endif()
|
||||
|
||||
configure_file(
|
||||
../third_party/libjpeg-turbo/jconfig.h.in include/jpegli/jconfig.h)
|
||||
configure_file(
|
||||
../third_party/libjpeg-turbo/jpeglib.h include/jpegli/jpeglib.h COPYONLY)
|
||||
configure_file(
|
||||
../third_party/libjpeg-turbo/jmorecfg.h include/jpegli/jmorecfg.h COPYONLY)
|
||||
|
||||
add_library(jpegli-static STATIC EXCLUDE_FROM_ALL "${JPEGXL_INTERNAL_JPEGLI_SOURCES}")
|
||||
target_compile_options(jpegli-static PRIVATE "${JPEGXL_INTERNAL_FLAGS}")
|
||||
target_compile_options(jpegli-static PUBLIC ${JPEGXL_COVERAGE_FLAGS})
|
||||
set_property(TARGET jpegli-static PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
target_include_directories(jpegli-static PRIVATE
|
||||
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>"
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
|
||||
"${JXL_HWY_INCLUDE_DIRS}"
|
||||
)
|
||||
target_include_directories(jpegli-static PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include/jpegli>"
|
||||
)
|
||||
target_link_libraries(jpegli-static PUBLIC ${JPEGLI_INTERNAL_LIBS})
|
||||
|
||||
#
|
||||
# Tests for jpegli-static
|
||||
#
|
||||
|
||||
find_package(JPEG)
|
||||
if(JPEG_FOUND AND BUILD_TESTING)
|
||||
# TODO(eustas): merge into jxl_tests.cmake?
|
||||
|
||||
add_library(jpegli_libjpeg_util-obj OBJECT
|
||||
${JPEGXL_INTERNAL_JPEGLI_LIBJPEG_HELPER_FILES}
|
||||
)
|
||||
target_include_directories(jpegli_libjpeg_util-obj PRIVATE
|
||||
"${PROJECT_SOURCE_DIR}"
|
||||
"${JPEG_INCLUDE_DIRS}"
|
||||
)
|
||||
target_compile_options(jpegli_libjpeg_util-obj PRIVATE
|
||||
"${JPEGXL_INTERNAL_FLAGS}" "${JPEGXL_COVERAGE_FLAGS}")
|
||||
|
||||
# Individual test binaries:
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests)
|
||||
foreach (TESTFILE IN LISTS JPEGXL_INTERNAL_JPEGLI_TESTS)
|
||||
# The TESTNAME is the name without the extension or directory.
|
||||
get_filename_component(TESTNAME ${TESTFILE} NAME_WE)
|
||||
add_executable(${TESTNAME} ${TESTFILE}
|
||||
$<TARGET_OBJECTS:jpegli_libjpeg_util-obj>
|
||||
${JPEGXL_INTERNAL_JPEGLI_TESTLIB_FILES}
|
||||
)
|
||||
target_compile_options(${TESTNAME} PRIVATE
|
||||
${JPEGXL_INTERNAL_FLAGS}
|
||||
# Add coverage flags to the test binary so code in the private headers of
|
||||
# the library is also instrumented when running tests that execute it.
|
||||
${JPEGXL_COVERAGE_FLAGS}
|
||||
)
|
||||
target_compile_definitions(${TESTNAME} PRIVATE
|
||||
-DTEST_DATA_PATH="${JPEGXL_TEST_DATA_PATH}")
|
||||
target_include_directories(${TESTNAME} PRIVATE
|
||||
"${PROJECT_SOURCE_DIR}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/include"
|
||||
)
|
||||
target_link_libraries(${TESTNAME}
|
||||
hwy
|
||||
jpegli-static
|
||||
gtest
|
||||
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")
|
||||
set_target_properties(${TESTNAME} PROPERTIES COMPILE_FLAGS "-Wno-error")
|
||||
endif ()
|
||||
# 240 seconds because some build types (e.g. coverage) can be quite slow.
|
||||
gtest_discover_tests(${TESTNAME} DISCOVERY_TIMEOUT 240)
|
||||
endforeach ()
|
||||
endif()
|
||||
|
||||
#
|
||||
# Build libjpeg.so that links to libjpeg-static
|
||||
#
|
||||
|
||||
if (JPEGXL_ENABLE_JPEGLI_LIBJPEG AND NOT APPLE AND NOT WIN32 AND NOT EMSCRIPTEN)
|
||||
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})
|
||||
set_property(TARGET jpegli-libjpeg-obj PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
target_include_directories(jpegli-libjpeg-obj PRIVATE
|
||||
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}>"
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include/jpegli>"
|
||||
)
|
||||
target_compile_definitions(jpegli-libjpeg-obj PUBLIC
|
||||
${JPEGLI_LIBJPEG_OBJ_COMPILE_DEFINITIONS}
|
||||
)
|
||||
set(JPEGLI_LIBJPEG_INTERNAL_OBJECTS $<TARGET_OBJECTS:jpegli-libjpeg-obj>)
|
||||
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/jpegli)
|
||||
add_library(jpeg SHARED ${JPEGLI_LIBJPEG_INTERNAL_OBJECTS})
|
||||
target_link_libraries(jpeg PUBLIC ${JPEGXL_COVERAGE_FLAGS})
|
||||
target_link_libraries(jpeg PRIVATE jpegli-static)
|
||||
set_target_properties(jpeg PROPERTIES
|
||||
VERSION ${JPEGLI_LIBJPEG_LIBRARY_VERSION}
|
||||
SOVERSION ${JPEGLI_LIBJPEG_LIBRARY_SOVERSION}
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/jpegli"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/jpegli")
|
||||
|
||||
# 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.${JPEGLI_LIBJPEG_LIBRARY_SOVERSION})
|
||||
set_property(TARGET jpeg APPEND_STRING PROPERTY
|
||||
LINK_FLAGS " -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/jpegli/jpeg.version.${JPEGLI_LIBJPEG_LIBRARY_SOVERSION}")
|
||||
|
||||
if (JPEGXL_INSTALL_JPEGLI_LIBJPEG)
|
||||
install(TARGETS jpeg
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(
|
||||
DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include/jpegli/"
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
endif()
|
||||
|
||||
# This hides the default visibility symbols from static libraries bundled into
|
||||
# the shared library. In particular this prevents exposing symbols from hwy
|
||||
# in the shared library.
|
||||
if(LINKER_SUPPORT_EXCLUDE_LIBS)
|
||||
set_property(TARGET jpeg APPEND_STRING PROPERTY
|
||||
LINK_FLAGS " ${LINKER_EXCLUDE_LIBS_FLAG}")
|
||||
endif()
|
||||
endif()
|
||||
@@ -1,52 +0,0 @@
|
||||
:warning: **Important Update:** Development continues at https://github.com/google/jpegli
|
||||
|
||||
|
||||
# Improved JPEG encoder and decoder implementation
|
||||
|
||||
This subdirectory contains a JPEG encoder and decoder implementation that is
|
||||
API and ABI compatible with libjpeg62.
|
||||
|
||||
## Building
|
||||
|
||||
When building the parent libjxl project, two binaries, `tools/cjpegli` and
|
||||
`tools/djpegli` will be built, as well as a
|
||||
`lib/jpegli/libjpeg.so.62.3.0` shared library that can be used as a drop-in
|
||||
replacement for the system library with the same name.
|
||||
|
||||
## Encoder improvements
|
||||
|
||||
Improvements and new features used by the encoder include:
|
||||
|
||||
* Support for 16-bit unsigned and 32-bit floating point input buffers.
|
||||
|
||||
* Color space conversions, chroma subsampling and DCT are all done in floating
|
||||
point precision, the conversion to integers happens first when producing
|
||||
the final quantized DCT coefficients.
|
||||
|
||||
* The desired quality can be indicated by a distance parameter that is
|
||||
analogous to the distance parameter of JPEG XL. The quantization tables
|
||||
are chosen based on the distance and the chroma subsampling mode, with
|
||||
different positions in the quantization matrix scaling differently, and the
|
||||
red and blue chrominance channels have separate quantization tables.
|
||||
|
||||
* Adaptive dead-zone quantization. On noisy parts of the image, quantization
|
||||
thresholds for zero coefficients are higher than on smoother parts of the
|
||||
image.
|
||||
|
||||
* Support for more efficient compression of JPEGs with an ICC profile
|
||||
representing the XYB colorspace. These JPEGs will not be converted to the
|
||||
YCbCr colorspace, but specialized quantization tables will be chosen for
|
||||
the original X, Y, B channels.
|
||||
|
||||
## Decoder improvements
|
||||
|
||||
* Support for 16-bit unsigned and 32-bit floating point output buffers.
|
||||
|
||||
* Non-zero DCT coefficients are dequantized to the expectation value of their
|
||||
respective quantization intervals assuming a Laplacian distribution of the
|
||||
original unquantized DCT coefficients.
|
||||
|
||||
* After dequantization, inverse DCT, chroma upsampling and color space
|
||||
conversions are all done in floating point precision, the conversion to
|
||||
integer samples happens only in the final output phase (unless output to
|
||||
floating point was requested).
|
||||
@@ -1,570 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/adaptive_quantization.h"
|
||||
|
||||
#include <jxl/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
|
||||
#undef HWY_TARGET_INCLUDE
|
||||
#define HWY_TARGET_INCLUDE "lib/jpegli/adaptive_quantization.cc"
|
||||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/jpegli/encode_internal.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::AbsDiff;
|
||||
using hwy::HWY_NAMESPACE::Add;
|
||||
using hwy::HWY_NAMESPACE::And;
|
||||
using hwy::HWY_NAMESPACE::ApproximateReciprocal;
|
||||
using hwy::HWY_NAMESPACE::Div;
|
||||
using hwy::HWY_NAMESPACE::Floor;
|
||||
using hwy::HWY_NAMESPACE::GetLane;
|
||||
using hwy::HWY_NAMESPACE::Max;
|
||||
using hwy::HWY_NAMESPACE::Min;
|
||||
using hwy::HWY_NAMESPACE::Mul;
|
||||
using hwy::HWY_NAMESPACE::MulAdd;
|
||||
using hwy::HWY_NAMESPACE::NegMulAdd;
|
||||
using hwy::HWY_NAMESPACE::Rebind;
|
||||
using hwy::HWY_NAMESPACE::ShiftLeft;
|
||||
using hwy::HWY_NAMESPACE::ShiftRight;
|
||||
using hwy::HWY_NAMESPACE::Sqrt;
|
||||
using hwy::HWY_NAMESPACE::Sub;
|
||||
using hwy::HWY_NAMESPACE::ZeroIfNegative;
|
||||
|
||||
constexpr float kInputScaling = 1.0f / 255.0f;
|
||||
|
||||
// Primary template: default to actual division.
|
||||
template <typename T, class V>
|
||||
struct FastDivision {
|
||||
HWY_INLINE V operator()(const V n, const V d) const { return n / d; }
|
||||
};
|
||||
// Partial specialization for float vectors.
|
||||
template <class V>
|
||||
struct FastDivision<float, V> {
|
||||
// One Newton-Raphson iteration.
|
||||
static HWY_INLINE V ReciprocalNR(const V x) {
|
||||
const auto rcp = ApproximateReciprocal(x);
|
||||
const auto sum = Add(rcp, rcp);
|
||||
const auto x_rcp = Mul(x, rcp);
|
||||
return NegMulAdd(x_rcp, rcp, sum);
|
||||
}
|
||||
|
||||
V operator()(const V n, const V d) const {
|
||||
#if JXL_TRUE // Faster on SKX
|
||||
return Div(n, d);
|
||||
#else
|
||||
return n * ReciprocalNR(d);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
// Approximates smooth functions via rational polynomials (i.e. dividing two
|
||||
// polynomials). Evaluates polynomials via Horner's scheme, which is faster than
|
||||
// Clenshaw recurrence for Chebyshev polynomials. LoadDup128 allows us to
|
||||
// specify constants (replicated 4x) independently of the lane count.
|
||||
template <size_t NP, size_t NQ, class D, class V, typename T>
|
||||
HWY_INLINE HWY_MAYBE_UNUSED V EvalRationalPolynomial(const D d, const V x,
|
||||
const T (&p)[NP],
|
||||
const T (&q)[NQ]) {
|
||||
constexpr size_t kDegP = NP / 4 - 1;
|
||||
constexpr size_t kDegQ = NQ / 4 - 1;
|
||||
auto yp = LoadDup128(d, &p[kDegP * 4]);
|
||||
auto yq = LoadDup128(d, &q[kDegQ * 4]);
|
||||
// We use pointer arithmetic to refer to &p[(kDegP - n) * 4] to avoid a
|
||||
// compiler warning that the index is out of bounds since we are already
|
||||
// checking that it is not out of bounds with (kDegP >= n) and the access
|
||||
// will be optimized away. Similarly with q and kDegQ.
|
||||
HWY_FENCE;
|
||||
if (kDegP >= 1) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 1) * 4)));
|
||||
if (kDegQ >= 1) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 1) * 4)));
|
||||
HWY_FENCE;
|
||||
if (kDegP >= 2) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 2) * 4)));
|
||||
if (kDegQ >= 2) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 2) * 4)));
|
||||
HWY_FENCE;
|
||||
if (kDegP >= 3) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 3) * 4)));
|
||||
if (kDegQ >= 3) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 3) * 4)));
|
||||
HWY_FENCE;
|
||||
if (kDegP >= 4) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 4) * 4)));
|
||||
if (kDegQ >= 4) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 4) * 4)));
|
||||
HWY_FENCE;
|
||||
if (kDegP >= 5) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 5) * 4)));
|
||||
if (kDegQ >= 5) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 5) * 4)));
|
||||
HWY_FENCE;
|
||||
if (kDegP >= 6) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 6) * 4)));
|
||||
if (kDegQ >= 6) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 6) * 4)));
|
||||
HWY_FENCE;
|
||||
if (kDegP >= 7) yp = MulAdd(yp, x, LoadDup128(d, p + ((kDegP - 7) * 4)));
|
||||
if (kDegQ >= 7) yq = MulAdd(yq, x, LoadDup128(d, q + ((kDegQ - 7) * 4)));
|
||||
|
||||
return FastDivision<T, V>()(yp, yq);
|
||||
}
|
||||
|
||||
// Computes base-2 logarithm like std::log2. Undefined if negative / NaN.
|
||||
// L1 error ~3.9E-6
|
||||
template <class DF, class V>
|
||||
V FastLog2f(const DF df, V x) {
|
||||
// 2,2 rational polynomial approximation of std::log1p(x) / std::log(2).
|
||||
HWY_ALIGN const float p[4 * (2 + 1)] = {HWY_REP4(-1.8503833400518310E-06f),
|
||||
HWY_REP4(1.4287160470083755E+00f),
|
||||
HWY_REP4(7.4245873327820566E-01f)};
|
||||
HWY_ALIGN const float q[4 * (2 + 1)] = {HWY_REP4(9.9032814277590719E-01f),
|
||||
HWY_REP4(1.0096718572241148E+00f),
|
||||
HWY_REP4(1.7409343003366853E-01f)};
|
||||
|
||||
const Rebind<int32_t, DF> di;
|
||||
const auto x_bits = BitCast(di, x);
|
||||
|
||||
// Range reduction to [-1/3, 1/3] - 3 integer, 2 float ops
|
||||
const auto exp_bits = Sub(x_bits, Set(di, 0x3f2aaaab)); // = 2/3
|
||||
// Shifted exponent = log2; also used to clear mantissa.
|
||||
const auto exp_shifted = ShiftRight<23>(exp_bits);
|
||||
const auto mantissa = BitCast(df, Sub(x_bits, ShiftLeft<23>(exp_shifted)));
|
||||
const auto exp_val = ConvertTo(df, exp_shifted);
|
||||
return Add(EvalRationalPolynomial(df, Sub(mantissa, Set(df, 1.0f)), p, q),
|
||||
exp_val);
|
||||
}
|
||||
|
||||
// max relative error ~3e-7
|
||||
template <class DF, class V>
|
||||
V FastPow2f(const DF df, V x) {
|
||||
const Rebind<int32_t, DF> di;
|
||||
auto floorx = Floor(x);
|
||||
auto exp =
|
||||
BitCast(df, ShiftLeft<23>(Add(ConvertTo(di, floorx), Set(di, 127))));
|
||||
auto frac = Sub(x, floorx);
|
||||
auto num = Add(frac, Set(df, 1.01749063e+01));
|
||||
num = MulAdd(num, frac, Set(df, 4.88687798e+01));
|
||||
num = MulAdd(num, frac, Set(df, 9.85506591e+01));
|
||||
num = Mul(num, exp);
|
||||
auto den = MulAdd(frac, Set(df, 2.10242958e-01), Set(df, -2.22328856e-02));
|
||||
den = MulAdd(den, frac, Set(df, -1.94414990e+01));
|
||||
den = MulAdd(den, frac, Set(df, 9.85506633e+01));
|
||||
return Div(num, den);
|
||||
}
|
||||
|
||||
inline float FastPow2f(float f) {
|
||||
HWY_CAPPED(float, 1) D;
|
||||
return GetLane(FastPow2f(D, Set(D, f)));
|
||||
}
|
||||
|
||||
// The following functions modulate an exponent (out_val) and return the updated
|
||||
// value. Their descriptor is limited to 8 lanes for 8x8 blocks.
|
||||
|
||||
template <class D, class V>
|
||||
V ComputeMask(const D d, const V out_val) {
|
||||
const auto kBase = Set(d, -0.74174993f);
|
||||
const auto kMul4 = Set(d, 3.2353257320940401f);
|
||||
const auto kMul2 = Set(d, 12.906028311180409f);
|
||||
const auto kOffset2 = Set(d, 305.04035728311436f);
|
||||
const auto kMul3 = Set(d, 5.0220313103171232f);
|
||||
const auto kOffset3 = Set(d, 2.1925739705298404f);
|
||||
const auto kOffset4 = Mul(Set(d, 0.25f), kOffset3);
|
||||
const auto kMul0 = Set(d, 0.74760422233706747f);
|
||||
const auto k1 = Set(d, 1.0f);
|
||||
|
||||
// Avoid division by zero.
|
||||
const auto v1 = Max(Mul(out_val, kMul0), Set(d, 1e-3f));
|
||||
const auto v2 = Div(k1, Add(v1, kOffset2));
|
||||
const auto v3 = Div(k1, MulAdd(v1, v1, kOffset3));
|
||||
const auto v4 = Div(k1, MulAdd(v1, v1, kOffset4));
|
||||
// TODO(jyrki):
|
||||
// A log or two here could make sense. In butteraugli we have effectively
|
||||
// log(log(x + C)) for this kind of use, as a single log is used in
|
||||
// saturating visual masking and here the modulation values are exponential,
|
||||
// another log would counter that.
|
||||
return Add(kBase, MulAdd(kMul4, v4, MulAdd(kMul2, v2, Mul(kMul3, v3))));
|
||||
}
|
||||
|
||||
// mul and mul2 represent a scaling difference between jxl and butteraugli.
|
||||
const float kSGmul = 226.0480446705883f;
|
||||
const float kSGmul2 = 1.0f / 73.377132366608819f;
|
||||
|
||||
// Multiplier for conversion of log2(x) result to ln(x).
|
||||
// print(1.0 / math.log2(math.e))
|
||||
constexpr float kInvLog2e = 0.6931471805599453;
|
||||
|
||||
// Includes correction factor for std::log -> log2.
|
||||
const float kSGRetMul = kSGmul2 * 18.6580932135f * kInvLog2e;
|
||||
const float kSGVOffset = 7.14672470003f;
|
||||
|
||||
template <bool invert, typename D, typename V>
|
||||
V RatioOfDerivativesOfCubicRootToSimpleGamma(const D d, V v) {
|
||||
// The opsin space in jxl is the cubic root of photons, i.e., v * v * v
|
||||
// is related to the number of photons.
|
||||
//
|
||||
// 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.
|
||||
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 * kInvLog2e + kEpsilon) / kInputScaling;
|
||||
static const float kDenMul =
|
||||
kInvLog2e * kSGmul * kInputScaling * kInputScaling;
|
||||
|
||||
v = ZeroIfNegative(v);
|
||||
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(num_mul, v2, num_offset);
|
||||
const auto den = MulAdd(Mul(den_mul, v), v2, den_offset);
|
||||
return invert ? Div(num, den) : Div(den, num);
|
||||
}
|
||||
|
||||
template <bool invert = false>
|
||||
float RatioOfDerivativesOfCubicRootToSimpleGamma(float v) {
|
||||
using DScalar = HWY_CAPPED(float, 1);
|
||||
auto vscalar = Load(DScalar(), &v);
|
||||
return GetLane(
|
||||
RatioOfDerivativesOfCubicRootToSimpleGamma<invert>(DScalar(), vscalar));
|
||||
}
|
||||
|
||||
// TODO(veluca): this function computes an approximation of the derivative of
|
||||
// SimpleGamma with (f(x+eps)-f(x))/eps. Consider two-sided approximation or
|
||||
// exact derivatives. For reference, SimpleGamma was:
|
||||
/*
|
||||
template <typename D, typename V>
|
||||
V SimpleGamma(const D d, V v) {
|
||||
// A simple HDR compatible gamma function.
|
||||
const auto mul = Set(d, kSGmul);
|
||||
const auto kRetMul = Set(d, kSGRetMul);
|
||||
const auto kRetAdd = Set(d, kSGmul2 * -20.2789020414f);
|
||||
const auto kVOffset = Set(d, kSGVOffset);
|
||||
|
||||
v *= mul;
|
||||
|
||||
// This should happen rarely, but may lead to a NaN, which is rather
|
||||
// undesirable. Since negative photons don't exist we solve the NaNs by
|
||||
// clamping here.
|
||||
// TODO(veluca): with FastLog2f, this no longer leads to NaNs.
|
||||
v = ZeroIfNegative(v);
|
||||
return kRetMul * FastLog2f(d, v + kVOffset) + kRetAdd;
|
||||
}
|
||||
*/
|
||||
|
||||
template <class D, class V>
|
||||
V GammaModulation(const D d, const size_t x, const size_t y,
|
||||
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);
|
||||
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 = block_start + dy * input.stride();
|
||||
for (size_t dx = 0; dx < 8; dx += Lanes(d)) {
|
||||
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), 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.
|
||||
const auto kGamma = Set(d, -0.15526878023684174f * kInvLog2e);
|
||||
return MulAdd(kGamma, FastLog2f(d, overall_ratio), out_val);
|
||||
}
|
||||
|
||||
// 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 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[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 = block_start + dy * input.stride();
|
||||
const float* JXL_RESTRICT row_in_next =
|
||||
dy == 7 ? row_in : row_in + input.stride();
|
||||
|
||||
for (size_t dx = 0; dx < 8; dx += Lanes(d)) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
sum = SumOfLanes(d, sum);
|
||||
return MulAdd(sum, sumcoeff, out_val);
|
||||
}
|
||||
|
||||
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 (y_quant_01 >= kDampenRampStart) {
|
||||
dampen = 1.0f - ((y_quant_01 - kDampenRampStart) /
|
||||
(kDampenRampEnd - kDampenRampStart));
|
||||
if (dampen < 0) {
|
||||
dampen = 0;
|
||||
}
|
||||
}
|
||||
const float mul = kAcQuant * dampen;
|
||||
const float add = (1.0f - dampen) * base_level;
|
||||
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, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename D, typename V>
|
||||
V MaskingSqrt(const D d, V v) {
|
||||
static const float kLogOffset = 28;
|
||||
static const float kMul = 211.50759899638012f;
|
||||
const auto mul_v = Set(d, kMul * 1e8);
|
||||
const auto offset_v = Set(d, kLogOffset);
|
||||
return Mul(Set(d, 0.25f), Sqrt(MulAdd(v, Sqrt(mul_v), offset_v)));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_d_out = pre_erosion->Row(y_out);
|
||||
for (size_t x = 0; x < xsize_out; x++) {
|
||||
row_d_out[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
|
||||
#if HWY_ONCE
|
||||
namespace jpegli {
|
||||
HWY_EXPORT(ComputePreErosion);
|
||||
HWY_EXPORT(FuzzyErosion);
|
||||
HWY_EXPORT(PerBlockModulations);
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kPreErosionBorder = 1;
|
||||
|
||||
} // namespace
|
||||
|
||||
void ComputeAdaptiveQuantField(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
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];
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
#endif // HWY_ONCE
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_ADAPTIVE_QUANTIZATION_H_
|
||||
#define LIB_JPEGLI_ADAPTIVE_QUANTIZATION_H_
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void ComputeAdaptiveQuantField(j_compress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_ADAPTIVE_QUANTIZATION_H_
|
||||
@@ -1,67 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/bit_writer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
#include "lib/jpegli/memory_manager.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
|
||||
@@ -1,98 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_BIT_WRITER_H_
|
||||
#define LIB_JPEGLI_BIT_WRITER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jxl/base/byte_order.h"
|
||||
#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 8 bytes without checking.
|
||||
StoreBE64(bw->put_buffer, bw->data + bw->pos);
|
||||
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_
|
||||
@@ -1,460 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/bitstream.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/bit_writer.h"
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void WriteOutput(j_compress_ptr cinfo, const uint8_t* buf, size_t bufsize) {
|
||||
size_t pos = 0;
|
||||
while (pos < bufsize) {
|
||||
if (cinfo->dest->free_in_buffer == 0 &&
|
||||
!(*cinfo->dest->empty_output_buffer)(cinfo)) {
|
||||
JPEGLI_ERROR("Destination suspension is not supported in markers.");
|
||||
}
|
||||
size_t len = std::min<size_t>(cinfo->dest->free_in_buffer, bufsize - pos);
|
||||
memcpy(cinfo->dest->next_output_byte, buf + pos, len);
|
||||
pos += len;
|
||||
cinfo->dest->free_in_buffer -= len;
|
||||
cinfo->dest->next_output_byte += len;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteOutput(j_compress_ptr cinfo, const std::vector<uint8_t>& bytes) {
|
||||
WriteOutput(cinfo, bytes.data(), bytes.size());
|
||||
}
|
||||
|
||||
void WriteOutput(j_compress_ptr cinfo, std::initializer_list<uint8_t> bytes) {
|
||||
WriteOutput(cinfo, bytes.begin(), bytes.size());
|
||||
}
|
||||
|
||||
void EncodeAPP0(j_compress_ptr cinfo) {
|
||||
WriteOutput(cinfo,
|
||||
{0xff, 0xe0, 0, 16, 'J', 'F', 'I', 'F', '\0',
|
||||
cinfo->JFIF_major_version, cinfo->JFIF_minor_version,
|
||||
cinfo->density_unit, static_cast<uint8_t>(cinfo->X_density >> 8),
|
||||
static_cast<uint8_t>(cinfo->X_density & 0xff),
|
||||
static_cast<uint8_t>(cinfo->Y_density >> 8),
|
||||
static_cast<uint8_t>(cinfo->Y_density & 0xff), 0, 0});
|
||||
}
|
||||
|
||||
void EncodeAPP14(j_compress_ptr cinfo) {
|
||||
uint8_t color_transform = cinfo->jpeg_color_space == JCS_YCbCr ? 1
|
||||
: cinfo->jpeg_color_space == JCS_YCCK ? 2
|
||||
: 0;
|
||||
WriteOutput(cinfo, {0xff, 0xee, 0, 14, 'A', 'd', 'o', 'b', 'e', 0, 100, 0, 0,
|
||||
0, 0, color_transform});
|
||||
}
|
||||
|
||||
void WriteFileHeader(j_compress_ptr cinfo) {
|
||||
WriteOutput(cinfo, {0xFF, 0xD8}); // SOI
|
||||
if (cinfo->write_JFIF_header) {
|
||||
EncodeAPP0(cinfo);
|
||||
}
|
||||
if (cinfo->write_Adobe_marker) {
|
||||
EncodeAPP14(cinfo);
|
||||
}
|
||||
}
|
||||
|
||||
bool EncodeDQT(j_compress_ptr cinfo, bool write_all_tables) {
|
||||
uint8_t data[4 + NUM_QUANT_TBLS * (1 + 2 * DCTSIZE2)]; // 520 bytes
|
||||
size_t pos = 0;
|
||||
data[pos++] = 0xFF;
|
||||
data[pos++] = 0xDB;
|
||||
pos += 2; // Length will be filled in later.
|
||||
|
||||
int send_table[NUM_QUANT_TBLS] = {};
|
||||
if (write_all_tables) {
|
||||
for (int i = 0; i < NUM_QUANT_TBLS; ++i) {
|
||||
if (cinfo->quant_tbl_ptrs[i]) send_table[i] = 1;
|
||||
}
|
||||
} else {
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
send_table[cinfo->comp_info[c].quant_tbl_no] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_baseline = true;
|
||||
for (int i = 0; i < NUM_QUANT_TBLS; ++i) {
|
||||
if (!send_table[i]) continue;
|
||||
JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[i];
|
||||
if (quant_table == nullptr) {
|
||||
JPEGLI_ERROR("Missing quant table %d", i);
|
||||
}
|
||||
int precision = 0;
|
||||
for (UINT16 q : quant_table->quantval) {
|
||||
if (q > 255) {
|
||||
precision = 1;
|
||||
is_baseline = false;
|
||||
}
|
||||
}
|
||||
if (quant_table->sent_table) {
|
||||
continue;
|
||||
}
|
||||
data[pos++] = (precision << 4) + i;
|
||||
for (size_t j = 0; j < DCTSIZE2; ++j) {
|
||||
int val_idx = kJPEGNaturalOrder[j];
|
||||
int val = quant_table->quantval[val_idx];
|
||||
if (val == 0) {
|
||||
JPEGLI_ERROR("Invalid quantval 0.");
|
||||
}
|
||||
if (precision) {
|
||||
data[pos++] = val >> 8;
|
||||
}
|
||||
data[pos++] = val & 0xFFu;
|
||||
}
|
||||
quant_table->sent_table = TRUE;
|
||||
}
|
||||
if (pos > 4) {
|
||||
data[2] = (pos - 2) >> 8u;
|
||||
data[3] = (pos - 2) & 0xFFu;
|
||||
WriteOutput(cinfo, data, pos);
|
||||
}
|
||||
return is_baseline;
|
||||
}
|
||||
|
||||
void EncodeSOF(j_compress_ptr cinfo, bool is_baseline) {
|
||||
if (cinfo->data_precision != kJpegPrecision) {
|
||||
JPEGLI_ERROR("Unsupported data precision %d", cinfo->data_precision);
|
||||
}
|
||||
const uint8_t marker = cinfo->progressive_mode ? 0xc2
|
||||
: is_baseline ? 0xc0
|
||||
: 0xc1;
|
||||
const size_t n_comps = cinfo->num_components;
|
||||
const size_t marker_len = 8 + 3 * n_comps;
|
||||
std::vector<uint8_t> data(marker_len + 2);
|
||||
size_t pos = 0;
|
||||
data[pos++] = 0xFF;
|
||||
data[pos++] = marker;
|
||||
data[pos++] = marker_len >> 8u;
|
||||
data[pos++] = marker_len & 0xFFu;
|
||||
data[pos++] = kJpegPrecision;
|
||||
data[pos++] = cinfo->image_height >> 8u;
|
||||
data[pos++] = cinfo->image_height & 0xFFu;
|
||||
data[pos++] = cinfo->image_width >> 8u;
|
||||
data[pos++] = cinfo->image_width & 0xFFu;
|
||||
data[pos++] = n_comps;
|
||||
for (size_t i = 0; i < n_comps; ++i) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[i];
|
||||
data[pos++] = comp->component_id;
|
||||
data[pos++] = ((comp->h_samp_factor << 4u) | (comp->v_samp_factor));
|
||||
const uint32_t quant_idx = comp->quant_tbl_no;
|
||||
if (cinfo->quant_tbl_ptrs[quant_idx] == nullptr) {
|
||||
JPEGLI_ERROR("Invalid component quant table index %u.", quant_idx);
|
||||
}
|
||||
data[pos++] = quant_idx;
|
||||
}
|
||||
WriteOutput(cinfo, data);
|
||||
}
|
||||
|
||||
void WriteFrameHeader(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
bool is_baseline = EncodeDQT(cinfo, /*write_all_tables=*/false);
|
||||
if (cinfo->progressive_mode || cinfo->arith_code ||
|
||||
cinfo->data_precision != 8) {
|
||||
is_baseline = false;
|
||||
}
|
||||
for (size_t i = 0; i < m->num_huffman_tables; ++i) {
|
||||
int slot_id = m->slot_id_map[i];
|
||||
if (slot_id > 0x11 || (slot_id > 0x01 && slot_id < 0x10)) {
|
||||
is_baseline = false;
|
||||
}
|
||||
}
|
||||
EncodeSOF(cinfo, is_baseline);
|
||||
}
|
||||
|
||||
void EncodeDRI(j_compress_ptr cinfo) {
|
||||
WriteOutput(cinfo, {0xFF, 0xDD, 0, 4,
|
||||
static_cast<uint8_t>(cinfo->restart_interval >> 8),
|
||||
static_cast<uint8_t>(cinfo->restart_interval & 0xFF)});
|
||||
}
|
||||
|
||||
void EncodeDHT(j_compress_ptr cinfo, size_t offset, size_t num) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
size_t marker_len = 2;
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
const JHUFF_TBL& table = m->huffman_tables[offset + i];
|
||||
if (table.sent_table) continue;
|
||||
marker_len += kJpegHuffmanMaxBitLength + 1;
|
||||
for (size_t j = 0; j <= kJpegHuffmanMaxBitLength; ++j) {
|
||||
marker_len += table.bits[j];
|
||||
}
|
||||
}
|
||||
std::vector<uint8_t> data(marker_len + 2);
|
||||
size_t pos = 0;
|
||||
data[pos++] = 0xFF;
|
||||
data[pos++] = 0xC4;
|
||||
data[pos++] = marker_len >> 8u;
|
||||
data[pos++] = marker_len & 0xFFu;
|
||||
for (size_t t = 0; t < num; ++t) {
|
||||
const JHUFF_TBL& table = m->huffman_tables[offset + t];
|
||||
if (table.sent_table) continue;
|
||||
size_t total_count = 0;
|
||||
for (size_t i = 0; i <= kJpegHuffmanMaxBitLength; ++i) {
|
||||
total_count += table.bits[i];
|
||||
}
|
||||
data[pos++] = m->slot_id_map[offset + t];
|
||||
for (size_t i = 1; i <= kJpegHuffmanMaxBitLength; ++i) {
|
||||
data[pos++] = table.bits[i];
|
||||
}
|
||||
for (size_t i = 0; i < total_count; ++i) {
|
||||
data[pos++] = table.huffval[i];
|
||||
}
|
||||
}
|
||||
if (marker_len > 2) {
|
||||
WriteOutput(cinfo, data);
|
||||
}
|
||||
}
|
||||
|
||||
void EncodeSOS(j_compress_ptr cinfo, int scan_index) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
const jpeg_scan_info* scan_info = &cinfo->scan_info[scan_index];
|
||||
const size_t marker_len = 6 + 2 * scan_info->comps_in_scan;
|
||||
std::vector<uint8_t> data(marker_len + 2);
|
||||
size_t pos = 0;
|
||||
data[pos++] = 0xFF;
|
||||
data[pos++] = 0xDA;
|
||||
data[pos++] = marker_len >> 8u;
|
||||
data[pos++] = marker_len & 0xFFu;
|
||||
data[pos++] = scan_info->comps_in_scan;
|
||||
for (int i = 0; i < scan_info->comps_in_scan; ++i) {
|
||||
int comp_idx = scan_info->component_index[i];
|
||||
data[pos++] = cinfo->comp_info[comp_idx].component_id;
|
||||
int dc_slot_id = m->slot_id_map[m->context_map[comp_idx]];
|
||||
int ac_context = m->ac_ctx_offset[scan_index] + i;
|
||||
int ac_slot_id = m->slot_id_map[m->context_map[ac_context]];
|
||||
data[pos++] = (dc_slot_id << 4u) + (ac_slot_id - 16);
|
||||
}
|
||||
data[pos++] = scan_info->Ss;
|
||||
data[pos++] = scan_info->Se;
|
||||
data[pos++] = ((scan_info->Ah << 4u) | (scan_info->Al));
|
||||
WriteOutput(cinfo, data);
|
||||
}
|
||||
|
||||
void WriteScanHeader(j_compress_ptr cinfo, int scan_index) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
const jpeg_scan_info* scan_info = &cinfo->scan_info[scan_index];
|
||||
cinfo->restart_interval = m->scan_token_info[scan_index].restart_interval;
|
||||
if (cinfo->restart_interval != m->last_restart_interval) {
|
||||
EncodeDRI(cinfo);
|
||||
m->last_restart_interval = cinfo->restart_interval;
|
||||
}
|
||||
size_t num_dht = 0;
|
||||
if (scan_index == 0) {
|
||||
// For the first scan we emit all DC and at most 4 AC Huffman codes.
|
||||
for (size_t i = 0, num_ac = 0; i < m->num_huffman_tables; ++i) {
|
||||
if (m->slot_id_map[i] >= 16 && num_ac++ >= 4) break;
|
||||
++num_dht;
|
||||
}
|
||||
} else if (scan_info->Ss > 0) {
|
||||
// For multi-scan sequential and progressive DC scans we have already
|
||||
// emitted all Huffman codes that we need before the first scan. For
|
||||
// progressive AC scans we only need at most one new Huffman code.
|
||||
if (m->context_map[m->ac_ctx_offset[scan_index]] == m->next_dht_index) {
|
||||
num_dht = 1;
|
||||
}
|
||||
}
|
||||
if (num_dht > 0) {
|
||||
EncodeDHT(cinfo, m->next_dht_index, num_dht);
|
||||
m->next_dht_index += num_dht;
|
||||
}
|
||||
EncodeSOS(cinfo, scan_index);
|
||||
}
|
||||
|
||||
void WriteBlock(const int32_t* JXL_RESTRICT symbols,
|
||||
const int32_t* JXL_RESTRICT extra_bits, const int num_nonzeros,
|
||||
const bool emit_eob,
|
||||
const HuffmanCodeTable* JXL_RESTRICT dc_code,
|
||||
const HuffmanCodeTable* JXL_RESTRICT ac_code,
|
||||
JpegBitWriter* JXL_RESTRICT bw) {
|
||||
int symbol = symbols[0];
|
||||
WriteBits(bw, dc_code->depth[symbol], dc_code->code[symbol] | extra_bits[0]);
|
||||
for (int i = 1; i < num_nonzeros; ++i) {
|
||||
symbol = symbols[i];
|
||||
if (symbol > 255) {
|
||||
WriteBits(bw, ac_code->depth[0xf0], ac_code->code[0xf0]);
|
||||
symbol -= 256;
|
||||
if (symbol > 255) {
|
||||
WriteBits(bw, ac_code->depth[0xf0], ac_code->code[0xf0]);
|
||||
symbol -= 256;
|
||||
if (symbol > 255) {
|
||||
WriteBits(bw, ac_code->depth[0xf0], ac_code->code[0xf0]);
|
||||
symbol -= 256;
|
||||
}
|
||||
}
|
||||
}
|
||||
WriteBits(bw, ac_code->depth[symbol],
|
||||
ac_code->code[symbol] | extra_bits[i]);
|
||||
}
|
||||
if (emit_eob) {
|
||||
WriteBits(bw, ac_code->depth[0], ac_code->code[0]);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
JXL_INLINE void EmitMarker(JpegBitWriter* bw, int marker) {
|
||||
bw->data[bw->pos++] = 0xFF;
|
||||
bw->data[bw->pos++] = marker;
|
||||
}
|
||||
|
||||
void WriteTokens(j_compress_ptr cinfo, int scan_index, JpegBitWriter* bw) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
HuffmanCodeTable* coding_tables = &m->coding_tables[0];
|
||||
int next_restart_marker = 0;
|
||||
const ScanTokenInfo& sti = m->scan_token_info[scan_index];
|
||||
size_t num_token_arrays = m->cur_token_array + 1;
|
||||
size_t total_tokens = 0;
|
||||
size_t restart_idx = 0;
|
||||
size_t next_restart = sti.restarts[restart_idx];
|
||||
uint8_t* context_map = m->context_map;
|
||||
for (size_t ta = 0; ta < num_token_arrays; ++ta) {
|
||||
Token* tokens = m->token_arrays[ta].tokens;
|
||||
size_t num_tokens = m->token_arrays[ta].num_tokens;
|
||||
if (sti.token_offset < total_tokens + num_tokens &&
|
||||
total_tokens < sti.token_offset + sti.num_tokens) {
|
||||
size_t start_ix =
|
||||
total_tokens < sti.token_offset ? sti.token_offset - total_tokens : 0;
|
||||
size_t end_ix = std::min(sti.token_offset + sti.num_tokens - total_tokens,
|
||||
num_tokens);
|
||||
size_t cycle_len = bw->len / 8;
|
||||
size_t next_cycle = cycle_len;
|
||||
for (size_t i = start_ix; i < end_ix; ++i) {
|
||||
if (total_tokens + i == next_restart) {
|
||||
JumpToByteBoundary(bw);
|
||||
EmitMarker(bw, 0xD0 + next_restart_marker);
|
||||
next_restart_marker += 1;
|
||||
next_restart_marker &= 0x7;
|
||||
next_restart = sti.restarts[++restart_idx];
|
||||
}
|
||||
Token t = tokens[i];
|
||||
const HuffmanCodeTable* code = &coding_tables[context_map[t.context]];
|
||||
WriteBits(bw, code->depth[t.symbol], code->code[t.symbol] | t.bits);
|
||||
if (--next_cycle == 0) {
|
||||
if (!EmptyBitWriterBuffer(bw)) {
|
||||
JPEGLI_ERROR(
|
||||
"Output suspension is not supported in "
|
||||
"finish_compress");
|
||||
}
|
||||
next_cycle = cycle_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
total_tokens += num_tokens;
|
||||
}
|
||||
}
|
||||
|
||||
void WriteACRefinementTokens(j_compress_ptr cinfo, int scan_index,
|
||||
JpegBitWriter* bw) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
const ScanTokenInfo& sti = m->scan_token_info[scan_index];
|
||||
const uint8_t context = m->ac_ctx_offset[scan_index];
|
||||
const HuffmanCodeTable* code = &m->coding_tables[m->context_map[context]];
|
||||
size_t cycle_len = bw->len / 64;
|
||||
size_t next_cycle = cycle_len;
|
||||
size_t refbit_idx = 0;
|
||||
size_t eobrun_idx = 0;
|
||||
size_t restart_idx = 0;
|
||||
size_t next_restart = sti.restarts[restart_idx];
|
||||
int next_restart_marker = 0;
|
||||
for (size_t i = 0; i < sti.num_tokens; ++i) {
|
||||
if (i == next_restart) {
|
||||
JumpToByteBoundary(bw);
|
||||
EmitMarker(bw, 0xD0 + next_restart_marker);
|
||||
next_restart_marker += 1;
|
||||
next_restart_marker &= 0x7;
|
||||
next_restart = sti.restarts[++restart_idx];
|
||||
}
|
||||
RefToken t = sti.tokens[i];
|
||||
int symbol = t.symbol & 253;
|
||||
uint16_t bits = 0;
|
||||
if ((symbol & 1) == 0) {
|
||||
int r = symbol >> 4;
|
||||
if (r > 0 && r < 15) {
|
||||
bits = sti.eobruns[eobrun_idx++];
|
||||
}
|
||||
} else {
|
||||
bits = (t.symbol >> 1) & 1;
|
||||
}
|
||||
WriteBits(bw, code->depth[symbol], code->code[symbol] | bits);
|
||||
for (int j = 0; j < t.refbits; ++j) {
|
||||
WriteBits(bw, 1, sti.refbits[refbit_idx++]);
|
||||
}
|
||||
if (--next_cycle == 0) {
|
||||
if (!EmptyBitWriterBuffer(bw)) {
|
||||
JPEGLI_ERROR("Output suspension is not supported in finish_compress");
|
||||
}
|
||||
next_cycle = cycle_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WriteDCRefinementBits(j_compress_ptr cinfo, int scan_index,
|
||||
JpegBitWriter* bw) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
const ScanTokenInfo& sti = m->scan_token_info[scan_index];
|
||||
size_t restart_idx = 0;
|
||||
size_t next_restart = sti.restarts[restart_idx];
|
||||
int next_restart_marker = 0;
|
||||
size_t cycle_len = bw->len * 4;
|
||||
size_t next_cycle = cycle_len;
|
||||
size_t refbit_idx = 0;
|
||||
for (size_t i = 0; i < sti.num_tokens; ++i) {
|
||||
if (i == next_restart) {
|
||||
JumpToByteBoundary(bw);
|
||||
EmitMarker(bw, 0xD0 + next_restart_marker);
|
||||
next_restart_marker += 1;
|
||||
next_restart_marker &= 0x7;
|
||||
next_restart = sti.restarts[++restart_idx];
|
||||
}
|
||||
WriteBits(bw, 1, sti.refbits[refbit_idx++]);
|
||||
if (--next_cycle == 0) {
|
||||
if (!EmptyBitWriterBuffer(bw)) {
|
||||
JPEGLI_ERROR(
|
||||
"Output suspension is not supported in "
|
||||
"finish_compress");
|
||||
}
|
||||
next_cycle = cycle_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void WriteScanData(j_compress_ptr cinfo, int scan_index) {
|
||||
const jpeg_scan_info* scan_info = &cinfo->scan_info[scan_index];
|
||||
JpegBitWriter* bw = &cinfo->master->bw;
|
||||
if (scan_info->Ah == 0) {
|
||||
WriteTokens(cinfo, scan_index, bw);
|
||||
} else if (scan_info->Ss > 0) {
|
||||
WriteACRefinementTokens(cinfo, scan_index, bw);
|
||||
} else {
|
||||
WriteDCRefinementBits(cinfo, scan_index, bw);
|
||||
}
|
||||
if (!bw->healthy) {
|
||||
JPEGLI_ERROR("Unknown Huffman coded symbol found in scan %d", scan_index);
|
||||
}
|
||||
JumpToByteBoundary(bw);
|
||||
if (!EmptyBitWriterBuffer(bw)) {
|
||||
JPEGLI_ERROR("Output suspension is not supported in finish_compress");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
@@ -1,48 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_BITSTREAM_H_
|
||||
#define LIB_JPEGLI_BITSTREAM_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/bit_writer.h"
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void WriteOutput(j_compress_ptr cinfo, const uint8_t* buf, size_t bufsize);
|
||||
void WriteOutput(j_compress_ptr cinfo, const std::vector<uint8_t>& bytes);
|
||||
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 WriteFileHeader(j_compress_ptr cinfo);
|
||||
|
||||
// Returns true of only baseline 8-bit tables are used.
|
||||
bool EncodeDQT(j_compress_ptr cinfo, bool write_all_tables);
|
||||
void EncodeSOF(j_compress_ptr cinfo, bool is_baseline);
|
||||
void WriteFrameHeader(j_compress_ptr cinfo);
|
||||
|
||||
void EncodeDRI(j_compress_ptr cinfo);
|
||||
void EncodeDHT(j_compress_ptr cinfo, size_t offset, size_t num);
|
||||
void EncodeSOS(j_compress_ptr cinfo, int scan_index);
|
||||
void WriteScanHeader(j_compress_ptr cinfo, int scan_index);
|
||||
|
||||
void WriteBlock(const int32_t* JXL_RESTRICT symbols,
|
||||
const int32_t* JXL_RESTRICT extra_bits, int num_nonzeros,
|
||||
bool emit_eob, const HuffmanCodeTable* JXL_RESTRICT dc_code,
|
||||
const HuffmanCodeTable* JXL_RESTRICT ac_code,
|
||||
JpegBitWriter* JXL_RESTRICT bw);
|
||||
void WriteScanData(j_compress_ptr cinfo, int scan_index);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_BITSTREAM_H_
|
||||
@@ -1,550 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/color_quantize.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/decode_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jpegli/memory_manager.h"
|
||||
#include "lib/jxl/base/common.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kNumColorCellBits[kMaxComponents] = {3, 4, 3, 3};
|
||||
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.
|
||||
const int kMaxPriority = 1 << 13;
|
||||
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>(std::lround(sqrt(d * 0.25)));
|
||||
}
|
||||
|
||||
// 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
|
||||
// appearance in the image. Return the number of unique colors found.
|
||||
// The colors are pre-quantized to 3 * 6 bits precision.
|
||||
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 =
|
||||
static_cast<size_t>(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;
|
||||
auto red_storage = jxl::make_uninitialized_vector<uint8_t>(max_color_count);
|
||||
auto green_storage = jxl::make_uninitialized_vector<uint8_t>(max_color_count);
|
||||
auto blue_storage = jxl::make_uninitialized_vector<uint8_t>(max_color_count);
|
||||
uint8_t* red = red_storage.data();
|
||||
uint8_t* green = green_storage.data();
|
||||
uint8_t* blue = blue_storage.data();
|
||||
std::vector<int> count(max_color_count, 0);
|
||||
// number of colors
|
||||
int n = BuildRGBColorIndex(m->pixels_, num_pixels, count.data(), red, green,
|
||||
blue);
|
||||
|
||||
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, green, blue, count.data(), i, k++, n, dist.data(),
|
||||
cluster.data(), ¢er[0], &error);
|
||||
in_palette[i] = true;
|
||||
}
|
||||
}
|
||||
if (k == 0) {
|
||||
AddToRGBPalette(red, green, blue, count.data(), winner, k++, n, dist.data(),
|
||||
cluster.data(), ¢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, green, blue, count.data(), i, k++, n, dist.data(),
|
||||
cluster.data(), ¢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, const 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;
|
||||
int 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;
|
||||
JPEGLI_CHECK(ncomp > 0);
|
||||
JPEGLI_CHECK(ncomp <= kMaxComponents);
|
||||
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, const 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];
|
||||
}
|
||||
JPEGLI_CHECK(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
JPEGLI_CHECK(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
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_COLOR_QUANTIZE_H_
|
||||
#define LIB_JPEGLI_COLOR_QUANTIZE_H_
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
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, const JSAMPLE* pixel);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_COLOR_QUANTIZE_H_
|
||||
@@ -1,554 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/color_transform.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
|
||||
#undef HWY_TARGET_INCLUDE
|
||||
#define HWY_TARGET_INCLUDE "lib/jpegli/color_transform.cc"
|
||||
#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 {
|
||||
|
||||
// These templates are not found via ADL.
|
||||
using hwy::HWY_NAMESPACE::Add;
|
||||
using hwy::HWY_NAMESPACE::Div;
|
||||
using hwy::HWY_NAMESPACE::Mul;
|
||||
using hwy::HWY_NAMESPACE::MulAdd;
|
||||
using hwy::HWY_NAMESPACE::Sub;
|
||||
|
||||
template <int kRed, int kGreen, int kBlue, int kAlpha>
|
||||
void YCbCrToExtRGB(float* row[kMaxComponents], size_t xsize) {
|
||||
const HWY_CAPPED(float, 8) df;
|
||||
const float* row_y = row[0];
|
||||
const float* row_cb = row[1];
|
||||
const float* row_cr = row[2];
|
||||
float* row_r = row[kRed];
|
||||
float* row_g = row[kGreen];
|
||||
float* row_b = row[kBlue];
|
||||
float* row_a = row[kAlpha];
|
||||
|
||||
// Full-range BT.601 as defined by JFIF Clause 7:
|
||||
// https://www.itu.int/rec/T-REC-T.871-201105-I/en
|
||||
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 alpha_opaque = Set(df, 127.0f / 255.0f);
|
||||
|
||||
for (size_t x = 0; x < xsize; x += Lanes(df)) {
|
||||
const auto y_vec = Load(df, row_y + x);
|
||||
const auto cb_vec = Load(df, row_cb + x);
|
||||
const auto cr_vec = Load(df, row_cr + x);
|
||||
const auto r_vec = MulAdd(crcr, cr_vec, y_vec);
|
||||
const auto g_vec = MulAdd(cgcr, cr_vec, MulAdd(cgcb, cb_vec, y_vec));
|
||||
const auto b_vec = MulAdd(cbcb, cb_vec, y_vec);
|
||||
Store(r_vec, df, row_r + x);
|
||||
Store(g_vec, df, row_g + x);
|
||||
Store(b_vec, df, row_b + x);
|
||||
if (kAlpha >= 0) {
|
||||
Store(alpha_opaque, df, row_a + x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void YCbCrToRGB(float* row[kMaxComponents], size_t xsize) {
|
||||
YCbCrToExtRGB<0, 1, 2, -1>(row, xsize);
|
||||
}
|
||||
|
||||
void YCbCrToBGR(float* row[kMaxComponents], size_t xsize) {
|
||||
YCbCrToExtRGB<2, 1, 0, -1>(row, xsize);
|
||||
}
|
||||
|
||||
void YCbCrToRGBA(float* row[kMaxComponents], size_t xsize) {
|
||||
YCbCrToExtRGB<0, 1, 2, 3>(row, xsize);
|
||||
}
|
||||
|
||||
void YCbCrToBGRA(float* row[kMaxComponents], size_t xsize) {
|
||||
YCbCrToExtRGB<2, 1, 0, 3>(row, xsize);
|
||||
}
|
||||
|
||||
void YCbCrToARGB(float* row[kMaxComponents], size_t xsize) {
|
||||
YCbCrToExtRGB<1, 2, 3, 0>(row, xsize);
|
||||
}
|
||||
|
||||
void YCbCrToABGR(float* row[kMaxComponents], size_t xsize) {
|
||||
YCbCrToExtRGB<3, 2, 1, 0>(row, 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);
|
||||
}
|
||||
}
|
||||
|
||||
template <int kRed, int kGreen, int kBlue>
|
||||
void ExtRGBToYCbCr(float* row[kMaxComponents], size_t xsize) {
|
||||
const HWY_CAPPED(float, 8) df;
|
||||
const float* row_r = row[kRed];
|
||||
const float* row_g = row[kGreen];
|
||||
const float* row_b = row[kBlue];
|
||||
float* row_y = row[0];
|
||||
float* row_cb = row[1];
|
||||
float* row_cr = 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);
|
||||
const auto kR = Set(df, 0.299f); // NTSC luma
|
||||
const auto kG = Set(df, 0.587f);
|
||||
const auto kB = Set(df, 0.114f);
|
||||
const auto kAmpR = Set(df, 0.701f);
|
||||
const auto kAmpB = Set(df, 0.886f);
|
||||
const auto kDiffR = Add(kAmpR, kR);
|
||||
const auto kDiffB = Add(kAmpB, kB);
|
||||
const auto kNormR = Div(Set(df, 1.0f), (Add(kAmpR, Add(kG, kB))));
|
||||
const auto kNormB = Div(Set(df, 1.0f), (Add(kR, Add(kG, kAmpB))));
|
||||
|
||||
for (size_t x = 0; x < xsize; x += Lanes(df)) {
|
||||
const auto r = Load(df, row_r + x);
|
||||
const auto g = Load(df, row_g + x);
|
||||
const auto b = Load(df, row_b + x);
|
||||
const auto r_base = Mul(r, kR);
|
||||
const auto r_diff = Mul(r, kDiffR);
|
||||
const auto g_base = Mul(g, kG);
|
||||
const auto b_base = Mul(b, kB);
|
||||
const auto b_diff = Mul(b, kDiffB);
|
||||
const auto y_base = Add(r_base, Add(g_base, b_base));
|
||||
const auto cb_vec = MulAdd(Sub(b_diff, y_base), kNormB, c128);
|
||||
const auto cr_vec = MulAdd(Sub(r_diff, y_base), kNormR, c128);
|
||||
Store(y_base, df, row_y + x);
|
||||
Store(cb_vec, df, row_cb + x);
|
||||
Store(cr_vec, df, row_cr + x);
|
||||
}
|
||||
}
|
||||
|
||||
void RGBToYCbCr(float* row[kMaxComponents], size_t xsize) {
|
||||
ExtRGBToYCbCr<0, 1, 2>(row, xsize);
|
||||
}
|
||||
|
||||
void BGRToYCbCr(float* row[kMaxComponents], size_t xsize) {
|
||||
ExtRGBToYCbCr<2, 1, 0>(row, xsize);
|
||||
}
|
||||
|
||||
void ARGBToYCbCr(float* row[kMaxComponents], size_t xsize) {
|
||||
ExtRGBToYCbCr<1, 2, 3>(row, xsize);
|
||||
}
|
||||
|
||||
void ABGRToYCbCr(float* row[kMaxComponents], size_t xsize) {
|
||||
ExtRGBToYCbCr<3, 2, 1>(row, xsize);
|
||||
}
|
||||
|
||||
void CMYKToYCCK(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];
|
||||
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(row, xsize);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
|
||||
#if HWY_ONCE
|
||||
namespace jpegli {
|
||||
|
||||
HWY_EXPORT(CMYKToYCCK);
|
||||
HWY_EXPORT(YCCKToCMYK);
|
||||
HWY_EXPORT(YCbCrToRGB);
|
||||
HWY_EXPORT(YCbCrToBGR);
|
||||
HWY_EXPORT(YCbCrToRGBA);
|
||||
HWY_EXPORT(YCbCrToBGRA);
|
||||
HWY_EXPORT(YCbCrToARGB);
|
||||
HWY_EXPORT(YCbCrToABGR);
|
||||
HWY_EXPORT(RGBToYCbCr);
|
||||
HWY_EXPORT(BGRToYCbCr);
|
||||
HWY_EXPORT(ARGBToYCbCr);
|
||||
HWY_EXPORT(ABGRToYCbCr);
|
||||
|
||||
bool CheckColorSpaceComponents(int num_components, J_COLOR_SPACE colorspace) {
|
||||
switch (colorspace) {
|
||||
case JCS_GRAYSCALE:
|
||||
return num_components == 1;
|
||||
case JCS_RGB:
|
||||
case JCS_YCbCr:
|
||||
#ifdef JCS_EXTENSIONS
|
||||
case JCS_EXT_RGB:
|
||||
case JCS_EXT_BGR:
|
||||
#endif
|
||||
return num_components == 3;
|
||||
case JCS_CMYK:
|
||||
case JCS_YCCK:
|
||||
#ifdef JCS_EXTENSIONS
|
||||
case JCS_EXT_RGBX:
|
||||
case JCS_EXT_BGRX:
|
||||
case JCS_EXT_XBGR:
|
||||
case JCS_EXT_XRGB:
|
||||
#endif
|
||||
#ifdef JCS_ALPHA_EXTENSIONS
|
||||
case JCS_EXT_RGBA:
|
||||
case JCS_EXT_BGRA:
|
||||
case JCS_EXT_ABGR:
|
||||
case JCS_EXT_ARGB:
|
||||
#endif
|
||||
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 NullTransform(float* row[kMaxComponents], size_t len) {}
|
||||
|
||||
void FillAlpha(float* row, size_t len) {
|
||||
static const float kAlpha = 127.0f / 255.0f;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
row[i] = kAlpha;
|
||||
}
|
||||
}
|
||||
|
||||
// Works for BGR as well.
|
||||
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]));
|
||||
}
|
||||
|
||||
// Works for BGRA as well.
|
||||
void GrayscaleToRGBA(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]));
|
||||
FillAlpha(row[3], len);
|
||||
}
|
||||
|
||||
// Works for ABGR as well.
|
||||
void GrayscaleToARGB(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]));
|
||||
memcpy(row[3], row[0], len * sizeof(row[1][0]));
|
||||
FillAlpha(row[0], len);
|
||||
}
|
||||
|
||||
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 RGBToBGR(float* row[kMaxComponents], size_t len) {
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
std::swap(row[0][i], row[2][i]);
|
||||
}
|
||||
}
|
||||
|
||||
void RGBToRGBA(float* row[kMaxComponents], size_t len) {
|
||||
FillAlpha(row[3], len);
|
||||
}
|
||||
|
||||
void RGBToBGRA(float* row[kMaxComponents], size_t len) {
|
||||
static const float kAlpha = 127.0f / 255.0f;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
std::swap(row[0][i], row[2][i]);
|
||||
row[3][i] = kAlpha;
|
||||
}
|
||||
}
|
||||
|
||||
void RGBToARGB(float* row[kMaxComponents], size_t len) {
|
||||
memcpy(row[3], row[2], len * sizeof(row[1][0]));
|
||||
memcpy(row[2], row[1], len * sizeof(row[2][0]));
|
||||
memcpy(row[1], row[0], len * sizeof(row[1][0]));
|
||||
FillAlpha(row[0], len);
|
||||
}
|
||||
|
||||
void RGBToABGR(float* row[kMaxComponents], size_t len) {
|
||||
static const float kAlpha = 127.0f / 255.0f;
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
std::swap(row[1][i], row[2][i]);
|
||||
row[3][i] = row[0][i];
|
||||
row[0][i] = kAlpha;
|
||||
}
|
||||
}
|
||||
|
||||
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 (cinfo->jpeg_color_space == JCS_GRAYSCALE ||
|
||||
cinfo->jpeg_color_space == JCS_YCbCr) {
|
||||
switch (cinfo->in_color_space) {
|
||||
#ifdef JCS_EXTENSIONS
|
||||
case JCS_EXT_RGB:
|
||||
case JCS_EXT_RGBX:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr);
|
||||
break;
|
||||
case JCS_EXT_BGR:
|
||||
case JCS_EXT_BGRX:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(BGRToYCbCr);
|
||||
break;
|
||||
case JCS_EXT_XRGB:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(ARGBToYCbCr);
|
||||
break;
|
||||
case JCS_EXT_XBGR:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(ABGRToYCbCr);
|
||||
break;
|
||||
#endif
|
||||
#ifdef JCS_ALPHA_EXTENSIONS
|
||||
case JCS_EXT_RGBA:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr);
|
||||
break;
|
||||
case JCS_EXT_BGRA:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(BGRToYCbCr);
|
||||
break;
|
||||
case JCS_EXT_ARGB:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(ARGBToYCbCr);
|
||||
break;
|
||||
case JCS_EXT_ABGR:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(ABGRToYCbCr);
|
||||
break;
|
||||
#endif
|
||||
default:; // Nothing to do.
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
switch (cinfo->out_color_space) {
|
||||
case JCS_RGB:
|
||||
m->color_transform = GrayscaleToRGB;
|
||||
break;
|
||||
#ifdef JCS_EXTENSIONS
|
||||
case JCS_EXT_RGB:
|
||||
case JCS_EXT_BGR:
|
||||
m->color_transform = GrayscaleToRGB;
|
||||
break;
|
||||
case JCS_EXT_RGBX:
|
||||
case JCS_EXT_BGRX:
|
||||
m->color_transform = GrayscaleToRGBA;
|
||||
break;
|
||||
case JCS_EXT_XRGB:
|
||||
case JCS_EXT_XBGR:
|
||||
m->color_transform = GrayscaleToARGB;
|
||||
break;
|
||||
#endif
|
||||
#ifdef JCS_ALPHA_EXTENSIONS
|
||||
case JCS_EXT_RGBA:
|
||||
case JCS_EXT_BGRA:
|
||||
m->color_transform = GrayscaleToRGBA;
|
||||
break;
|
||||
case JCS_EXT_ARGB:
|
||||
case JCS_EXT_ABGR:
|
||||
m->color_transform = GrayscaleToARGB;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
m->color_transform = nullptr;
|
||||
}
|
||||
} else if (cinfo->jpeg_color_space == JCS_RGB) {
|
||||
switch (cinfo->out_color_space) {
|
||||
case JCS_GRAYSCALE:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(RGBToYCbCr);
|
||||
break;
|
||||
#ifdef JCS_EXTENSIONS
|
||||
case JCS_EXT_RGB:
|
||||
m->color_transform = NullTransform;
|
||||
break;
|
||||
case JCS_EXT_BGR:
|
||||
m->color_transform = RGBToBGR;
|
||||
break;
|
||||
case JCS_EXT_RGBX:
|
||||
m->color_transform = RGBToRGBA;
|
||||
break;
|
||||
case JCS_EXT_BGRX:
|
||||
m->color_transform = RGBToBGRA;
|
||||
break;
|
||||
case JCS_EXT_XRGB:
|
||||
m->color_transform = RGBToARGB;
|
||||
break;
|
||||
case JCS_EXT_XBGR:
|
||||
m->color_transform = RGBToABGR;
|
||||
break;
|
||||
#endif
|
||||
#ifdef JCS_ALPHA_EXTENSIONS
|
||||
case JCS_EXT_RGBA:
|
||||
m->color_transform = RGBToRGBA;
|
||||
break;
|
||||
case JCS_EXT_BGRA:
|
||||
m->color_transform = RGBToBGRA;
|
||||
break;
|
||||
case JCS_EXT_ARGB:
|
||||
m->color_transform = RGBToARGB;
|
||||
break;
|
||||
case JCS_EXT_ABGR:
|
||||
m->color_transform = RGBToABGR;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
m->color_transform = nullptr;
|
||||
}
|
||||
} else if (cinfo->jpeg_color_space == JCS_YCbCr) {
|
||||
switch (cinfo->out_color_space) {
|
||||
case JCS_GRAYSCALE:
|
||||
m->color_transform = NullTransform;
|
||||
break;
|
||||
case JCS_RGB:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGB);
|
||||
break;
|
||||
#ifdef JCS_EXTENSIONS
|
||||
case JCS_EXT_RGB:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGB);
|
||||
break;
|
||||
case JCS_EXT_BGR:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToBGR);
|
||||
break;
|
||||
case JCS_EXT_RGBX:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGBA);
|
||||
break;
|
||||
case JCS_EXT_BGRX:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToBGRA);
|
||||
break;
|
||||
case JCS_EXT_XRGB:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToARGB);
|
||||
break;
|
||||
case JCS_EXT_XBGR:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToABGR);
|
||||
break;
|
||||
#endif
|
||||
#ifdef JCS_ALPHA_EXTENSIONS
|
||||
case JCS_EXT_RGBA:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToRGBA);
|
||||
break;
|
||||
case JCS_EXT_BGRA:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToBGRA);
|
||||
break;
|
||||
case JCS_EXT_ARGB:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToARGB);
|
||||
break;
|
||||
case JCS_EXT_ABGR:
|
||||
m->color_transform = HWY_DYNAMIC_DISPATCH(YCbCrToABGR);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
m->color_transform = nullptr;
|
||||
}
|
||||
} 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
|
||||
#endif // HWY_ONCE
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_COLOR_TRANSFORM_H_
|
||||
#define LIB_JPEGLI_COLOR_TRANSFORM_H_
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void ChooseColorTransform(j_compress_ptr cinfo);
|
||||
|
||||
void ChooseColorTransform(j_decompress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_COLOR_TRANSFORM_H_
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/decode_internal.h"
|
||||
#include "lib/jpegli/memory_manager.h"
|
||||
#include "lib/jpegli/types.h"
|
||||
|
||||
void jpegli_abort(j_common_ptr cinfo) {
|
||||
if (cinfo->mem == nullptr) return;
|
||||
for (int pool_id = 0; pool_id < JPOOL_NUMPOOLS; ++pool_id) {
|
||||
if (pool_id == JPOOL_PERMANENT) continue;
|
||||
(*cinfo->mem->free_pool)(cinfo, pool_id);
|
||||
}
|
||||
if (cinfo->is_decompressor) {
|
||||
cinfo->global_state = jpegli::kDecStart;
|
||||
} else {
|
||||
cinfo->global_state = jpegli::kEncStart;
|
||||
}
|
||||
}
|
||||
|
||||
void jpegli_destroy(j_common_ptr cinfo) {
|
||||
if (cinfo->mem == nullptr) return;
|
||||
(*cinfo->mem->self_destruct)(cinfo);
|
||||
if (cinfo->is_decompressor) {
|
||||
cinfo->global_state = jpegli::kDecNull;
|
||||
jpeg_decomp_master* master =
|
||||
reinterpret_cast<j_decompress_ptr>(cinfo)->master;
|
||||
delete master;
|
||||
} else {
|
||||
cinfo->global_state = jpegli::kEncNull;
|
||||
}
|
||||
}
|
||||
|
||||
JQUANT_TBL* jpegli_alloc_quant_table(j_common_ptr cinfo) {
|
||||
JQUANT_TBL* table = jpegli::Allocate<JQUANT_TBL>(cinfo, 1);
|
||||
table->sent_table = FALSE;
|
||||
return table;
|
||||
}
|
||||
|
||||
JHUFF_TBL* jpegli_alloc_huff_table(j_common_ptr cinfo) {
|
||||
JHUFF_TBL* table = jpegli::Allocate<JHUFF_TBL>(cinfo, 1);
|
||||
table->sent_table = FALSE;
|
||||
return table;
|
||||
}
|
||||
|
||||
int jpegli_bytes_per_sample(JpegliDataType data_type) {
|
||||
switch (data_type) {
|
||||
case JPEGLI_TYPE_UINT8:
|
||||
return 1;
|
||||
case JPEGLI_TYPE_UINT16:
|
||||
return 2;
|
||||
case JPEGLI_TYPE_FLOAT:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
// This file contains the C API of the common encoder/decoder part of libjpegli
|
||||
// library, which is based on the C API of libjpeg, with the function names
|
||||
// changed from jpeg_* to jpegli_*, while compressor and decompressor object
|
||||
// definitions are included directly from jpeglib.h
|
||||
//
|
||||
// Applications can use the libjpegli library in one of the following ways:
|
||||
//
|
||||
// (1) Include jpegli/encode.h and/or jpegli/decode.h, update the function
|
||||
// names of the API and link against libjpegli.
|
||||
//
|
||||
// (2) Leave the application code unchanged, but replace the libjpeg.so library
|
||||
// with the one built by this project that is API- and ABI-compatible with
|
||||
// libjpeg-turbo's version of libjpeg.so.
|
||||
|
||||
#ifndef LIB_JPEGLI_COMMON_H_
|
||||
#define LIB_JPEGLI_COMMON_H_
|
||||
|
||||
#include "lib/jxl/base/include_jpeglib.h" // IWYU pragma: export
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct jpeg_error_mgr* jpegli_std_error(struct jpeg_error_mgr* err);
|
||||
|
||||
void jpegli_abort(j_common_ptr cinfo);
|
||||
|
||||
void jpegli_destroy(j_common_ptr cinfo);
|
||||
|
||||
JQUANT_TBL* jpegli_alloc_quant_table(j_common_ptr cinfo);
|
||||
|
||||
JHUFF_TBL* jpegli_alloc_huff_table(j_common_ptr cinfo);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // LIB_JPEGLI_COMMON_H_
|
||||
@@ -1,147 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_COMMON_INTERNAL_H_
|
||||
#define LIB_JPEGLI_COMMON_INTERNAL_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <hwy/aligned_allocator.h>
|
||||
|
||||
#include "lib/jpegli/memory_manager.h"
|
||||
#include "lib/jpegli/simd.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
enum State {
|
||||
kDecNull,
|
||||
kDecStart,
|
||||
kDecInHeader,
|
||||
kDecHeaderDone,
|
||||
kDecProcessMarkers,
|
||||
kDecProcessScan,
|
||||
kEncNull,
|
||||
kEncStart,
|
||||
kEncHeader,
|
||||
kEncReadImage,
|
||||
kEncWriteCoeffs,
|
||||
};
|
||||
|
||||
template <typename T1, typename T2>
|
||||
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.
|
||||
constexpr int kMaxComponents = 4;
|
||||
constexpr int kMaxQuantTables = 4;
|
||||
constexpr int kJpegPrecision = 8;
|
||||
constexpr int kMaxHuffmanTables = 4;
|
||||
constexpr size_t kJpegHuffmanMaxBitLength = 16;
|
||||
constexpr int kJpegHuffmanAlphabetSize = 256;
|
||||
constexpr int kJpegDCAlphabetSize = 12;
|
||||
constexpr int kMaxDHTMarkers = 512;
|
||||
constexpr int kMaxDimPixels = 65535;
|
||||
constexpr uint8_t kApp1 = 0xE1;
|
||||
constexpr uint8_t kApp2 = 0xE2;
|
||||
const uint8_t kIccProfileTag[12] = "ICC_PROFILE";
|
||||
const uint8_t kExifTag[6] = "Exif\0";
|
||||
const uint8_t kXMPTag[29] = "http://ns.adobe.com/xap/1.0/";
|
||||
|
||||
/* clang-format off */
|
||||
constexpr uint32_t kJPEGNaturalOrder[80] = {
|
||||
0, 1, 8, 16, 9, 2, 3, 10,
|
||||
17, 24, 32, 25, 18, 11, 4, 5,
|
||||
12, 19, 26, 33, 40, 48, 41, 34,
|
||||
27, 20, 13, 6, 7, 14, 21, 28,
|
||||
35, 42, 49, 56, 57, 50, 43, 36,
|
||||
29, 22, 15, 23, 30, 37, 44, 51,
|
||||
58, 59, 52, 45, 38, 31, 39, 46,
|
||||
53, 60, 61, 54, 47, 55, 62, 63,
|
||||
// extra entries for safety in decoder
|
||||
63, 63, 63, 63, 63, 63, 63, 63,
|
||||
63, 63, 63, 63, 63, 63, 63, 63
|
||||
};
|
||||
|
||||
constexpr uint32_t kJPEGZigZagOrder[64] = {
|
||||
0, 1, 5, 6, 14, 15, 27, 28,
|
||||
2, 4, 7, 13, 16, 26, 29, 42,
|
||||
3, 8, 12, 17, 25, 30, 41, 43,
|
||||
9, 11, 18, 24, 31, 40, 44, 53,
|
||||
10, 19, 23, 32, 39, 45, 52, 54,
|
||||
20, 22, 33, 38, 46, 51, 55, 60,
|
||||
21, 34, 37, 47, 50, 56, 59, 61,
|
||||
35, 36, 48, 49, 57, 58, 62, 63
|
||||
};
|
||||
/* clang-format on */
|
||||
|
||||
template <typename T>
|
||||
class RowBuffer {
|
||||
public:
|
||||
template <typename CInfoType>
|
||||
void Allocate(CInfoType cinfo, size_t num_rows, size_t rowsize) {
|
||||
static_assert(sizeof(T) == 4, "4-byte T is assumed");
|
||||
size_t vec_size = std::max(VectorSize(), sizeof(T));
|
||||
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_ = memstride / sizeof(T);
|
||||
offset_ = alignment / sizeof(T);
|
||||
data_ = ::jpegli::Allocate<T>(cinfo, ysize_ * stride_, JPOOL_IMAGE_ALIGNED);
|
||||
}
|
||||
|
||||
T* Row(ptrdiff_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_; }
|
||||
|
||||
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(ptrdiff_t dst_row, ptrdiff_t src_row, int border) {
|
||||
memcpy(Row(dst_row) - border, Row(src_row) - border,
|
||||
(xsize_ + 2 * border) * sizeof(T));
|
||||
}
|
||||
|
||||
void FillRow(ptrdiff_t y, T val, size_t len) {
|
||||
T* row = Row(y);
|
||||
for (size_t x = 0; x < len; ++x) {
|
||||
row[x] = val;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
size_t xsize_;
|
||||
size_t ysize_;
|
||||
size_t stride_;
|
||||
size_t offset_;
|
||||
T* data_;
|
||||
};
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_COMMON_INTERNAL_H_
|
||||
@@ -1,267 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
#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 <hwy/highway.h>
|
||||
|
||||
#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::Half;
|
||||
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;
|
||||
|
||||
template <size_t N>
|
||||
void AddReverse(const float* JXL_RESTRICT a_in1,
|
||||
const float* JXL_RESTRICT a_in2, float* JXL_RESTRICT a_out) {
|
||||
HWY_CAPPED(float, 8) d8;
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
auto in1 = Load(d8, a_in1 + i * 8);
|
||||
auto in2 = Load(d8, a_in2 + (N - i - 1) * 8);
|
||||
Store(Add(in1, in2), d8, a_out + i * 8);
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
void SubReverse(const float* JXL_RESTRICT a_in1,
|
||||
const float* JXL_RESTRICT a_in2, float* JXL_RESTRICT a_out) {
|
||||
HWY_CAPPED(float, 8) d8;
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
auto in1 = Load(d8, a_in1 + i * 8);
|
||||
auto in2 = Load(d8, a_in2 + (N - i - 1) * 8);
|
||||
Store(Sub(in1, in2), d8, a_out + 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_0 = Load(d8, coeff);
|
||||
auto in2_0 = Load(d8, coeff + 8);
|
||||
Store(MulAdd(in1_0, sqrt2, in2_0), 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 a_in, float* JXL_RESTRICT a_out) {
|
||||
HWY_CAPPED(float, 8) d8;
|
||||
for (size_t i = 0; i < N / 2; i++) {
|
||||
auto in1 = Load(d8, a_in + i * 8);
|
||||
Store(in1, d8, a_out + 2 * i * 8);
|
||||
}
|
||||
for (size_t i = N / 2; i < N; i++) {
|
||||
auto in1 = Load(d8, a_in + i * 8);
|
||||
Store(in1, d8, a_out + (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,
|
||||
};
|
||||
};
|
||||
|
||||
#if JXL_CXX_LANG < JXL_CXX_17
|
||||
constexpr float WcMultipliers<4>::kMultipliers[];
|
||||
constexpr float WcMultipliers<8>::kMultipliers[];
|
||||
#endif
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
JXL_INLINE JXL_MAYBE_UNUSED 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);
|
||||
}
|
||||
|
||||
JXL_INLINE JXL_MAYBE_UNUSED void StoreQuantizedValue(
|
||||
const Vec<HWY_FULL(int32_t)>& ival, int16_t* out) {
|
||||
Half<HWY_FULL(int16_t)> di16;
|
||||
Store(DemoteTo(di16, ival), di16, out);
|
||||
}
|
||||
|
||||
JXL_INLINE JXL_MAYBE_UNUSED void StoreQuantizedValue(
|
||||
const Vec<HWY_FULL(int32_t)>& ival, int32_t* out) {
|
||||
const HWY_FULL(int32_t) 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) {
|
||||
const HWY_FULL(float) d;
|
||||
const HWY_FULL(int32_t) 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 ComputeCoefficientBlock(const float* JXL_RESTRICT pixels, size_t stride,
|
||||
const float* JXL_RESTRICT qmc,
|
||||
int16_t last_dc_coeff, 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);
|
||||
QuantizeBlock(dct, qmc, aq_strength, zero_bias_offset, zero_bias_mul, block);
|
||||
// Center DC values around zero.
|
||||
static constexpr float kDCBias = 128.0f;
|
||||
const float dc = (dct[0] - kDCBias) * qmc[0];
|
||||
float dc_threshold = zero_bias_offset[0] + aq_strength * zero_bias_mul[0];
|
||||
if (std::abs(dc - last_dc_coeff) < dc_threshold) {
|
||||
block[0] = last_dc_coeff;
|
||||
} else {
|
||||
block[0] = std::round(dc);
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
#endif // LIB_JPEGLI_DCT_INL_H_
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,110 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
// This file contains the C API of the decoder part of the libjpegli library,
|
||||
// which is based on the C API of libjpeg, with the function names changed from
|
||||
// jpeg_* to jpegli_*, while decompressor object definitions are included
|
||||
// directly from jpeglib.h
|
||||
//
|
||||
// Applications can use the libjpegli library in one of the following ways:
|
||||
//
|
||||
// (1) Include jpegli/encode.h and/or jpegli/decode.h, update the function
|
||||
// names of the API and link against libjpegli.
|
||||
//
|
||||
// (2) Leave the application code unchanged, but replace the libjpeg.so library
|
||||
// with the one built by this project that is API- and ABI-compatible with
|
||||
// libjpeg-turbo's version of libjpeg.so.
|
||||
|
||||
#ifndef LIB_JPEGLI_DECODE_H_
|
||||
#define LIB_JPEGLI_DECODE_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define jpegli_create_decompress(cinfo) \
|
||||
jpegli_CreateDecompress((cinfo), JPEG_LIB_VERSION, \
|
||||
(size_t)sizeof(struct jpeg_decompress_struct))
|
||||
|
||||
void jpegli_CreateDecompress(j_decompress_ptr cinfo, int version,
|
||||
size_t structsize);
|
||||
|
||||
void jpegli_stdio_src(j_decompress_ptr cinfo, FILE *infile);
|
||||
|
||||
void jpegli_mem_src(j_decompress_ptr cinfo, const unsigned char *inbuffer,
|
||||
unsigned long insize /* NOLINT */);
|
||||
|
||||
int jpegli_read_header(j_decompress_ptr cinfo, boolean require_image);
|
||||
|
||||
boolean jpegli_start_decompress(j_decompress_ptr cinfo);
|
||||
|
||||
JDIMENSION jpegli_read_scanlines(j_decompress_ptr cinfo, JSAMPARRAY scanlines,
|
||||
JDIMENSION max_lines);
|
||||
|
||||
JDIMENSION jpegli_skip_scanlines(j_decompress_ptr cinfo, JDIMENSION num_lines);
|
||||
|
||||
void jpegli_crop_scanline(j_decompress_ptr cinfo, JDIMENSION *xoffset,
|
||||
JDIMENSION *width);
|
||||
|
||||
boolean jpegli_finish_decompress(j_decompress_ptr cinfo);
|
||||
|
||||
JDIMENSION jpegli_read_raw_data(j_decompress_ptr cinfo, JSAMPIMAGE data,
|
||||
JDIMENSION max_lines);
|
||||
|
||||
jvirt_barray_ptr *jpegli_read_coefficients(j_decompress_ptr cinfo);
|
||||
|
||||
boolean jpegli_has_multiple_scans(j_decompress_ptr cinfo);
|
||||
|
||||
boolean jpegli_start_output(j_decompress_ptr cinfo, int scan_number);
|
||||
|
||||
boolean jpegli_finish_output(j_decompress_ptr cinfo);
|
||||
|
||||
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,
|
||||
unsigned int length_limit);
|
||||
|
||||
void jpegli_set_marker_processor(j_decompress_ptr cinfo, int marker_code,
|
||||
jpeg_marker_parser_method routine);
|
||||
|
||||
boolean jpegli_resync_to_restart(j_decompress_ptr cinfo, int desired);
|
||||
|
||||
boolean jpegli_read_icc_profile(j_decompress_ptr cinfo, JOCTET **icc_data_ptr,
|
||||
unsigned int *icc_data_len);
|
||||
|
||||
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
|
||||
//
|
||||
// NOTE: This part of the API is still experimental and will probably change in
|
||||
// the future.
|
||||
//
|
||||
|
||||
void jpegli_set_output_format(j_decompress_ptr cinfo, JpegliDataType data_type,
|
||||
JpegliEndianness endianness);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // LIB_JPEGLI_DECODE_H_
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,156 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_DECODE_INTERNAL_H_
|
||||
#define LIB_JPEGLI_DECODE_INTERNAL_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/huffman.h"
|
||||
#include "lib/jpegli/types.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
coeff_t coeffs[D_MAX_BLOCKS_IN_MCU * DCTSIZE2];
|
||||
};
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
// Use this forward-declared libjpeg struct to hold all our private variables.
|
||||
// TODO(szabadka) Remove variables that have a corresponding version in cinfo.
|
||||
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_;
|
||||
|
||||
// Coefficient buffers
|
||||
jvirt_barray_ptr* coef_arrays;
|
||||
JBLOCKARRAY coeff_rows[jpegli::kMaxComponents];
|
||||
|
||||
bool streaming_mode_;
|
||||
|
||||
//
|
||||
// Marker data processing state.
|
||||
//
|
||||
bool found_soi_;
|
||||
bool found_dri_;
|
||||
bool found_sof_;
|
||||
bool found_sos_;
|
||||
bool found_eoi_;
|
||||
|
||||
// Whether this jpeg has multiple scans (progressive or non-interleaved
|
||||
// sequential).
|
||||
bool is_multiscan_;
|
||||
|
||||
size_t icc_index_;
|
||||
size_t icc_total_;
|
||||
std::vector<uint8_t> icc_profile_;
|
||||
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;
|
||||
|
||||
// Fields defined by SOF marker.
|
||||
size_t iMCU_cols_;
|
||||
int h_factor[jpegli::kMaxComponents];
|
||||
int v_factor[jpegli::kMaxComponents];
|
||||
|
||||
// Initialized at start of frame.
|
||||
uint16_t scan_progression_[jpegli::kMaxComponents][DCTSIZE2];
|
||||
|
||||
//
|
||||
// Per scan state.
|
||||
//
|
||||
size_t scan_mcu_row_;
|
||||
size_t scan_mcu_col_;
|
||||
size_t mcu_rows_per_iMCU_row_;
|
||||
jpegli::coeff_t last_dc_coeff_[jpegli::kMaxComponents];
|
||||
int eobrun_;
|
||||
int restarts_to_go_;
|
||||
int next_restart_marker_;
|
||||
|
||||
jpegli::MCUCodingState mcu_;
|
||||
|
||||
//
|
||||
// Rendering state.
|
||||
//
|
||||
int output_passes_done_;
|
||||
JpegliDataType output_data_type_ = JPEGLI_TYPE_UINT8;
|
||||
size_t xoffset_;
|
||||
bool swap_endianness_ = false;
|
||||
bool need_context_rows_;
|
||||
bool regenerate_inverse_colormap_;
|
||||
bool apply_smoothing;
|
||||
|
||||
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_;
|
||||
std::vector<std::vector<uint8_t>> candidate_lists_;
|
||||
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.
|
||||
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];
|
||||
};
|
||||
|
||||
#endif // LIB_JPEGLI_DECODE_INTERNAL_H_
|
||||
@@ -1,588 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/decode_marker.h"
|
||||
|
||||
#include <jxl/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/decode_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jpegli/memory_manager.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
|
||||
namespace jpegli {
|
||||
namespace {
|
||||
|
||||
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) { \
|
||||
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) \
|
||||
if ((var) < (low) || (var) > (high)) { \
|
||||
JPEGLI_ERROR("Invalid " #var ": %d", static_cast<int>(var)); \
|
||||
}
|
||||
|
||||
#define JPEG_VERIFY_MARKER_END() \
|
||||
if (pos != len) { \
|
||||
JPEGLI_ERROR("Invalid marker length: declared=%" PRIuS " actual=%" PRIuS, \
|
||||
len, pos); \
|
||||
}
|
||||
|
||||
inline int ReadUint8(const uint8_t* data, size_t* pos) {
|
||||
return data[(*pos)++];
|
||||
}
|
||||
|
||||
inline int ReadUint16(const uint8_t* data, size_t* pos) {
|
||||
int v = (data[*pos] << 8) + data[*pos + 1];
|
||||
*pos += 2;
|
||||
return v;
|
||||
}
|
||||
|
||||
void ProcessSOF(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (!m->found_soi_) {
|
||||
JPEGLI_ERROR("Unexpected SOF marker.");
|
||||
}
|
||||
if (m->found_sof_) {
|
||||
JPEGLI_ERROR("Duplicate SOF marker.");
|
||||
}
|
||||
m->found_sof_ = true;
|
||||
cinfo->progressive_mode = TO_JXL_BOOL(cinfo->unread_marker == 0xc2);
|
||||
cinfo->arith_code = 0;
|
||||
size_t pos = 2;
|
||||
JPEG_VERIFY_LEN(6);
|
||||
cinfo->data_precision = ReadUint8(data, &pos);
|
||||
cinfo->image_height = ReadUint16(data, &pos);
|
||||
cinfo->image_width = ReadUint16(data, &pos);
|
||||
cinfo->num_components = ReadUint8(data, &pos);
|
||||
JPEG_VERIFY_INPUT(cinfo->data_precision, kJpegPrecision, kJpegPrecision);
|
||||
JPEG_VERIFY_INPUT(cinfo->image_height, 1, kMaxDimPixels);
|
||||
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, JPOOL_IMAGE);
|
||||
|
||||
// Read sampling factors and quant table index for each component.
|
||||
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) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[i];
|
||||
comp->component_index = i;
|
||||
const int id = ReadUint8(data, &pos);
|
||||
if (ids_seen[id]) { // (cf. section B.2.2, syntax of Ci)
|
||||
JPEGLI_ERROR("Duplicate ID %d in SOF.", id);
|
||||
}
|
||||
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, 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);
|
||||
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 = nullptr; // will be allocated after SOS marker
|
||||
}
|
||||
JPEG_VERIFY_MARKER_END();
|
||||
|
||||
// Set the input colorspace based on the markers we have seen and set
|
||||
// default output colorspace.
|
||||
if (cinfo->num_components == 1) {
|
||||
cinfo->jpeg_color_space = JCS_GRAYSCALE;
|
||||
cinfo->out_color_space = JCS_GRAYSCALE;
|
||||
} else if (cinfo->num_components == 3) {
|
||||
if (cinfo->saw_JFIF_marker) {
|
||||
cinfo->jpeg_color_space = JCS_YCbCr;
|
||||
} else if (cinfo->saw_Adobe_marker) {
|
||||
cinfo->jpeg_color_space =
|
||||
cinfo->Adobe_transform == 0 ? JCS_RGB : JCS_YCbCr;
|
||||
} else {
|
||||
cinfo->jpeg_color_space = JCS_YCbCr;
|
||||
if (cinfo->comp_info[0].component_id == 'R' && //
|
||||
cinfo->comp_info[1].component_id == 'G' && //
|
||||
cinfo->comp_info[2].component_id == 'B') {
|
||||
cinfo->jpeg_color_space = JCS_RGB;
|
||||
}
|
||||
}
|
||||
cinfo->out_color_space = JCS_RGB;
|
||||
} else if (cinfo->num_components == 4) {
|
||||
if (cinfo->saw_Adobe_marker) {
|
||||
cinfo->jpeg_color_space =
|
||||
cinfo->Adobe_transform == 0 ? JCS_CMYK : JCS_YCCK;
|
||||
} else {
|
||||
cinfo->jpeg_color_space = JCS_CMYK;
|
||||
}
|
||||
cinfo->out_color_space = JCS_CMYK;
|
||||
}
|
||||
|
||||
// We have checked above that none of the sampling factors are 0, so the max
|
||||
// sampling factors can not be 0.
|
||||
cinfo->total_iMCU_rows =
|
||||
DivCeil(cinfo->image_height, cinfo->max_v_samp_factor * DCTSIZE);
|
||||
m->iMCU_cols_ =
|
||||
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) {
|
||||
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.");
|
||||
}
|
||||
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_));
|
||||
}
|
||||
|
||||
void ProcessSOS(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (!m->found_sof_) {
|
||||
JPEGLI_ERROR("Unexpected SOS marker.");
|
||||
}
|
||||
m->found_sos_ = true;
|
||||
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);
|
||||
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)
|
||||
JPEGLI_ERROR("Duplicate ID %d in SOS.", id);
|
||||
}
|
||||
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) {
|
||||
comp = &cinfo->comp_info[j];
|
||||
cinfo->cur_comp_info[i] = comp;
|
||||
}
|
||||
}
|
||||
if (!comp) {
|
||||
JPEGLI_ERROR("SOS marker: Could not find component with id %d", id);
|
||||
}
|
||||
int c = ReadUint8(data, &pos);
|
||||
comp->dc_tbl_no = c >> 4;
|
||||
comp->ac_tbl_no = c & 0xf;
|
||||
JPEG_VERIFY_INPUT(comp->dc_tbl_no, 0, 3);
|
||||
JPEG_VERIFY_INPUT(comp->ac_tbl_no, 0, 3);
|
||||
comp->MCU_width = is_interleaved ? comp->h_samp_factor : 1;
|
||||
comp->MCU_height = is_interleaved ? comp->v_samp_factor : 1;
|
||||
comp->MCU_blocks = comp->MCU_width * comp->MCU_height;
|
||||
if (cinfo->blocks_in_MCU + comp->MCU_blocks > D_MAX_BLOCKS_IN_MCU) {
|
||||
JPEGLI_ERROR("Too many blocks in MCU.");
|
||||
}
|
||||
for (int j = 0; j < comp->MCU_blocks; ++j) {
|
||||
cinfo->MCU_membership[cinfo->blocks_in_MCU++] = i;
|
||||
}
|
||||
}
|
||||
JPEG_VERIFY_LEN(3);
|
||||
cinfo->Ss = ReadUint8(data, &pos);
|
||||
cinfo->Se = ReadUint8(data, &pos);
|
||||
JPEG_VERIFY_INPUT(cinfo->Ss, 0, 63);
|
||||
JPEG_VERIFY_INPUT(cinfo->Se, cinfo->Ss, 63);
|
||||
int bit_span = ReadUint8(data, &pos);
|
||||
cinfo->Ah = bit_span >> 4;
|
||||
cinfo->Al = bit_span & 0xf;
|
||||
JPEG_VERIFY_MARKER_END();
|
||||
|
||||
if (cinfo->input_scan_number == 0) {
|
||||
m->is_multiscan_ = (cinfo->comps_in_scan < cinfo->num_components ||
|
||||
FROM_JXL_BOOL(cinfo->progressive_mode));
|
||||
}
|
||||
if (cinfo->Ah != 0 && cinfo->Al != cinfo->Ah - 1) {
|
||||
// section G.1.1.1.2 : Successive approximation control only improves
|
||||
// by one bit at a time.
|
||||
JPEGLI_ERROR("Invalid progressive parameters: Al=%d Ah=%d", cinfo->Al,
|
||||
cinfo->Ah);
|
||||
}
|
||||
if (!cinfo->progressive_mode) {
|
||||
cinfo->Ss = 0;
|
||||
cinfo->Se = 63;
|
||||
cinfo->Ah = 0;
|
||||
cinfo->Al = 0;
|
||||
}
|
||||
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) {
|
||||
if (m->scan_progression_[comp_idx][k] & scan_bitmask) {
|
||||
JPEGLI_ERROR(
|
||||
"Overlapping scans: component=%d k=%d prev_mask: %u cur_mask %u",
|
||||
comp_idx, k, m->scan_progression_[i][k], scan_bitmask);
|
||||
}
|
||||
if (m->scan_progression_[comp_idx][k] & refinement_bitmask) {
|
||||
JPEGLI_ERROR(
|
||||
"Invalid scan order, a more refined scan was already done: "
|
||||
"component=%d k=%d prev_mask=%u cur_mask=%u",
|
||||
comp_idx, k, m->scan_progression_[i][k], scan_bitmask);
|
||||
}
|
||||
m->scan_progression_[comp_idx][k] |= scan_bitmask;
|
||||
}
|
||||
}
|
||||
if (cinfo->Al > 10) {
|
||||
JPEGLI_ERROR("Scan parameter Al=%d is not supported.", cinfo->Al);
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
size_t pos = 2;
|
||||
if (pos == len) {
|
||||
JPEGLI_ERROR("DHT marker: no Huffman table found");
|
||||
}
|
||||
while (pos < len) {
|
||||
JPEG_VERIFY_LEN(1 + kJpegHuffmanMaxBitLength);
|
||||
// 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);
|
||||
int huffman_index = slot_id;
|
||||
bool is_ac_table = ((slot_id & 0x10) != 0);
|
||||
JHUFF_TBL** table;
|
||||
if (is_ac_table) {
|
||||
huffman_index -= 0x10;
|
||||
JPEG_VERIFY_INPUT(huffman_index, 0, NUM_HUFF_TBLS - 1);
|
||||
table = &cinfo->ac_huff_tbl_ptrs[huffman_index];
|
||||
} else {
|
||||
JPEG_VERIFY_INPUT(huffman_index, 0, NUM_HUFF_TBLS - 1);
|
||||
table = &cinfo->dc_huff_tbl_ptrs[huffman_index];
|
||||
}
|
||||
if (*table == nullptr) {
|
||||
*table = jpegli_alloc_huff_table(reinterpret_cast<j_common_ptr>(cinfo));
|
||||
}
|
||||
int total_count = 0;
|
||||
for (size_t i = 1; i <= kJpegHuffmanMaxBitLength; ++i) {
|
||||
int count = ReadUint8(data, &pos);
|
||||
(*table)->bits[i] = count;
|
||||
total_count += count;
|
||||
}
|
||||
if (is_ac_table) {
|
||||
JPEG_VERIFY_INPUT(total_count, 0, kJpegHuffmanAlphabetSize);
|
||||
} else {
|
||||
// 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);
|
||||
for (int i = 0; i < total_count; ++i) {
|
||||
int value = ReadUint8(data, &pos);
|
||||
if (!is_ac_table) {
|
||||
JPEG_VERIFY_INPUT(value, 0, 15);
|
||||
}
|
||||
(*table)->huffval[i] = value;
|
||||
}
|
||||
for (int i = total_count; i < kJpegHuffmanAlphabetSize; ++i) {
|
||||
(*table)->huffval[i] = 0;
|
||||
}
|
||||
}
|
||||
JPEG_VERIFY_MARKER_END();
|
||||
}
|
||||
|
||||
void ProcessDQT(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (m->found_sos_) {
|
||||
JPEGLI_ERROR("Updating quant tables between scans is not supported.");
|
||||
}
|
||||
size_t pos = 2;
|
||||
if (pos == len) {
|
||||
JPEGLI_ERROR("DQT marker: no quantization table found");
|
||||
}
|
||||
while (pos < len) {
|
||||
JPEG_VERIFY_LEN(1);
|
||||
int quant_table_index = ReadUint8(data, &pos);
|
||||
int precision = quant_table_index >> 4;
|
||||
JPEG_VERIFY_INPUT(precision, 0, 1);
|
||||
quant_table_index &= 0xf;
|
||||
JPEG_VERIFY_INPUT(quant_table_index, 0, NUM_QUANT_TBLS - 1);
|
||||
JPEG_VERIFY_LEN((precision + 1) * DCTSIZE2);
|
||||
|
||||
if (cinfo->quant_tbl_ptrs[quant_table_index] == nullptr) {
|
||||
cinfo->quant_tbl_ptrs[quant_table_index] =
|
||||
jpegli_alloc_quant_table(reinterpret_cast<j_common_ptr>(cinfo));
|
||||
}
|
||||
JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[quant_table_index];
|
||||
|
||||
for (size_t i = 0; i < DCTSIZE2; ++i) {
|
||||
int quant_val =
|
||||
precision ? ReadUint16(data, &pos) : ReadUint8(data, &pos);
|
||||
JPEG_VERIFY_INPUT(quant_val, 1, 65535);
|
||||
quant_table->quantval[kJPEGNaturalOrder[i]] = quant_val;
|
||||
}
|
||||
}
|
||||
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_) {
|
||||
JPEGLI_ERROR("Duplicate DRI marker.");
|
||||
}
|
||||
m->found_dri_ = true;
|
||||
size_t pos = 2;
|
||||
JPEG_VERIFY_LEN(2);
|
||||
cinfo->restart_interval = ReadUint16(data, &pos);
|
||||
JPEG_VERIFY_MARKER_END();
|
||||
}
|
||||
|
||||
void ProcessAPP(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
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;
|
||||
cinfo->JFIF_major_version = payload[5];
|
||||
cinfo->JFIF_minor_version = payload[6];
|
||||
cinfo->density_unit = payload[7];
|
||||
cinfo->X_density = (payload[8] << 8) + payload[9];
|
||||
cinfo->Y_density = (payload[10] << 8) + payload[11];
|
||||
}
|
||||
} else if (marker == 0xEE) {
|
||||
if (payload_size >= 12 && memcmp(payload, "Adobe", 5) == 0) {
|
||||
cinfo->saw_Adobe_marker = TRUE;
|
||||
cinfo->Adobe_transform = payload[11];
|
||||
}
|
||||
} else if (marker == 0xE2) {
|
||||
if (payload_size >= sizeof(kIccProfileTag) &&
|
||||
memcmp(payload, kIccProfileTag, sizeof(kIccProfileTag)) == 0) {
|
||||
payload += sizeof(kIccProfileTag);
|
||||
payload_size -= sizeof(kIccProfileTag);
|
||||
if (payload_size < 2) {
|
||||
JPEGLI_ERROR("ICC chunk is too small.");
|
||||
}
|
||||
uint8_t index = payload[0];
|
||||
uint8_t total = payload[1];
|
||||
++m->icc_index_;
|
||||
if (m->icc_index_ != index) {
|
||||
JPEGLI_ERROR("Invalid ICC chunk order.");
|
||||
}
|
||||
if (total == 0) {
|
||||
JPEGLI_ERROR("Invalid ICC chunk total.");
|
||||
}
|
||||
if (m->icc_total_ == 0) {
|
||||
m->icc_total_ = total;
|
||||
} else if (m->icc_total_ != total) {
|
||||
JPEGLI_ERROR("Invalid ICC chunk total.");
|
||||
}
|
||||
if (m->icc_index_ > m->icc_total_) {
|
||||
JPEGLI_ERROR("Invalid ICC chunk index.");
|
||||
}
|
||||
m->icc_profile_.insert(m->icc_profile_.end(), payload + 2,
|
||||
payload + payload_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessCOM(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
// Ignore marker.
|
||||
}
|
||||
|
||||
void ProcessSOI(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (m->found_soi_) {
|
||||
JPEGLI_ERROR("Duplicate SOI marker");
|
||||
}
|
||||
m->found_soi_ = true;
|
||||
}
|
||||
|
||||
void ProcessEOI(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
cinfo->master->found_eoi_ = true;
|
||||
}
|
||||
|
||||
void SaveMarker(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
|
||||
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, 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, JPOOL_IMAGE);
|
||||
memcpy(cinfo->marker_list->data, payload, payload_size);
|
||||
}
|
||||
|
||||
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;
|
||||
size_t num_skipped = 0;
|
||||
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",
|
||||
static_cast<int>(num_skipped), marker);
|
||||
} else {
|
||||
JPEGLI_ERROR("Did not find SOI marker.");
|
||||
}
|
||||
}
|
||||
*pos += 2;
|
||||
cinfo->unread_marker = marker;
|
||||
}
|
||||
if (!m->found_soi_ && marker != 0xd8) {
|
||||
JPEGLI_ERROR("Did not find SOI marker.");
|
||||
}
|
||||
if (GetMarkerProcessor(cinfo)) {
|
||||
return kHandleMarkerProcessor;
|
||||
}
|
||||
const uint8_t* marker_data = &data[*pos];
|
||||
size_t marker_len = 0;
|
||||
if (marker != 0xd8 && marker != 0xd9) {
|
||||
if (*pos + 2 > len) {
|
||||
return kNeedMoreInput;
|
||||
}
|
||||
marker_len += (data[*pos] << 8) + data[*pos + 1];
|
||||
if (marker_len < 2) {
|
||||
JPEGLI_ERROR("Invalid marker length");
|
||||
}
|
||||
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 (marker >= 0xe0 && m->markers_to_save_[marker - 0xe0]) {
|
||||
SaveMarker(cinfo, marker_data, marker_len);
|
||||
}
|
||||
}
|
||||
if (marker == 0xc0 || marker == 0xc1 || marker == 0xc2) {
|
||||
ProcessSOF(cinfo, marker_data, marker_len);
|
||||
} else if (marker == 0xc4) {
|
||||
ProcessDHT(cinfo, marker_data, marker_len);
|
||||
} else if (marker == 0xda) {
|
||||
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) {
|
||||
ProcessAPP(cinfo, marker_data, marker_len);
|
||||
} else if (marker == 0xfe) {
|
||||
ProcessCOM(cinfo, marker_data, marker_len);
|
||||
} else if (marker == 0xd8) {
|
||||
ProcessSOI(cinfo, marker_data, marker_len);
|
||||
} else if (marker == 0xd9) {
|
||||
ProcessEOI(cinfo, marker_data, marker_len);
|
||||
} else {
|
||||
JPEGLI_ERROR("Unexpected marker 0x%x", 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
|
||||
|
||||
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 (;;) {
|
||||
int status = ProcessNextMarker(cinfo, data, len, pos);
|
||||
if (status != kProcessNextMarker) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_DECODE_MARKER_H_
|
||||
#define LIB_JPEGLI_DECODE_MARKER_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
// Reads the available input in the source manager's input buffer until either
|
||||
// the end of the next SOS marker or the end of the input.
|
||||
// The corresponding fields of cinfo are updated with the processed input data.
|
||||
// Upon return, the input buffer will be at the start or at the end of a marker
|
||||
// data segment (inter-marker data is allowed).
|
||||
// Return value is one of:
|
||||
// * JPEG_SUSPENDED, if the current input buffer ends before the next SOS or
|
||||
// EOI marker. Input buffer refill is handled by the caller;
|
||||
// * JPEG_REACHED_SOS, if the next SOS marker is found;
|
||||
// * JPEG_REACHED_EOR, if the end of the input is found.
|
||||
int ProcessMarkers(j_decompress_ptr cinfo, const uint8_t* data, size_t len,
|
||||
size_t* pos);
|
||||
|
||||
jpeg_marker_parser_method GetMarkerProcessor(j_decompress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_DECODE_MARKER_H_
|
||||
@@ -1,570 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/decode_scan.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <hwy/base.h> // HWY_ALIGN_MAX
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/decode_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jpegli/huffman.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jpegli {
|
||||
namespace {
|
||||
|
||||
// Max 14 block per MCU (when 1 channel is subsampled)
|
||||
// Max 64 nonzero coefficients per block
|
||||
// Max 16 symbol bits plus 11 extra bits per nonzero symbol
|
||||
// Max 2 bytes per 8 bits (worst case is all bytes are escaped 0xff)
|
||||
constexpr int kMaxMCUByteSize = 6048;
|
||||
|
||||
// Helper structure to read bits from the entropy coded data segment.
|
||||
struct BitReaderState {
|
||||
BitReaderState(const uint8_t* data, const size_t len, size_t pos)
|
||||
: data_(data), len_(len), start_pos_(pos) {
|
||||
Reset(pos);
|
||||
}
|
||||
|
||||
void Reset(size_t pos) {
|
||||
pos_ = pos;
|
||||
val_ = 0;
|
||||
bits_left_ = 0;
|
||||
next_marker_pos_ = len_;
|
||||
FillBitWindow();
|
||||
}
|
||||
|
||||
// Returns the next byte and skips the 0xff/0x00 escape sequences.
|
||||
uint8_t GetNextByte() {
|
||||
if (pos_ >= next_marker_pos_) {
|
||||
++pos_;
|
||||
return 0;
|
||||
}
|
||||
uint8_t c = data_[pos_++];
|
||||
if (c == 0xff) {
|
||||
uint8_t escape = pos_ < len_ ? data_[pos_] : 0;
|
||||
if (escape == 0) {
|
||||
++pos_;
|
||||
} else {
|
||||
// 0xff was followed by a non-zero byte, which means that we found the
|
||||
// start of the next marker segment.
|
||||
next_marker_pos_ = pos_ - 1;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
void FillBitWindow() {
|
||||
if (bits_left_ <= 16) {
|
||||
while (bits_left_ <= 56) {
|
||||
val_ <<= 8;
|
||||
val_ |= static_cast<uint64_t>(GetNextByte());
|
||||
bits_left_ += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ReadBits(int nbits) {
|
||||
FillBitWindow();
|
||||
uint64_t val = (val_ >> (bits_left_ - nbits)) & ((1ULL << nbits) - 1);
|
||||
bits_left_ -= nbits;
|
||||
return val;
|
||||
}
|
||||
|
||||
// Sets *pos to the next stream position, and *bit_pos to the bit position
|
||||
// within the next byte where parsing should continue.
|
||||
// Returns false if the stream ended too early.
|
||||
bool FinishStream(size_t* pos, size_t* bit_pos) {
|
||||
*bit_pos = (8 - (bits_left_ & 7)) & 7;
|
||||
// Give back some bytes that we did not use.
|
||||
int unused_bytes_left = DivCeil(bits_left_, 8);
|
||||
while (unused_bytes_left-- > 0) {
|
||||
--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_ && pos_ == next_marker_pos_) ||
|
||||
(pos_ > 0 && pos_ < next_marker_pos_ && data_[pos_] == 0)) &&
|
||||
(data_[pos_ - 1] == 0xff)) {
|
||||
--pos_;
|
||||
}
|
||||
}
|
||||
if (pos_ >= next_marker_pos_) {
|
||||
*pos = next_marker_pos_;
|
||||
if (pos_ > next_marker_pos_ || *bit_pos > 0) {
|
||||
// Data ran out before the scan was complete.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*pos = pos_;
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint8_t* data_;
|
||||
const size_t len_;
|
||||
size_t pos_;
|
||||
uint64_t val_;
|
||||
int bits_left_;
|
||||
size_t next_marker_pos_;
|
||||
size_t start_pos_;
|
||||
};
|
||||
|
||||
// Returns the next Huffman-coded symbol.
|
||||
int ReadSymbol(const HuffmanTableEntry* table, BitReaderState* br) {
|
||||
int nbits;
|
||||
br->FillBitWindow();
|
||||
int val = (br->val_ >> (br->bits_left_ - 8)) & 0xff;
|
||||
table += val;
|
||||
nbits = table->bits - 8;
|
||||
if (nbits > 0) {
|
||||
br->bits_left_ -= 8;
|
||||
table += table->value;
|
||||
val = (br->val_ >> (br->bits_left_ - nbits)) & ((1 << nbits) - 1);
|
||||
table += val;
|
||||
}
|
||||
br->bits_left_ -= table->bits;
|
||||
return table->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DC diff or AC value for extra bits value x and prefix code s.
|
||||
*
|
||||
* CCITT Rec. T.81 (1992 E)
|
||||
* Table F.1 – Difference magnitude categories for DC coding
|
||||
* SSSS | DIFF values
|
||||
* ------+--------------------------
|
||||
* 0 | 0
|
||||
* 1 | –1, 1
|
||||
* 2 | –3, –2, 2, 3
|
||||
* 3 | –7..–4, 4..7
|
||||
* ......|..........................
|
||||
* 11 | –2047..–1024, 1024..2047
|
||||
*
|
||||
* CCITT Rec. T.81 (1992 E)
|
||||
* Table F.2 – Categories assigned to coefficient values
|
||||
* [ Same as Table F.1, but does not include SSSS equal to 0 and 11]
|
||||
*
|
||||
*
|
||||
* CCITT Rec. T.81 (1992 E)
|
||||
* F.1.2.1.1 Structure of DC code table
|
||||
* For each category,... additional bits... appended... to uniquely identify
|
||||
* which difference... occurred... When DIFF is positive... SSSS... bits of DIFF
|
||||
* are appended. When DIFF is negative... SSSS... bits of (DIFF – 1) are
|
||||
* appended... Most significant bit... is 0 for negative differences and 1 for
|
||||
* positive differences.
|
||||
*
|
||||
* In other words the upper half of extra bits range represents DIFF as is.
|
||||
* The lower half represents the negative DIFFs with an offset.
|
||||
*/
|
||||
int HuffExtend(int x, int s) {
|
||||
JXL_DASSERT(s > 0);
|
||||
int half = 1 << (s - 1);
|
||||
if (x >= half) {
|
||||
JXL_DASSERT(x < (1 << s));
|
||||
return x;
|
||||
} else {
|
||||
return x - (1 << s) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Decodes one 8x8 block of DCT coefficients from the bit stream.
|
||||
bool DecodeDCTBlock(const HuffmanTableEntry* dc_huff,
|
||||
const HuffmanTableEntry* ac_huff, int Ss, int Se, int Al,
|
||||
int* eobrun, BitReaderState* br, coeff_t* last_dc_coeff,
|
||||
coeff_t* coeffs) {
|
||||
// Nowadays multiplication is even faster than variable shift.
|
||||
int Am = 1 << Al;
|
||||
bool eobrun_allowed = Ss > 0;
|
||||
if (Ss == 0) {
|
||||
int s = ReadSymbol(dc_huff, br);
|
||||
if (s >= kJpegDCAlphabetSize) {
|
||||
return false;
|
||||
}
|
||||
int diff = 0;
|
||||
if (s > 0) {
|
||||
int bits = br->ReadBits(s);
|
||||
diff = HuffExtend(bits, s);
|
||||
}
|
||||
int coeff = diff + *last_dc_coeff;
|
||||
const int dc_coeff = coeff * Am;
|
||||
coeffs[0] = dc_coeff;
|
||||
// TODO(eustas): is there a more elegant / explicit way to check this?
|
||||
if (dc_coeff != coeffs[0]) {
|
||||
return false;
|
||||
}
|
||||
*last_dc_coeff = coeff;
|
||||
++Ss;
|
||||
}
|
||||
if (Ss > Se) {
|
||||
return true;
|
||||
}
|
||||
if (*eobrun > 0) {
|
||||
--(*eobrun);
|
||||
return true;
|
||||
}
|
||||
for (int k = Ss; k <= Se; k++) {
|
||||
int sr = ReadSymbol(ac_huff, br);
|
||||
if (sr >= kJpegHuffmanAlphabetSize) {
|
||||
return false;
|
||||
}
|
||||
int r = sr >> 4;
|
||||
int s = sr & 15;
|
||||
if (s > 0) {
|
||||
k += r;
|
||||
if (k > Se) {
|
||||
return false;
|
||||
}
|
||||
if (s + Al >= kJpegDCAlphabetSize) {
|
||||
return false;
|
||||
}
|
||||
int bits = br->ReadBits(s);
|
||||
int coeff = HuffExtend(bits, s);
|
||||
coeffs[kJPEGNaturalOrder[k]] = coeff * Am;
|
||||
} else if (r == 15) {
|
||||
k += 15;
|
||||
} else {
|
||||
*eobrun = 1 << r;
|
||||
if (r > 0) {
|
||||
if (!eobrun_allowed) {
|
||||
return false;
|
||||
}
|
||||
*eobrun += br->ReadBits(r);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
--(*eobrun);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RefineDCTBlock(const HuffmanTableEntry* ac_huff, int Ss, int Se, int Al,
|
||||
int* eobrun, BitReaderState* br, coeff_t* coeffs) {
|
||||
// Nowadays multiplication is even faster than variable shift.
|
||||
int Am = 1 << Al;
|
||||
bool eobrun_allowed = Ss > 0;
|
||||
if (Ss == 0) {
|
||||
int s = br->ReadBits(1);
|
||||
coeff_t dc_coeff = coeffs[0];
|
||||
dc_coeff |= s * Am;
|
||||
coeffs[0] = dc_coeff;
|
||||
++Ss;
|
||||
}
|
||||
if (Ss > Se) {
|
||||
return true;
|
||||
}
|
||||
int p1 = Am;
|
||||
int m1 = -Am;
|
||||
int k = Ss;
|
||||
int r;
|
||||
int s;
|
||||
bool in_zero_run = false;
|
||||
if (*eobrun <= 0) {
|
||||
for (; k <= Se; k++) {
|
||||
s = ReadSymbol(ac_huff, br);
|
||||
if (s >= kJpegHuffmanAlphabetSize) {
|
||||
return false;
|
||||
}
|
||||
r = s >> 4;
|
||||
s &= 15;
|
||||
if (s) {
|
||||
if (s != 1) {
|
||||
return false;
|
||||
}
|
||||
s = br->ReadBits(1) ? p1 : m1;
|
||||
in_zero_run = false;
|
||||
} else {
|
||||
if (r != 15) {
|
||||
*eobrun = 1 << r;
|
||||
if (r > 0) {
|
||||
if (!eobrun_allowed) {
|
||||
return false;
|
||||
}
|
||||
*eobrun += br->ReadBits(r);
|
||||
}
|
||||
break;
|
||||
}
|
||||
in_zero_run = true;
|
||||
}
|
||||
do {
|
||||
coeff_t thiscoef = coeffs[kJPEGNaturalOrder[k]];
|
||||
if (thiscoef != 0) {
|
||||
if (br->ReadBits(1)) {
|
||||
if ((thiscoef & p1) == 0) {
|
||||
if (thiscoef >= 0) {
|
||||
thiscoef += p1;
|
||||
} else {
|
||||
thiscoef += m1;
|
||||
}
|
||||
}
|
||||
}
|
||||
coeffs[kJPEGNaturalOrder[k]] = thiscoef;
|
||||
} else {
|
||||
if (--r < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
k++;
|
||||
} while (k <= Se);
|
||||
if (s) {
|
||||
if (k > Se) {
|
||||
return false;
|
||||
}
|
||||
coeffs[kJPEGNaturalOrder[k]] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (in_zero_run) {
|
||||
return false;
|
||||
}
|
||||
if (*eobrun > 0) {
|
||||
for (; k <= Se; k++) {
|
||||
coeff_t thiscoef = coeffs[kJPEGNaturalOrder[k]];
|
||||
if (thiscoef != 0) {
|
||||
if (br->ReadBits(1)) {
|
||||
if ((thiscoef & p1) == 0) {
|
||||
if (thiscoef >= 0) {
|
||||
thiscoef += p1;
|
||||
} else {
|
||||
thiscoef += m1;
|
||||
}
|
||||
}
|
||||
}
|
||||
coeffs[kJPEGNaturalOrder[k]] = thiscoef;
|
||||
}
|
||||
}
|
||||
}
|
||||
--(*eobrun);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SaveMCUCodingState(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
memcpy(m->mcu_.last_dc_coeff, m->last_dc_coeff_, sizeof(m->last_dc_coeff_));
|
||||
m->mcu_.eobrun = m->eobrun_;
|
||||
size_t offset = 0;
|
||||
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;
|
||||
size_t block_x = m->scan_mcu_col_ * comp->MCU_width;
|
||||
for (int iy = 0; iy < comp->MCU_height; ++iy) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RestoreMCUCodingState(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
memcpy(m->last_dc_coeff_, m->mcu_.last_dc_coeff, sizeof(m->last_dc_coeff_));
|
||||
m->eobrun_ = m->mcu_.eobrun;
|
||||
size_t offset = 0;
|
||||
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;
|
||||
size_t block_x = m->scan_mcu_col_ * comp->MCU_width;
|
||||
for (int iy = 0; iy < comp->MCU_height; ++iy) {
|
||||
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
|
||||
|
||||
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;
|
||||
}
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
for (;;) {
|
||||
// Handle the restart intervals.
|
||||
if (cinfo->restart_interval > 0 && m->restarts_to_go_ == 0) {
|
||||
if (!FinishScan(cinfo, data, len, pos, bit_pos)) {
|
||||
return kNeedMoreInput;
|
||||
}
|
||||
// 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 (num_skipped > 0) {
|
||||
JPEGLI_WARN("Skipped %d bytes before restart marker",
|
||||
static_cast<int>(num_skipped));
|
||||
}
|
||||
if (*pos + 2 > len) {
|
||||
return kNeedMoreInput;
|
||||
}
|
||||
cinfo->unread_marker = data[*pos + 1];
|
||||
*pos += 2;
|
||||
return kHandleRestart;
|
||||
}
|
||||
|
||||
size_t start_pos = *pos;
|
||||
BitReaderState br(data, len, start_pos);
|
||||
if (*bit_pos > 0) {
|
||||
br.ReadBits(*bit_pos);
|
||||
}
|
||||
if (start_pos + kMaxMCUByteSize > len) {
|
||||
SaveMCUCodingState(cinfo);
|
||||
}
|
||||
|
||||
// Decode one MCU.
|
||||
HWY_ALIGN_MAX static coeff_t sink_block[DCTSIZE2] = {0};
|
||||
bool scan_ok = true;
|
||||
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;
|
||||
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) {
|
||||
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) {
|
||||
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 sink_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 = sink_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,
|
||||
&m->last_dc_coeff_[comp->component_index],
|
||||
coeffs)) {
|
||||
scan_ok = false;
|
||||
}
|
||||
} else {
|
||||
if (!RefineDCTBlock(ac_lut, cinfo->Ss, cinfo->Se, cinfo->Al,
|
||||
&m->eobrun_, &br, coeffs)) {
|
||||
scan_ok = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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 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 (m->restarts_to_go_ > 0) {
|
||||
--m->restarts_to_go_;
|
||||
}
|
||||
++m->scan_mcu_col_;
|
||||
if (m->scan_mcu_col_ == cinfo->MCUs_per_row) {
|
||||
++m->scan_mcu_row_;
|
||||
m->scan_mcu_col_ = 0;
|
||||
if (m->scan_mcu_row_ == cinfo->MCU_rows_in_scan) {
|
||||
if (!FinishScan(cinfo, data, len, pos, bit_pos)) {
|
||||
return kNeedMoreInput;
|
||||
}
|
||||
break;
|
||||
} else if ((m->scan_mcu_row_ % m->mcu_rows_per_iMCU_row_) == 0) {
|
||||
// Current iMCU row is done.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
++cinfo->input_iMCU_row;
|
||||
if (cinfo->input_iMCU_row < cinfo->total_iMCU_rows) {
|
||||
PrepareForiMCURow(cinfo);
|
||||
return JPEG_ROW_COMPLETED;
|
||||
}
|
||||
return JPEG_SCAN_COMPLETED;
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_DECODE_SCAN_H_
|
||||
#define LIB_JPEGLI_DECODE_SCAN_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
// Reads the available input in the source manager's input buffer until the end
|
||||
// of the next iMCU row.
|
||||
// The corresponding fields of cinfo are updated with the processed input data.
|
||||
// Upon return, the input buffer will be at the start of an MCU, or at the end
|
||||
// of the scan.
|
||||
// Return value is one of:
|
||||
// * 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, const uint8_t* data, size_t len,
|
||||
size_t* pos, size_t* bit_pos);
|
||||
|
||||
void PrepareForiMCURow(j_decompress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_DECODE_SCAN_H_
|
||||
@@ -1,152 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/encode.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jpegli/memory_manager.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
constexpr size_t kDestBufferSize = 64 << 10;
|
||||
|
||||
struct StdioDestinationManager {
|
||||
jpeg_destination_mgr pub;
|
||||
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) {
|
||||
JPEGLI_ERROR("Failed to write to output stream.");
|
||||
}
|
||||
dest->pub.next_output_byte = dest->buffer;
|
||||
dest->pub.free_in_buffer = kDestBufferSize;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void term_destination(j_compress_ptr cinfo) {
|
||||
auto* dest = reinterpret_cast<StdioDestinationManager*>(cinfo->dest);
|
||||
size_t bytes_left = kDestBufferSize - dest->pub.free_in_buffer;
|
||||
if (bytes_left &&
|
||||
fwrite(dest->buffer, 1, bytes_left, dest->f) != bytes_left) {
|
||||
JPEGLI_ERROR("Failed to write to output stream.");
|
||||
}
|
||||
fflush(dest->f);
|
||||
if (ferror(dest->f)) {
|
||||
JPEGLI_ERROR("Failed to write to output stream.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct MemoryDestinationManager {
|
||||
jpeg_destination_mgr pub;
|
||||
// Output buffer supplied by the application
|
||||
uint8_t** output;
|
||||
unsigned long* output_size; // NOLINT
|
||||
// Output buffer allocated by us.
|
||||
uint8_t* temp_buffer;
|
||||
// Current output buffer (either application supplied or allocated by us).
|
||||
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 =
|
||||
reinterpret_cast<uint8_t*>(malloc(dest->buffer_size * 2));
|
||||
memcpy(next_buffer, dest->current_buffer, dest->buffer_size);
|
||||
if (dest->temp_buffer != nullptr) {
|
||||
free(dest->temp_buffer);
|
||||
}
|
||||
dest->temp_buffer = next_buffer;
|
||||
dest->current_buffer = next_buffer;
|
||||
*dest->output = next_buffer;
|
||||
*dest->output_size = dest->buffer_size;
|
||||
dest->pub.next_output_byte = next_buffer + dest->buffer_size;
|
||||
dest->pub.free_in_buffer = dest->buffer_size;
|
||||
dest->buffer_size *= 2;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void term_destination(j_compress_ptr cinfo) {
|
||||
auto* dest = reinterpret_cast<MemoryDestinationManager*>(cinfo->dest);
|
||||
*dest->output_size = dest->buffer_size - dest->pub.free_in_buffer;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
void jpegli_stdio_dest(j_compress_ptr cinfo, FILE* outfile) {
|
||||
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::StdioDestinationManager::init_destination;
|
||||
dest->pub.empty_output_buffer =
|
||||
jpegli::StdioDestinationManager::empty_output_buffer;
|
||||
dest->pub.term_destination =
|
||||
jpegli::StdioDestinationManager::term_destination;
|
||||
}
|
||||
|
||||
void jpegli_mem_dest(j_compress_ptr cinfo, unsigned char** outbuffer,
|
||||
unsigned long* outsize /* NOLINT */) {
|
||||
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;
|
||||
if (*outbuffer == nullptr || *outsize == 0) {
|
||||
dest->temp_buffer =
|
||||
reinterpret_cast<uint8_t*>(malloc(jpegli::kDestBufferSize));
|
||||
*outbuffer = dest->temp_buffer;
|
||||
*outsize = jpegli::kDestBufferSize;
|
||||
}
|
||||
dest->current_buffer = *outbuffer;
|
||||
dest->buffer_size = *outsize;
|
||||
dest->pub.next_output_byte = dest->current_buffer;
|
||||
dest->pub.free_in_buffer = dest->buffer_size;
|
||||
}
|
||||
@@ -1,363 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/downsample.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jxl/base/compiler_specific.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; // NOLINT
|
||||
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; // NOLINT
|
||||
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; // NOLINT
|
||||
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; // NOLINT
|
||||
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 y_out0 = y0 / v_factor;
|
||||
float* rows_in[MAX_SAMP_FACTOR];
|
||||
for (size_t y_in = y0, y_out = y_out0; y_in < y1;
|
||||
y_in += v_factor, ++y_out) {
|
||||
for (int iy = 0; iy < v_factor; ++iy) {
|
||||
rows_in[iy] = input.Row(y_in + iy);
|
||||
}
|
||||
float* row_out = output.Row(y_out);
|
||||
(*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 ptrdiff_t y0 = m->next_iMCU_row * iMCU_height;
|
||||
const ptrdiff_t y1 = y0 + iMCU_height;
|
||||
const ptrdiff_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 (ptrdiff_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 (ptrdiff_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
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_DOWNSAMPLE_H_
|
||||
#define LIB_JPEGLI_DOWNSAMPLE_H_
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
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_
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,162 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
// This file contains the C API of the encoder part of the libjpegli library,
|
||||
// which is based on the C API of libjpeg, with the function names changed from
|
||||
// jpeg_* to jpegli_*, while compressor object definitions are included directly
|
||||
// from jpeglib.h
|
||||
//
|
||||
// Applications can use the libjpegli library in one of the following ways:
|
||||
//
|
||||
// (1) Include jpegli/encode.h and/or jpegli/decode.h, update the function
|
||||
// names of the API and link against libjpegli.
|
||||
//
|
||||
// (2) Leave the application code unchanged, but replace the libjpeg.so library
|
||||
// with the one built by this project that is API- and ABI-compatible with
|
||||
// libjpeg-turbo's version of libjpeg.so.
|
||||
|
||||
#ifndef LIB_JPEGLI_ENCODE_H_
|
||||
#define LIB_JPEGLI_ENCODE_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define jpegli_create_compress(cinfo) \
|
||||
jpegli_CreateCompress((cinfo), JPEG_LIB_VERSION, \
|
||||
(size_t)sizeof(struct jpeg_compress_struct))
|
||||
void jpegli_CreateCompress(j_compress_ptr cinfo, int version,
|
||||
size_t structsize);
|
||||
|
||||
void jpegli_stdio_dest(j_compress_ptr cinfo, FILE* outfile);
|
||||
|
||||
void jpegli_mem_dest(j_compress_ptr cinfo, unsigned char** outbuffer,
|
||||
unsigned long* outsize /* NOLINT */);
|
||||
|
||||
void jpegli_set_defaults(j_compress_ptr cinfo);
|
||||
|
||||
void jpegli_default_colorspace(j_compress_ptr cinfo);
|
||||
|
||||
void jpegli_set_colorspace(j_compress_ptr cinfo, J_COLOR_SPACE colorspace);
|
||||
|
||||
void jpegli_set_quality(j_compress_ptr cinfo, int quality,
|
||||
boolean force_baseline);
|
||||
|
||||
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,
|
||||
const unsigned int* basic_table, int scale_factor,
|
||||
boolean force_baseline);
|
||||
|
||||
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);
|
||||
|
||||
void jpegli_write_m_header(j_compress_ptr cinfo, int marker,
|
||||
unsigned int datalen);
|
||||
|
||||
void jpegli_write_m_byte(j_compress_ptr cinfo, int val);
|
||||
|
||||
void jpegli_write_marker(j_compress_ptr cinfo, int marker,
|
||||
const JOCTET* dataptr, unsigned int datalen);
|
||||
|
||||
void jpegli_write_icc_profile(j_compress_ptr cinfo, const JOCTET* icc_data_ptr,
|
||||
unsigned int icc_data_len);
|
||||
|
||||
void jpegli_start_compress(j_compress_ptr cinfo, boolean write_all_tables);
|
||||
|
||||
void jpegli_write_tables(j_compress_ptr cinfo);
|
||||
|
||||
JDIMENSION jpegli_write_scanlines(j_compress_ptr cinfo, JSAMPARRAY scanlines,
|
||||
JDIMENSION num_lines);
|
||||
|
||||
JDIMENSION jpegli_write_raw_data(j_compress_ptr cinfo, JSAMPIMAGE data,
|
||||
JDIMENSION num_lines);
|
||||
|
||||
void jpegli_write_coefficients(j_compress_ptr cinfo,
|
||||
jvirt_barray_ptr* coef_arrays);
|
||||
|
||||
void jpegli_finish_compress(j_compress_ptr cinfo);
|
||||
|
||||
void jpegli_abort_compress(j_compress_ptr cinfo);
|
||||
|
||||
void jpegli_destroy_compress(j_compress_ptr cinfo);
|
||||
|
||||
//
|
||||
// New API functions that are not available in libjpeg
|
||||
//
|
||||
// NOTE: This part of the API is still experimental and will probably change in
|
||||
// the future.
|
||||
//
|
||||
|
||||
// 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);
|
||||
|
||||
// Enables distance parameter search to meet the given psnr target.
|
||||
void jpegli_set_psnr(j_compress_ptr cinfo, float psnr, float tolerance,
|
||||
float min_distance, float max_distance);
|
||||
|
||||
// Changes the default behaviour of the encoder in the selection of quantization
|
||||
// matrices and chroma subsampling. Must be called before jpegli_set_defaults()
|
||||
// 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);
|
||||
|
||||
// Sets whether or not the encoder uses adaptive quantization for creating more
|
||||
// zero coefficients based on the local properties of the image.
|
||||
// Enabled by default.
|
||||
void jpegli_enable_adaptive_quantization(j_compress_ptr cinfo, boolean value);
|
||||
|
||||
// Sets the default progression parameters, where level 0 is sequential, and
|
||||
// greater level value means more progression steps. Default is 2.
|
||||
void jpegli_set_progressive_level(j_compress_ptr cinfo, int level);
|
||||
|
||||
// If this function is called before starting compression, the quality and
|
||||
// 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. Must be called before jpegli_set_defaults().
|
||||
void jpegli_use_standard_quant_tables(j_compress_ptr cinfo);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // LIB_JPEGLI_ENCODE_H_
|
||||
@@ -1,874 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/encode.h"
|
||||
#include "lib/jpegli/libjpeg_test_util.h"
|
||||
#include "lib/jpegli/test_params.h"
|
||||
#include "lib/jpegli/test_utils.h"
|
||||
#include "lib/jpegli/testing.h"
|
||||
#include "lib/jpegli/types.h"
|
||||
|
||||
namespace jpegli {
|
||||
namespace {
|
||||
|
||||
struct TestConfig {
|
||||
TestImage input;
|
||||
CompressParams jparams;
|
||||
JpegIOMode input_mode = PIXELS;
|
||||
double max_bpp;
|
||||
double max_dist;
|
||||
};
|
||||
|
||||
class EncodeAPITestParam : public ::testing::TestWithParam<TestConfig> {};
|
||||
|
||||
void GenerateInput(JpegIOMode input_mode, const CompressParams& jparams,
|
||||
TestImage* input) {
|
||||
GeneratePixels(input);
|
||||
if (input_mode == RAW_DATA) {
|
||||
GenerateRawData(jparams, input);
|
||||
} else if (input_mode == COEFFICIENTS) {
|
||||
GenerateCoeffs(jparams, input);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(EncodeAPITestParam, TestAPI) {
|
||||
TestConfig config = GetParam();
|
||||
GenerateInput(config.input_mode, config.jparams, &config.input);
|
||||
std::vector<uint8_t> compressed;
|
||||
ASSERT_TRUE(EncodeWithJpegli(config.input, config.jparams, &compressed));
|
||||
if (config.jparams.icc.empty()) {
|
||||
double bpp =
|
||||
compressed.size() * 8.0 / (config.input.xsize * config.input.ysize);
|
||||
printf("bpp: %f\n", bpp);
|
||||
EXPECT_LT(bpp, config.max_bpp);
|
||||
}
|
||||
DecompressParams dparams;
|
||||
dparams.output_mode =
|
||||
config.input_mode == COEFFICIENTS ? COEFFICIENTS : PIXELS;
|
||||
if (config.jparams.set_jpeg_colorspace &&
|
||||
config.jparams.jpeg_color_space == JCS_GRAYSCALE) {
|
||||
ConvertToGrayscale(&config.input);
|
||||
} else {
|
||||
dparams.set_out_color_space = true;
|
||||
dparams.out_color_space = config.input.color_space;
|
||||
}
|
||||
TestImage output;
|
||||
DecodeWithLibjpeg(config.jparams, dparams, compressed, &output);
|
||||
VerifyOutputImage(config.input, output, config.max_dist);
|
||||
}
|
||||
|
||||
TEST(EncodeAPITest, ReuseCinfoSameImageTwice) {
|
||||
TestImage input;
|
||||
input.xsize = 129;
|
||||
input.ysize = 73;
|
||||
CompressParams jparams;
|
||||
GenerateInput(PIXELS, jparams, &input);
|
||||
uint8_t* buffer = nullptr;
|
||||
unsigned long buffer_size = 0; // NOLINT
|
||||
std::vector<uint8_t> compressed0;
|
||||
std::vector<uint8_t> compressed1;
|
||||
jpeg_compress_struct cinfo;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_compress(&cinfo);
|
||||
jpegli_mem_dest(&cinfo, &buffer, &buffer_size);
|
||||
EncodeWithJpegli(input, jparams, &cinfo);
|
||||
compressed0.assign(buffer, buffer + buffer_size);
|
||||
jpegli_mem_dest(&cinfo, &buffer, &buffer_size);
|
||||
EncodeWithJpegli(input, jparams, &cinfo);
|
||||
compressed1.assign(buffer, buffer + buffer_size);
|
||||
return true;
|
||||
};
|
||||
EXPECT_TRUE(try_catch_block());
|
||||
jpegli_destroy_compress(&cinfo);
|
||||
if (buffer) free(buffer);
|
||||
ASSERT_EQ(compressed0.size(), compressed1.size());
|
||||
EXPECT_EQ(0,
|
||||
memcmp(compressed0.data(), compressed1.data(), compressed0.size()));
|
||||
}
|
||||
|
||||
std::vector<TestConfig> GenerateBasicConfigs() {
|
||||
std::vector<TestConfig> all_configs;
|
||||
for (int samp : {1, 2}) {
|
||||
for (int progr : {0, 2}) {
|
||||
for (int optimize : {0, 1}) {
|
||||
if (progr && optimize) continue;
|
||||
TestConfig config;
|
||||
config.input.xsize = 257 + samp * 37;
|
||||
config.input.ysize = 265 + optimize * 17;
|
||||
config.jparams.h_sampling = {samp, 1, 1};
|
||||
config.jparams.v_sampling = {samp, 1, 1};
|
||||
config.jparams.progressive_mode = progr;
|
||||
config.jparams.optimize_coding = optimize;
|
||||
config.max_dist = 2.4f;
|
||||
GeneratePixels(&config.input);
|
||||
all_configs.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
return all_configs;
|
||||
}
|
||||
|
||||
TEST(EncodeAPITest, ReuseCinfoSameMemOutput) {
|
||||
std::vector<TestConfig> all_configs = GenerateBasicConfigs();
|
||||
uint8_t* buffer = nullptr;
|
||||
unsigned long buffer_size = 0; // NOLINT
|
||||
{
|
||||
jpeg_compress_struct cinfo;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_compress(&cinfo);
|
||||
jpegli_mem_dest(&cinfo, &buffer, &buffer_size);
|
||||
for (const TestConfig& config : all_configs) {
|
||||
EncodeWithJpegli(config.input, config.jparams, &cinfo);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
EXPECT_TRUE(try_catch_block());
|
||||
jpegli_destroy_compress(&cinfo);
|
||||
}
|
||||
size_t pos = 0;
|
||||
for (auto& config : all_configs) {
|
||||
TestImage output;
|
||||
pos += DecodeWithLibjpeg(config.jparams, DecompressParams(), nullptr, 0,
|
||||
buffer + pos, buffer_size - pos, &output);
|
||||
VerifyOutputImage(config.input, output, config.max_dist);
|
||||
}
|
||||
if (buffer) free(buffer);
|
||||
}
|
||||
|
||||
TEST(EncodeAPITest, ReuseCinfoSameStdOutput) {
|
||||
std::vector<TestConfig> all_configs = GenerateBasicConfigs();
|
||||
FILE* tmpf = tmpfile();
|
||||
ASSERT_TRUE(tmpf);
|
||||
{
|
||||
jpeg_compress_struct cinfo;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_compress(&cinfo);
|
||||
jpegli_stdio_dest(&cinfo, tmpf);
|
||||
for (const TestConfig& config : all_configs) {
|
||||
EncodeWithJpegli(config.input, config.jparams, &cinfo);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
EXPECT_TRUE(try_catch_block());
|
||||
jpegli_destroy_compress(&cinfo);
|
||||
}
|
||||
size_t total_size = ftell(tmpf);
|
||||
fseek(tmpf, 0, SEEK_SET);
|
||||
std::vector<uint8_t> compressed(total_size);
|
||||
ASSERT_TRUE(total_size == fread(compressed.data(), 1, total_size, tmpf));
|
||||
fclose(tmpf);
|
||||
size_t pos = 0;
|
||||
for (auto& config : all_configs) {
|
||||
TestImage output;
|
||||
pos +=
|
||||
DecodeWithLibjpeg(config.jparams, DecompressParams(), nullptr, 0,
|
||||
&compressed[pos], compressed.size() - pos, &output);
|
||||
VerifyOutputImage(config.input, output, config.max_dist);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodeAPITest, ReuseCinfoChangeParams) {
|
||||
TestImage input;
|
||||
TestImage output;
|
||||
CompressParams jparams;
|
||||
DecompressParams dparams;
|
||||
uint8_t* buffer = nullptr;
|
||||
unsigned long buffer_size = 0; // NOLINT
|
||||
std::vector<uint8_t> compressed;
|
||||
jpeg_compress_struct cinfo;
|
||||
const auto max_rms = [](int q, int hs, int vs) {
|
||||
if (hs == 1 && vs == 1) return q == 90 ? 2.2 : 0.6;
|
||||
if (hs == 2 && vs == 2) return q == 90 ? 2.8 : 1.2;
|
||||
return q == 90 ? 2.4 : 1.0;
|
||||
};
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_compress(&cinfo);
|
||||
input.xsize = 129;
|
||||
input.ysize = 73;
|
||||
dparams.set_out_color_space = true;
|
||||
for (JpegIOMode input_mode : {PIXELS, RAW_DATA, PIXELS, COEFFICIENTS}) {
|
||||
for (int h_samp : {2, 1}) {
|
||||
for (int v_samp : {2, 1}) {
|
||||
for (int progr : {0, 2}) {
|
||||
for (int quality : {90, 100}) {
|
||||
input.Clear();
|
||||
input.color_space =
|
||||
(input_mode == RAW_DATA ? JCS_YCbCr : JCS_RGB);
|
||||
jparams.quality = quality;
|
||||
jparams.h_sampling = {h_samp, 1, 1};
|
||||
jparams.v_sampling = {v_samp, 1, 1};
|
||||
jparams.progressive_mode = progr;
|
||||
printf(
|
||||
"Generating input with quality %d chroma subsampling %dx%d "
|
||||
"input mode %d progressive_mode %d\n",
|
||||
quality, h_samp, v_samp, input_mode, progr);
|
||||
GenerateInput(input_mode, jparams, &input);
|
||||
jpegli_mem_dest(&cinfo, &buffer, &buffer_size);
|
||||
if (input_mode != COEFFICIENTS) {
|
||||
cinfo.image_width = input.xsize;
|
||||
cinfo.image_height = input.ysize;
|
||||
cinfo.input_components = input.components;
|
||||
jpegli_set_defaults(&cinfo);
|
||||
jpegli_start_compress(&cinfo, TRUE);
|
||||
jpegli_abort_compress(&cinfo);
|
||||
jpegli_mem_dest(&cinfo, &buffer, &buffer_size);
|
||||
}
|
||||
EncodeWithJpegli(input, jparams, &cinfo);
|
||||
compressed.resize(buffer_size);
|
||||
std::copy_n(buffer, buffer_size, compressed.data());
|
||||
dparams.output_mode =
|
||||
input_mode == COEFFICIENTS ? COEFFICIENTS : PIXELS;
|
||||
dparams.out_color_space = input.color_space;
|
||||
output.Clear();
|
||||
DecodeWithLibjpeg(jparams, dparams, compressed, &output);
|
||||
VerifyOutputImage(input, output,
|
||||
max_rms(quality, h_samp, v_samp));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
EXPECT_TRUE(try_catch_block());
|
||||
jpegli_destroy_compress(&cinfo);
|
||||
if (buffer) free(buffer);
|
||||
}
|
||||
|
||||
TEST(EncodeAPITest, AbbreviatedStreams) {
|
||||
uint8_t* table_stream = nullptr;
|
||||
unsigned long table_stream_size = 0; // NOLINT
|
||||
uint8_t* data_stream = nullptr;
|
||||
unsigned long data_stream_size = 0; // NOLINT
|
||||
{
|
||||
jpeg_compress_struct cinfo;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_compress(&cinfo);
|
||||
jpegli_mem_dest(&cinfo, &table_stream, &table_stream_size);
|
||||
cinfo.input_components = 3;
|
||||
cinfo.in_color_space = JCS_RGB;
|
||||
jpegli_set_defaults(&cinfo);
|
||||
jpegli_write_tables(&cinfo);
|
||||
jpegli_mem_dest(&cinfo, &data_stream, &data_stream_size);
|
||||
cinfo.image_width = 1;
|
||||
cinfo.image_height = 1;
|
||||
cinfo.optimize_coding = FALSE;
|
||||
jpegli_set_progressive_level(&cinfo, 0);
|
||||
jpegli_start_compress(&cinfo, FALSE);
|
||||
JSAMPLE image[3] = {0};
|
||||
JSAMPROW row[] = {image};
|
||||
jpegli_write_scanlines(&cinfo, row, 1);
|
||||
jpegli_finish_compress(&cinfo);
|
||||
return true;
|
||||
};
|
||||
EXPECT_TRUE(try_catch_block());
|
||||
EXPECT_LT(data_stream_size, 50u);
|
||||
jpegli_destroy_compress(&cinfo);
|
||||
}
|
||||
TestImage output;
|
||||
DecodeWithLibjpeg(CompressParams(), DecompressParams(), table_stream,
|
||||
table_stream_size, data_stream, data_stream_size, &output);
|
||||
EXPECT_EQ(1u, output.xsize);
|
||||
EXPECT_EQ(1u, output.ysize);
|
||||
EXPECT_EQ(3u, output.components);
|
||||
EXPECT_EQ(0u, output.pixels[0]);
|
||||
EXPECT_EQ(0u, output.pixels[1]);
|
||||
EXPECT_EQ(0u, output.pixels[2]);
|
||||
if (table_stream) free(table_stream);
|
||||
if (data_stream) free(data_stream);
|
||||
}
|
||||
|
||||
void CopyQuantTables(j_compress_ptr cinfo, uint16_t* quant_tables) {
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
int quant_idx = cinfo->comp_info[c].quant_tbl_no;
|
||||
JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[quant_idx];
|
||||
for (int k = 0; k < DCTSIZE2; ++k) {
|
||||
quant_tables[c * DCTSIZE2 + k] = quant_table->quantval[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EncodeAPITest, QualitySettings) {
|
||||
// Test that jpegli_set_quality, jpegli_set_linear_quality and
|
||||
// jpegli_quality_scaling are consistent with each other.
|
||||
uint16_t quant_tables0[3 * DCTSIZE2];
|
||||
uint16_t quant_tables1[3 * DCTSIZE2];
|
||||
jpeg_compress_struct cinfo;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_compress(&cinfo);
|
||||
cinfo.input_components = 3;
|
||||
cinfo.in_color_space = JCS_RGB;
|
||||
jpegli_set_defaults(&cinfo);
|
||||
for (boolean baseline : {FALSE, TRUE}) {
|
||||
for (int q = 1; q <= 100; ++q) {
|
||||
jpegli_set_quality(&cinfo, q, baseline);
|
||||
CopyQuantTables(&cinfo, quant_tables0);
|
||||
jpegli_set_linear_quality(&cinfo, jpegli_quality_scaling(q), baseline);
|
||||
CopyQuantTables(&cinfo, quant_tables1);
|
||||
EXPECT_EQ(0,
|
||||
memcmp(quant_tables0, quant_tables1, sizeof(quant_tables0)));
|
||||
#if JPEG_LIB_VERSION >= 70
|
||||
for (int i = 0; i < NUM_QUANT_TBLS; ++i) {
|
||||
cinfo.q_scale_factor[i] = jpegli_quality_scaling(q);
|
||||
}
|
||||
jpegli_default_qtables(&cinfo, baseline);
|
||||
CopyQuantTables(&cinfo, quant_tables1);
|
||||
EXPECT_EQ(0,
|
||||
memcmp(quant_tables0, quant_tables1, sizeof(quant_tables0)));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
EXPECT_TRUE(try_catch_block());
|
||||
jpegli_destroy_compress(&cinfo);
|
||||
// Test jpegli_quality_scaling for some specific values .
|
||||
EXPECT_EQ(5000, jpegli_quality_scaling(-1));
|
||||
EXPECT_EQ(5000, jpegli_quality_scaling(0));
|
||||
EXPECT_EQ(5000, jpegli_quality_scaling(1));
|
||||
EXPECT_EQ(100, jpegli_quality_scaling(50));
|
||||
EXPECT_EQ(50, jpegli_quality_scaling(75));
|
||||
EXPECT_EQ(20, jpegli_quality_scaling(90));
|
||||
EXPECT_EQ(0, jpegli_quality_scaling(100));
|
||||
EXPECT_EQ(0, jpegli_quality_scaling(101));
|
||||
}
|
||||
|
||||
std::vector<TestConfig> GenerateTests() {
|
||||
std::vector<TestConfig> all_tests;
|
||||
for (int h_samp : {1, 2}) {
|
||||
for (int v_samp : {1, 2}) {
|
||||
for (int progr : {0, 2}) {
|
||||
for (int optimize : {0, 1}) {
|
||||
if (progr && optimize) continue;
|
||||
TestConfig config;
|
||||
config.jparams.h_sampling = {h_samp, 1, 1};
|
||||
config.jparams.v_sampling = {v_samp, 1, 1};
|
||||
config.jparams.progressive_mode = progr;
|
||||
if (!progr) {
|
||||
config.jparams.optimize_coding = optimize;
|
||||
}
|
||||
const float kMaxBpp[4] = {1.55, 1.4, 1.4, 1.32};
|
||||
const float kMaxDist[4] = {1.95, 2.2, 2.2, 2.0};
|
||||
const int idx = v_samp * 2 + h_samp - 3;
|
||||
config.max_bpp =
|
||||
kMaxBpp[idx] * (optimize ? 0.97 : 1.0) * (progr ? 0.97 : 1.0);
|
||||
config.max_dist = kMaxDist[idx];
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
TestConfig config;
|
||||
config.jparams.quality = 100;
|
||||
config.jparams.h_sampling = {1, 1, 1};
|
||||
config.jparams.v_sampling = {1, 1, 1};
|
||||
config.max_bpp = 6.6;
|
||||
config.max_dist = 0.6;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
{
|
||||
TestConfig config;
|
||||
config.jparams.quality = 80;
|
||||
config.max_bpp = 1.05;
|
||||
config.max_dist = 2.7;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
for (int samp : {1, 2}) {
|
||||
for (int progr : {0, 2}) {
|
||||
for (int optimize : {0, 1}) {
|
||||
if (progr && optimize) continue;
|
||||
TestConfig config;
|
||||
config.input.xsize = 257;
|
||||
config.input.ysize = 265;
|
||||
config.jparams.h_sampling = {samp, 1, 1};
|
||||
config.jparams.v_sampling = {samp, 1, 1};
|
||||
config.jparams.progressive_mode = progr;
|
||||
if (!progr) {
|
||||
config.jparams.optimize_coding = optimize;
|
||||
}
|
||||
config.jparams.use_adaptive_quantization = false;
|
||||
config.max_bpp = 2.05f;
|
||||
config.max_dist = 2.3f;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int h0_samp : {1, 2, 4}) {
|
||||
for (int v0_samp : {1, 2, 4}) {
|
||||
for (int h2_samp : {1, 2, 4}) {
|
||||
for (int v2_samp : {1, 2, 4}) {
|
||||
TestConfig config;
|
||||
config.input.xsize = 137;
|
||||
config.input.ysize = 75;
|
||||
config.jparams.progressive_mode = 2;
|
||||
config.jparams.h_sampling = {h0_samp, 1, h2_samp};
|
||||
config.jparams.v_sampling = {v0_samp, 1, v2_samp};
|
||||
config.max_bpp = 2.5;
|
||||
config.max_dist = 12.0;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int h0_samp : {1, 3}) {
|
||||
for (int v0_samp : {1, 3}) {
|
||||
for (int h2_samp : {1, 3}) {
|
||||
for (int v2_samp : {1, 3}) {
|
||||
TestConfig config;
|
||||
config.input.xsize = 205;
|
||||
config.input.ysize = 99;
|
||||
config.jparams.progressive_mode = 2;
|
||||
config.jparams.h_sampling = {h0_samp, 1, h2_samp};
|
||||
config.jparams.v_sampling = {v0_samp, 1, v2_samp};
|
||||
config.max_bpp = 2.5;
|
||||
config.max_dist = 10.0;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int h0_samp : {1, 2, 3, 4}) {
|
||||
for (int v0_samp : {1, 2, 3, 4}) {
|
||||
TestConfig config;
|
||||
config.input.xsize = 217;
|
||||
config.input.ysize = 129;
|
||||
config.jparams.progressive_mode = 2;
|
||||
config.jparams.h_sampling = {h0_samp, 1, 1};
|
||||
config.jparams.v_sampling = {v0_samp, 1, 1};
|
||||
config.max_bpp = 2.0;
|
||||
config.max_dist = 5.5;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
for (int p = 0; p < 3 + NumTestScanScripts(); ++p) {
|
||||
for (int samp : {1, 2}) {
|
||||
for (int quality : {100, 90, 1}) {
|
||||
for (int r : {0, 1024, 1}) {
|
||||
for (int optimize : {0, 1}) {
|
||||
bool progressive = p == 1 || p == 2 || p > 4;
|
||||
if (progressive && !optimize) continue;
|
||||
TestConfig config;
|
||||
config.input.xsize = 273;
|
||||
config.input.ysize = 265;
|
||||
config.jparams.progressive_mode = p;
|
||||
if (!progressive) {
|
||||
config.jparams.optimize_coding = optimize;
|
||||
}
|
||||
config.jparams.h_sampling = {samp, 1, 1};
|
||||
config.jparams.v_sampling = {samp, 1, 1};
|
||||
config.jparams.quality = quality;
|
||||
config.jparams.restart_interval = r;
|
||||
config.max_bpp = quality == 100 ? 8.0 : 1.9;
|
||||
if (r == 1) {
|
||||
config.max_bpp += 10.0;
|
||||
}
|
||||
config.max_dist = quality == 1 ? 20.0 : 2.1;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
TestConfig config;
|
||||
config.jparams.simple_progression = true;
|
||||
config.max_bpp = 1.48;
|
||||
config.max_dist = 2.0;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
{
|
||||
TestConfig config;
|
||||
config.input_mode = COEFFICIENTS;
|
||||
config.jparams.h_sampling = {2, 1, 1};
|
||||
config.jparams.v_sampling = {2, 1, 1};
|
||||
config.jparams.progressive_mode = 0;
|
||||
config.jparams.optimize_coding = 0;
|
||||
config.max_bpp = 16;
|
||||
config.max_dist = 0.0;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
{
|
||||
TestConfig config;
|
||||
config.jparams.xyb_mode = true;
|
||||
config.jparams.progressive_mode = 2;
|
||||
config.max_bpp = 1.5;
|
||||
config.max_dist = 3.5;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
{
|
||||
TestConfig config;
|
||||
config.jparams.libjpeg_mode = true;
|
||||
config.max_bpp = 2.1;
|
||||
config.max_dist = 1.7;
|
||||
config.jparams.h_sampling = {1, 1, 1};
|
||||
config.jparams.v_sampling = {1, 1, 1};
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
|
||||
for (J_COLOR_SPACE in_color_space :
|
||||
{JCS_RGB, JCS_YCbCr, JCS_GRAYSCALE, JCS_EXT_RGB, JCS_EXT_BGR,
|
||||
JCS_EXT_RGBA, JCS_EXT_BGRA, JCS_EXT_ARGB, JCS_EXT_ABGR}) {
|
||||
for (J_COLOR_SPACE jpeg_color_space : {JCS_RGB, JCS_YCbCr, JCS_GRAYSCALE}) {
|
||||
if (jpeg_color_space == JCS_RGB && in_color_space >= JCS_YCbCr) continue;
|
||||
TestConfig config;
|
||||
config.input.xsize = config.input.ysize = 256;
|
||||
config.input.color_space = in_color_space;
|
||||
config.jparams.set_jpeg_colorspace = true;
|
||||
config.jparams.jpeg_color_space = jpeg_color_space;
|
||||
config.jparams.h_sampling = {1, 1, 1};
|
||||
config.jparams.v_sampling = {1, 1, 1};
|
||||
config.max_bpp = jpeg_color_space == JCS_RGB ? 4.5 : 1.85;
|
||||
config.max_dist = jpeg_color_space == JCS_RGB ? 1.4 : 2.05;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
for (J_COLOR_SPACE in_color_space : {JCS_CMYK, JCS_YCCK}) {
|
||||
for (J_COLOR_SPACE jpeg_color_space : {JCS_CMYK, JCS_YCCK}) {
|
||||
if (jpeg_color_space == JCS_CMYK && in_color_space == JCS_YCCK) continue;
|
||||
TestConfig config;
|
||||
config.input.xsize = config.input.ysize = 256;
|
||||
config.input.color_space = in_color_space;
|
||||
if (in_color_space != jpeg_color_space) {
|
||||
config.jparams.set_jpeg_colorspace = true;
|
||||
config.jparams.jpeg_color_space = jpeg_color_space;
|
||||
}
|
||||
config.jparams.h_sampling = {1, 1, 1, 1};
|
||||
config.jparams.v_sampling = {1, 1, 1, 1};
|
||||
config.max_bpp = jpeg_color_space == JCS_CMYK ? 4.0 : 3.6;
|
||||
config.max_dist = jpeg_color_space == JCS_CMYK ? 1.2 : 1.5;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
{
|
||||
TestConfig config;
|
||||
config.input.color_space = JCS_YCbCr;
|
||||
config.max_bpp = 1.6;
|
||||
config.max_dist = 1.35;
|
||||
config.jparams.h_sampling = {1, 1, 1};
|
||||
config.jparams.v_sampling = {1, 1, 1};
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
for (bool xyb : {false, true}) {
|
||||
TestConfig config;
|
||||
config.input.color_space = JCS_GRAYSCALE;
|
||||
config.jparams.xyb_mode = xyb;
|
||||
config.max_bpp = 1.35;
|
||||
config.max_dist = 1.4;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
for (int channels = 1; channels <= 4; ++channels) {
|
||||
TestConfig config;
|
||||
config.input.color_space = JCS_UNKNOWN;
|
||||
config.input.components = channels;
|
||||
config.max_bpp = 1.35 * channels;
|
||||
config.max_dist = 1.4;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
for (size_t r : {1, 3, 17, 1024}) {
|
||||
for (int progr : {0, 2}) {
|
||||
TestConfig config;
|
||||
config.jparams.restart_interval = r;
|
||||
config.jparams.progressive_mode = progr;
|
||||
config.max_bpp = 1.58 + 5.5 / r;
|
||||
config.max_dist = 2.2;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
for (size_t rr : {1, 3, 8, 100}) {
|
||||
TestConfig config;
|
||||
config.jparams.restart_in_rows = rr;
|
||||
config.max_bpp = 1.6;
|
||||
config.max_dist = 2.2;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
for (int type : {0, 1, 10, 100, 10000}) {
|
||||
for (int scale : {1, 50, 100, 200, 500}) {
|
||||
for (bool add_raw : {false, true}) {
|
||||
for (bool baseline : {true, false}) {
|
||||
if (!baseline && (add_raw || type * scale < 25500)) continue;
|
||||
TestConfig config;
|
||||
config.input.xsize = 64;
|
||||
config.input.ysize = 64;
|
||||
CustomQuantTable table;
|
||||
table.table_type = type;
|
||||
table.scale_factor = scale;
|
||||
table.force_baseline = baseline;
|
||||
table.add_raw = add_raw;
|
||||
table.Generate();
|
||||
config.jparams.optimize_coding = 1;
|
||||
config.jparams.h_sampling = {1, 1, 1};
|
||||
config.jparams.v_sampling = {1, 1, 1};
|
||||
config.jparams.quant_tables.push_back(table);
|
||||
config.jparams.quant_indexes = {0, 0, 0};
|
||||
float q = (type == 0 ? 16 : type) * scale * 0.01f;
|
||||
if (baseline && !add_raw) q = std::max(1.0f, std::min(255.0f, q));
|
||||
config.max_bpp = 1.5f + 25.0f / q;
|
||||
config.max_dist = 0.6f + 0.25f * q;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int qidx = 0; qidx < 8; ++qidx) {
|
||||
if (qidx == 3) continue;
|
||||
TestConfig config;
|
||||
config.input.xsize = 256;
|
||||
config.input.ysize = 256;
|
||||
config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1,
|
||||
(qidx >> 0) & 1};
|
||||
config.jparams.h_sampling = {1, 1, 1};
|
||||
config.jparams.v_sampling = {1, 1, 1};
|
||||
config.max_bpp = 2.25;
|
||||
config.max_dist = 2.8;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
for (int qidx = 0; qidx < 8; ++qidx) {
|
||||
for (int slot_idx = 0; slot_idx < 2; ++slot_idx) {
|
||||
if (qidx == 0 && slot_idx == 0) continue;
|
||||
TestConfig config;
|
||||
config.input.xsize = 256;
|
||||
config.input.ysize = 256;
|
||||
config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1,
|
||||
(qidx >> 0) & 1};
|
||||
config.jparams.h_sampling = {1, 1, 1};
|
||||
config.jparams.v_sampling = {1, 1, 1};
|
||||
CustomQuantTable table;
|
||||
table.slot_idx = slot_idx;
|
||||
table.Generate();
|
||||
config.jparams.quant_tables.push_back(table);
|
||||
config.max_bpp = 2.3;
|
||||
config.max_dist = 2.9;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
for (int qidx = 0; qidx < 8; ++qidx) {
|
||||
for (bool xyb : {false, true}) {
|
||||
TestConfig config;
|
||||
config.input.xsize = 256;
|
||||
config.input.ysize = 256;
|
||||
config.jparams.xyb_mode = xyb;
|
||||
config.jparams.quant_indexes = {(qidx >> 2) & 1, (qidx >> 1) & 1,
|
||||
(qidx >> 0) & 1};
|
||||
if (!xyb) {
|
||||
config.jparams.h_sampling = {1, 1, 1};
|
||||
config.jparams.v_sampling = {1, 1, 1};
|
||||
}
|
||||
{
|
||||
CustomQuantTable table;
|
||||
table.slot_idx = 0;
|
||||
table.Generate();
|
||||
config.jparams.quant_tables.push_back(table);
|
||||
}
|
||||
{
|
||||
CustomQuantTable table;
|
||||
table.slot_idx = 1;
|
||||
table.table_type = 20;
|
||||
table.Generate();
|
||||
config.jparams.quant_tables.push_back(table);
|
||||
}
|
||||
config.max_bpp = 2.0;
|
||||
config.max_dist = 3.85;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
for (bool xyb : {false, true}) {
|
||||
TestConfig config;
|
||||
config.input.xsize = 256;
|
||||
config.input.ysize = 256;
|
||||
config.jparams.xyb_mode = xyb;
|
||||
config.jparams.quant_indexes = {0, 1, 2};
|
||||
if (!xyb) {
|
||||
config.jparams.h_sampling = {1, 1, 1};
|
||||
config.jparams.v_sampling = {1, 1, 1};
|
||||
}
|
||||
{
|
||||
CustomQuantTable table;
|
||||
table.slot_idx = 0;
|
||||
table.Generate();
|
||||
config.jparams.quant_tables.push_back(table);
|
||||
}
|
||||
{
|
||||
CustomQuantTable table;
|
||||
table.slot_idx = 1;
|
||||
table.table_type = 20;
|
||||
table.Generate();
|
||||
config.jparams.quant_tables.push_back(table);
|
||||
}
|
||||
{
|
||||
CustomQuantTable table;
|
||||
table.slot_idx = 2;
|
||||
table.table_type = 30;
|
||||
table.Generate();
|
||||
config.jparams.quant_tables.push_back(table);
|
||||
}
|
||||
config.max_bpp = 1.5;
|
||||
config.max_dist = 3.75;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
{
|
||||
TestConfig config;
|
||||
config.jparams.comp_ids = {7, 17, 177};
|
||||
config.input.xsize = config.input.ysize = 128;
|
||||
config.max_bpp = 2.25;
|
||||
config.max_dist = 2.4;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
for (int override_JFIF : {-1, 0, 1}) {
|
||||
for (int override_Adobe : {-1, 0, 1}) {
|
||||
if (override_JFIF == -1 && override_Adobe == -1) continue;
|
||||
TestConfig config;
|
||||
config.input.xsize = config.input.ysize = 128;
|
||||
config.jparams.override_JFIF = override_JFIF;
|
||||
config.jparams.override_Adobe = override_Adobe;
|
||||
config.max_bpp = 2.25;
|
||||
config.max_dist = 2.4;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
{
|
||||
TestConfig config;
|
||||
config.input.xsize = config.input.ysize = 256;
|
||||
config.max_bpp = 1.85;
|
||||
config.max_dist = 2.05;
|
||||
config.jparams.add_marker = true;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
for (size_t icc_size : {728, 70000, 1000000}) {
|
||||
TestConfig config;
|
||||
config.input.xsize = config.input.ysize = 256;
|
||||
config.max_dist = 2.05;
|
||||
config.jparams.icc.resize(icc_size);
|
||||
for (size_t i = 0; i < icc_size; ++i) {
|
||||
config.jparams.icc[i] = (i * 17) & 0xff;
|
||||
}
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
for (JpegIOMode input_mode : {PIXELS, RAW_DATA, COEFFICIENTS}) {
|
||||
TestConfig config;
|
||||
config.input.xsize = config.input.ysize = 256;
|
||||
config.input_mode = input_mode;
|
||||
if (input_mode == RAW_DATA) {
|
||||
config.input.color_space = JCS_YCbCr;
|
||||
}
|
||||
config.jparams.progressive_mode = 0;
|
||||
config.jparams.optimize_coding = 0;
|
||||
config.jparams.h_sampling = {1, 1, 1};
|
||||
config.jparams.v_sampling = {1, 1, 1};
|
||||
config.max_bpp = 1.85;
|
||||
config.max_dist = 2.05;
|
||||
if (input_mode == COEFFICIENTS) {
|
||||
config.max_bpp = 3.5;
|
||||
config.max_dist = 0.0;
|
||||
}
|
||||
all_tests.push_back(config);
|
||||
config.jparams.use_flat_dc_luma_code = true;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
for (int xsize : {640, 641, 648, 649}) {
|
||||
for (int ysize : {640, 641, 648, 649}) {
|
||||
for (int h_sampling : {1, 2}) {
|
||||
for (int v_sampling : {1, 2}) {
|
||||
if (h_sampling == 1 && v_sampling == 1) continue;
|
||||
for (int progr : {0, 2}) {
|
||||
TestConfig config;
|
||||
config.input.xsize = xsize;
|
||||
config.input.ysize = ysize;
|
||||
config.input.color_space = JCS_YCbCr;
|
||||
config.jparams.h_sampling = {h_sampling, 1, 1};
|
||||
config.jparams.v_sampling = {v_sampling, 1, 1};
|
||||
config.jparams.progressive_mode = progr;
|
||||
config.input_mode = RAW_DATA;
|
||||
config.max_bpp = 1.75;
|
||||
config.max_dist = 2.0;
|
||||
all_tests.push_back(config);
|
||||
config.input_mode = COEFFICIENTS;
|
||||
if (xsize & 1) {
|
||||
config.jparams.add_marker = true;
|
||||
}
|
||||
config.max_bpp = 24.0;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (JpegliDataType data_type : {JPEGLI_TYPE_UINT16, JPEGLI_TYPE_FLOAT}) {
|
||||
for (JpegliEndianness endianness :
|
||||
{JPEGLI_LITTLE_ENDIAN, JPEGLI_BIG_ENDIAN, JPEGLI_NATIVE_ENDIAN}) {
|
||||
J_COLOR_SPACE colorspace[4] = {JCS_GRAYSCALE, JCS_UNKNOWN, JCS_RGB,
|
||||
JCS_CMYK};
|
||||
float max_bpp[4] = {1.32, 2.7, 1.6, 4.0};
|
||||
for (int channels = 1; channels <= 4; ++channels) {
|
||||
TestConfig config;
|
||||
config.input.data_type = data_type;
|
||||
config.input.endianness = endianness;
|
||||
config.input.components = channels;
|
||||
config.input.color_space = colorspace[channels - 1];
|
||||
config.max_bpp = max_bpp[channels - 1];
|
||||
config.max_dist = 2.2;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int smoothing : {1, 5, 50, 100}) {
|
||||
for (int h_sampling : {1, 2}) {
|
||||
for (int v_sampling : {1, 2}) {
|
||||
TestConfig config;
|
||||
config.input.xsize = 257;
|
||||
config.input.ysize = 265;
|
||||
config.jparams.smoothing_factor = smoothing;
|
||||
config.jparams.h_sampling = {h_sampling, 1, 1};
|
||||
config.jparams.v_sampling = {v_sampling, 1, 1};
|
||||
config.max_bpp = 1.85;
|
||||
config.max_dist = 3.05f;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
return all_tests;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const TestConfig& c) {
|
||||
os << c.input;
|
||||
os << c.jparams;
|
||||
if (c.input_mode == RAW_DATA) {
|
||||
os << "RawDataIn";
|
||||
} else if (c.input_mode == COEFFICIENTS) {
|
||||
os << "WriteCoeffs";
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string TestDescription(
|
||||
const testing::TestParamInfo<EncodeAPITestParam::ParamType>& info) {
|
||||
std::stringstream name;
|
||||
name << info.param;
|
||||
return name.str();
|
||||
}
|
||||
|
||||
JPEGLI_INSTANTIATE_TEST_SUITE_P(EncodeAPITest, EncodeAPITestParam,
|
||||
testing::ValuesIn(GenerateTests()),
|
||||
TestDescription);
|
||||
} // namespace
|
||||
} // namespace jpegli
|
||||
@@ -1,237 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/encode_finish.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
#include "lib/jpegli/memory_manager.h"
|
||||
#include "lib/jpegli/quant.h"
|
||||
|
||||
#undef HWY_TARGET_INCLUDE
|
||||
#define HWY_TARGET_INCLUDE "lib/jpegli/encode_finish.cc"
|
||||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/jpegli/dct-inl.h"
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jpegli {
|
||||
namespace HWY_NAMESPACE {
|
||||
|
||||
// These templates are not found via ADL.
|
||||
using hwy::HWY_NAMESPACE::GetLane;
|
||||
|
||||
using D = HWY_FULL(float);
|
||||
using DI = HWY_FULL(int32_t);
|
||||
using DI16 = Rebind<int16_t, HWY_FULL(int32_t)>;
|
||||
|
||||
void ReQuantizeBlock(int16_t* block, const float* qmc, float aq_strength,
|
||||
const float* zero_bias_offset,
|
||||
const float* zero_bias_mul) {
|
||||
D d;
|
||||
DI di;
|
||||
DI16 di16;
|
||||
const auto aq_mul = Set(d, aq_strength);
|
||||
for (size_t k = 0; k < DCTSIZE2; k += Lanes(d)) {
|
||||
const auto in = Load(di16, block + k);
|
||||
const auto val = ConvertTo(d, PromoteTo(di, in));
|
||||
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 iqval = IfThenElseZero(nzero_mask, Round(qval));
|
||||
Store(DemoteTo(di16, ConvertTo(di, iqval)), di16, block + k);
|
||||
}
|
||||
}
|
||||
|
||||
float BlockError(const int16_t* block, const float* qmc, const float* iqmc,
|
||||
const float aq_strength, const float* zero_bias_offset,
|
||||
const float* zero_bias_mul) {
|
||||
D d;
|
||||
DI di;
|
||||
DI16 di16;
|
||||
auto err = Zero(d);
|
||||
const auto scale = Set(d, 1.0 / 16);
|
||||
const auto aq_mul = Set(d, aq_strength);
|
||||
for (size_t k = 0; k < DCTSIZE2; k += Lanes(d)) {
|
||||
const auto in = Load(di16, block + k);
|
||||
const auto val = ConvertTo(d, PromoteTo(di, in));
|
||||
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 iqval = IfThenElseZero(nzero_mask, Round(qval));
|
||||
const auto invq = Load(d, iqmc + k);
|
||||
const auto rval = Mul(iqval, invq);
|
||||
const auto diff = Mul(Sub(val, rval), scale);
|
||||
err = Add(err, Mul(diff, diff));
|
||||
}
|
||||
return GetLane(SumOfLanes(d, err));
|
||||
}
|
||||
|
||||
void ComputeInverseWeights(const float* qmc, float* iqmc) {
|
||||
for (int k = 0; k < 64; ++k) {
|
||||
iqmc[k] = 1.0f / qmc[k];
|
||||
}
|
||||
}
|
||||
|
||||
float ComputePSNR(j_compress_ptr cinfo, int sampling) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
InitQuantizer(cinfo, QuantPass::SEARCH_SECOND_PASS);
|
||||
double error = 0.0;
|
||||
size_t num = 0;
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
const float* qmc = m->quant_mul[c];
|
||||
const size_t h_factor = m->h_factor[c];
|
||||
const size_t 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];
|
||||
HWY_ALIGN float iqmc[64];
|
||||
ComputeInverseWeights(qmc, iqmc);
|
||||
for (JDIMENSION by = 0; by < comp->height_in_blocks; by += sampling) {
|
||||
JBLOCKARRAY blocks = GetBlockRow(cinfo, c, by);
|
||||
const float* qf = m->quant_field.Row(by * v_factor);
|
||||
for (JDIMENSION bx = 0; bx < comp->width_in_blocks; bx += sampling) {
|
||||
error += BlockError(&blocks[0][bx][0], qmc, iqmc, qf[bx * h_factor],
|
||||
zero_bias_offset, zero_bias_mul);
|
||||
num += DCTSIZE2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 4.3429448f * log(num / (error / 255. / 255.));
|
||||
}
|
||||
|
||||
void ReQuantizeCoeffs(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
InitQuantizer(cinfo, QuantPass::SEARCH_SECOND_PASS);
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
const float* qmc = m->quant_mul[c];
|
||||
const size_t h_factor = m->h_factor[c];
|
||||
const size_t 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];
|
||||
for (JDIMENSION by = 0; by < comp->height_in_blocks; ++by) {
|
||||
JBLOCKARRAY block = GetBlockRow(cinfo, c, by);
|
||||
const float* qf = m->quant_field.Row(by * v_factor);
|
||||
for (JDIMENSION bx = 0; bx < comp->width_in_blocks; ++bx) {
|
||||
ReQuantizeBlock(&block[0][bx][0], qmc, qf[bx * h_factor],
|
||||
zero_bias_offset, zero_bias_mul);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
|
||||
#if HWY_ONCE
|
||||
namespace jpegli {
|
||||
namespace {
|
||||
HWY_EXPORT(ComputePSNR);
|
||||
HWY_EXPORT(ReQuantizeCoeffs);
|
||||
|
||||
void ReQuantizeCoeffs(j_compress_ptr cinfo) {
|
||||
HWY_DYNAMIC_DISPATCH(ReQuantizeCoeffs)(cinfo);
|
||||
}
|
||||
|
||||
float ComputePSNR(j_compress_ptr cinfo, int sampling) {
|
||||
return HWY_DYNAMIC_DISPATCH(ComputePSNR)(cinfo, sampling);
|
||||
}
|
||||
|
||||
void UpdateDistance(j_compress_ptr cinfo, float distance) {
|
||||
float distances[NUM_QUANT_TBLS] = {distance, distance, distance};
|
||||
SetQuantMatrices(cinfo, distances, /*add_two_chroma_tables=*/true);
|
||||
}
|
||||
|
||||
float Clamp(float val, float minval, float maxval) {
|
||||
return std::max(minval, std::min(maxval, val));
|
||||
}
|
||||
|
||||
#define PSNR_SEARCH_DBG 0
|
||||
|
||||
float FindDistanceForPSNR(j_compress_ptr cinfo) {
|
||||
constexpr int kMaxIters = 20;
|
||||
const float psnr_target = cinfo->master->psnr_target;
|
||||
const float tolerance = cinfo->master->psnr_tolerance;
|
||||
const float min_dist = cinfo->master->min_distance;
|
||||
const float max_dist = cinfo->master->max_distance;
|
||||
float d = Clamp(1.0f, min_dist, max_dist);
|
||||
for (int sampling : {4, 1}) {
|
||||
float best_diff = std::numeric_limits<float>::max();
|
||||
float best_distance = 0.0f;
|
||||
float best_psnr = 0.0;
|
||||
float dmin = min_dist;
|
||||
float dmax = max_dist;
|
||||
bool found_lower_bound = false;
|
||||
bool found_upper_bound = false;
|
||||
for (int i = 0; i < kMaxIters; ++i) {
|
||||
UpdateDistance(cinfo, d);
|
||||
float psnr = ComputePSNR(cinfo, sampling);
|
||||
if (psnr > psnr_target) {
|
||||
dmin = d;
|
||||
found_lower_bound = true;
|
||||
} else {
|
||||
dmax = d;
|
||||
found_upper_bound = true;
|
||||
}
|
||||
#if (PSNR_SEARCH_DBG > 1)
|
||||
printf("sampling %d iter %2d d %7.4f psnr %.2f", sampling, i, d, psnr);
|
||||
if (found_upper_bound && found_lower_bound) {
|
||||
printf(" d-interval: [ %7.4f .. %7.4f ]", dmin, dmax);
|
||||
}
|
||||
printf("\n");
|
||||
#endif
|
||||
float diff = std::abs(psnr - psnr_target);
|
||||
if (diff < best_diff) {
|
||||
best_diff = diff;
|
||||
best_distance = d;
|
||||
best_psnr = psnr;
|
||||
}
|
||||
if (diff < tolerance * psnr_target || dmin == dmax) {
|
||||
break;
|
||||
}
|
||||
if (!found_lower_bound || !found_upper_bound) {
|
||||
d *= std::exp(0.15f * (psnr - psnr_target));
|
||||
} else {
|
||||
d = 0.5f * (dmin + dmax);
|
||||
}
|
||||
d = Clamp(d, min_dist, max_dist);
|
||||
}
|
||||
d = best_distance;
|
||||
if (sampling == 1 && PSNR_SEARCH_DBG) {
|
||||
printf("Final PSNR %.2f at distance %.4f\n", best_psnr, d);
|
||||
} else {
|
||||
(void)best_psnr;
|
||||
}
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void QuantizetoPSNR(j_compress_ptr cinfo) {
|
||||
float distance = FindDistanceForPSNR(cinfo);
|
||||
UpdateDistance(cinfo, distance);
|
||||
ReQuantizeCoeffs(cinfo);
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
#endif // HWY_ONCE
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_ENCODE_FINISH_H_
|
||||
#define LIB_JPEGLI_ENCODE_FINISH_H_
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void QuantizetoPSNR(j_compress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_ENCODE_FINISH_H_
|
||||
@@ -1,142 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_ENCODE_INTERNAL_H_
|
||||
#define LIB_JPEGLI_ENCODE_INTERNAL_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "lib/jpegli/bit_writer.h"
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/types.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
constexpr unsigned char kICCSignature[12] = {
|
||||
0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00};
|
||||
constexpr int kICCMarker = JPEG_APP0 + 2;
|
||||
|
||||
constexpr int kDefaultProgressiveLevel = 0;
|
||||
|
||||
typedef int16_t coeff_t;
|
||||
|
||||
struct HuffmanCodeTable {
|
||||
int depth[256];
|
||||
int code[256];
|
||||
};
|
||||
|
||||
struct Token {
|
||||
uint8_t context;
|
||||
uint8_t symbol;
|
||||
uint16_t bits;
|
||||
Token(int c, int s, int b) : context(c), symbol(s), bits(b) {}
|
||||
};
|
||||
|
||||
struct TokenArray {
|
||||
Token* tokens;
|
||||
size_t num_tokens;
|
||||
};
|
||||
|
||||
struct RefToken {
|
||||
uint8_t symbol;
|
||||
uint8_t refbits;
|
||||
};
|
||||
|
||||
struct ScanTokenInfo {
|
||||
RefToken* tokens;
|
||||
size_t num_tokens;
|
||||
uint8_t* refbits;
|
||||
uint16_t* eobruns;
|
||||
size_t* restarts;
|
||||
size_t num_restarts;
|
||||
size_t num_nonzeros;
|
||||
size_t num_future_nonzeros;
|
||||
size_t token_offset;
|
||||
size_t restart_interval;
|
||||
size_t MCUs_per_row;
|
||||
size_t MCU_rows_in_scan;
|
||||
size_t blocks_in_MCU;
|
||||
size_t num_blocks;
|
||||
};
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
struct jpeg_comp_master {
|
||||
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::ScanTokenInfo* scan_token_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];
|
||||
// Array of Huffman tables that will be encoded in one or more DHT segments.
|
||||
// In progressive mode we compute all Huffman tables that will be used in any
|
||||
// of the scans, thus we can have more than 4 tables here.
|
||||
JHUFF_TBL* huffman_tables;
|
||||
size_t num_huffman_tables;
|
||||
// Array of num_huffman_tables slot ids, where the ith element is the slot id
|
||||
// of the ith Huffman table, as it appears in the DHT segment. The range of
|
||||
// the slot ids is 0..3 for DC and 16..19 for AC Huffman codes.
|
||||
uint8_t* slot_id_map;
|
||||
// Maps context ids to an index in the huffman_tables array. Each component in
|
||||
// each scan has a DC and AC context id, which are defined as follows:
|
||||
// - DC context id is the component index (relative to cinfo->comp_info) of
|
||||
// the scan component
|
||||
// - AC context ids start at 4 and are increased for each component of each
|
||||
// scan that have AC components (i.e. Se > 0)
|
||||
uint8_t* context_map;
|
||||
size_t num_contexts;
|
||||
// Array of cinfo->num_scans context ids, where the ith element is the context
|
||||
// id of the first AC component of the ith scan.
|
||||
uint8_t* ac_ctx_offset;
|
||||
// Array of num_huffman tables derived coding tables.
|
||||
jpegli::HuffmanCodeTable* coding_tables;
|
||||
float* diff_buffer;
|
||||
jpegli::RowBuffer<float> fuzzy_erosion_tmp;
|
||||
jpegli::RowBuffer<float> pre_erosion;
|
||||
jpegli::RowBuffer<float> quant_field;
|
||||
jvirt_barray_ptr* coeff_buffers;
|
||||
size_t next_input_row;
|
||||
size_t next_iMCU_row;
|
||||
size_t next_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;
|
||||
jpegli::TokenArray* token_arrays;
|
||||
size_t cur_token_array;
|
||||
jpegli::Token* next_token;
|
||||
size_t num_tokens;
|
||||
size_t total_num_tokens;
|
||||
jpegli::RefToken* next_refinement_token;
|
||||
uint8_t* next_refinement_bit;
|
||||
float psnr_target;
|
||||
float psnr_tolerance;
|
||||
float min_distance;
|
||||
float max_distance;
|
||||
};
|
||||
|
||||
#endif // LIB_JPEGLI_ENCODE_INTERNAL_H_
|
||||
@@ -1,265 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/encode_streaming.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "lib/jpegli/bit_writer.h"
|
||||
#include "lib/jpegli/bitstream.h"
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
#include "lib/jpegli/entropy_coding.h"
|
||||
#include "lib/jpegli/memory_manager.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
#undef HWY_TARGET_INCLUDE
|
||||
#define HWY_TARGET_INCLUDE "lib/jpegli/encode_streaming.cc"
|
||||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/jpegli/dct-inl.h"
|
||||
#include "lib/jpegli/entropy_coding-inl.h"
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jpegli {
|
||||
namespace HWY_NAMESPACE {
|
||||
|
||||
static const int kStreamingModeCoefficients = 0;
|
||||
static const int kStreamingModeTokens = 1;
|
||||
static const int kStreamingModeBits = 2;
|
||||
|
||||
namespace {
|
||||
void ZigZagShuffle(int32_t* JXL_RESTRICT block) {
|
||||
// TODO(szabadka) SIMDify this.
|
||||
int32_t tmp[DCTSIZE2];
|
||||
tmp[0] = block[0];
|
||||
tmp[1] = block[1];
|
||||
tmp[2] = block[8];
|
||||
tmp[3] = block[16];
|
||||
tmp[4] = block[9];
|
||||
tmp[5] = block[2];
|
||||
tmp[6] = block[3];
|
||||
tmp[7] = block[10];
|
||||
tmp[8] = block[17];
|
||||
tmp[9] = block[24];
|
||||
tmp[10] = block[32];
|
||||
tmp[11] = block[25];
|
||||
tmp[12] = block[18];
|
||||
tmp[13] = block[11];
|
||||
tmp[14] = block[4];
|
||||
tmp[15] = block[5];
|
||||
tmp[16] = block[12];
|
||||
tmp[17] = block[19];
|
||||
tmp[18] = block[26];
|
||||
tmp[19] = block[33];
|
||||
tmp[20] = block[40];
|
||||
tmp[21] = block[48];
|
||||
tmp[22] = block[41];
|
||||
tmp[23] = block[34];
|
||||
tmp[24] = block[27];
|
||||
tmp[25] = block[20];
|
||||
tmp[26] = block[13];
|
||||
tmp[27] = block[6];
|
||||
tmp[28] = block[7];
|
||||
tmp[29] = block[14];
|
||||
tmp[30] = block[21];
|
||||
tmp[31] = block[28];
|
||||
tmp[32] = block[35];
|
||||
tmp[33] = block[42];
|
||||
tmp[34] = block[49];
|
||||
tmp[35] = block[56];
|
||||
tmp[36] = block[57];
|
||||
tmp[37] = block[50];
|
||||
tmp[38] = block[43];
|
||||
tmp[39] = block[36];
|
||||
tmp[40] = block[29];
|
||||
tmp[41] = block[22];
|
||||
tmp[42] = block[15];
|
||||
tmp[43] = block[23];
|
||||
tmp[44] = block[30];
|
||||
tmp[45] = block[37];
|
||||
tmp[46] = block[44];
|
||||
tmp[47] = block[51];
|
||||
tmp[48] = block[58];
|
||||
tmp[49] = block[59];
|
||||
tmp[50] = block[52];
|
||||
tmp[51] = block[45];
|
||||
tmp[52] = block[38];
|
||||
tmp[53] = block[31];
|
||||
tmp[54] = block[39];
|
||||
tmp[55] = block[46];
|
||||
tmp[56] = block[53];
|
||||
tmp[57] = block[60];
|
||||
tmp[58] = block[61];
|
||||
tmp[59] = block[54];
|
||||
tmp[60] = block[47];
|
||||
tmp[61] = block[55];
|
||||
tmp[62] = block[62];
|
||||
tmp[63] = block[63];
|
||||
memcpy(block, tmp, DCTSIZE2 * sizeof(tmp[0]));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
template <int kMode>
|
||||
void ProcessiMCURow(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
JpegBitWriter* bw = &m->bw;
|
||||
int xsize_mcus = DivCeil(cinfo->image_width, 8 * cinfo->max_h_samp_factor);
|
||||
int ysize_mcus = DivCeil(cinfo->image_height, 8 * cinfo->max_v_samp_factor);
|
||||
size_t mcu_y = m->next_iMCU_row;
|
||||
int32_t* block = m->block_tmp;
|
||||
int32_t* symbols = m->block_tmp + DCTSIZE2;
|
||||
int32_t* nonzero_idx = m->block_tmp + 3 * DCTSIZE2;
|
||||
coeff_t* JXL_RESTRICT last_dc_coeff = m->last_dc_coeff;
|
||||
bool adaptive_quant = m->use_adaptive_quantization && m->psnr_target == 0;
|
||||
JBLOCKARRAY blocks[kMaxComponents];
|
||||
if (kMode == kStreamingModeCoefficients) {
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
int by0 = mcu_y * 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);
|
||||
blocks[c] = (*cinfo->mem->access_virt_barray)(
|
||||
reinterpret_cast<j_common_ptr>(cinfo), m->coeff_buffers[c], by0,
|
||||
max_block_rows, true);
|
||||
}
|
||||
}
|
||||
if (kMode == kStreamingModeTokens) {
|
||||
TokenArray* ta = &m->token_arrays[m->cur_token_array];
|
||||
int max_tokens_per_mcu_row = MaxNumTokensPerMCURow(cinfo);
|
||||
if (ta->num_tokens + max_tokens_per_mcu_row > m->num_tokens) {
|
||||
if (ta->tokens) {
|
||||
m->total_num_tokens += ta->num_tokens;
|
||||
++m->cur_token_array;
|
||||
ta = &m->token_arrays[m->cur_token_array];
|
||||
}
|
||||
m->num_tokens =
|
||||
EstimateNumTokens(cinfo, mcu_y, ysize_mcus, m->total_num_tokens,
|
||||
max_tokens_per_mcu_row);
|
||||
ta->tokens = Allocate<Token>(cinfo, m->num_tokens, JPOOL_IMAGE);
|
||||
m->next_token = ta->tokens;
|
||||
}
|
||||
}
|
||||
const float* imcu_start[kMaxComponents];
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
imcu_start[c] = m->raw_data[c]->Row(mcu_y * comp->v_samp_factor * DCTSIZE);
|
||||
}
|
||||
const float* qf = nullptr;
|
||||
if (adaptive_quant) {
|
||||
qf = m->quant_field.Row(0);
|
||||
}
|
||||
HuffmanCodeTable* dc_code = nullptr;
|
||||
HuffmanCodeTable* ac_code = nullptr;
|
||||
const size_t qf_stride = m->quant_field.stride();
|
||||
for (int mcu_x = 0; mcu_x < xsize_mcus; ++mcu_x) {
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
if (kMode == kStreamingModeBits) {
|
||||
dc_code = &m->coding_tables[m->context_map[c]];
|
||||
ac_code = &m->coding_tables[m->context_map[c + 4]];
|
||||
}
|
||||
float* JXL_RESTRICT qmc = m->quant_mul[c];
|
||||
const size_t stride = m->raw_data[c]->stride();
|
||||
const int h_factor = m->h_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) {
|
||||
for (int ix = 0; ix < comp->h_samp_factor; ++ix) {
|
||||
size_t by = mcu_y * comp->v_samp_factor + iy;
|
||||
size_t bx = mcu_x * comp->h_samp_factor + ix;
|
||||
if (bx >= comp->width_in_blocks || by >= comp->height_in_blocks) {
|
||||
if (kMode == kStreamingModeTokens) {
|
||||
*m->next_token++ = Token(c, 0, 0);
|
||||
*m->next_token++ = Token(c + 4, 0, 0);
|
||||
} else if (kMode == kStreamingModeBits) {
|
||||
WriteBits(bw, dc_code->depth[0], dc_code->code[0]);
|
||||
WriteBits(bw, ac_code->depth[0], ac_code->code[0]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (adaptive_quant) {
|
||||
aq_strength = qf[iy * qf_stride + bx * h_factor];
|
||||
}
|
||||
const float* pixels = imcu_start[c] + (iy * stride + bx) * DCTSIZE;
|
||||
ComputeCoefficientBlock(pixels, stride, qmc, last_dc_coeff[c],
|
||||
aq_strength, zero_bias_offset, zero_bias_mul,
|
||||
m->dct_buffer, block);
|
||||
if (kMode == kStreamingModeCoefficients) {
|
||||
JCOEF* cblock = &blocks[c][iy][bx][0];
|
||||
for (int k = 0; k < DCTSIZE2; ++k) {
|
||||
cblock[k] = block[kJPEGNaturalOrder[k]];
|
||||
}
|
||||
}
|
||||
block[0] -= last_dc_coeff[c];
|
||||
last_dc_coeff[c] += block[0];
|
||||
if (kMode == kStreamingModeTokens) {
|
||||
ComputeTokensForBlock<int32_t, false>(block, 0, c, c + 4,
|
||||
&m->next_token);
|
||||
} else if (kMode == kStreamingModeBits) {
|
||||
ZigZagShuffle(block);
|
||||
const int num_nonzeros = CompactBlock(block, nonzero_idx);
|
||||
const bool emit_eob = nonzero_idx[num_nonzeros - 1] < 1008;
|
||||
ComputeSymbols(num_nonzeros, nonzero_idx, block, symbols);
|
||||
WriteBlock(symbols, block, num_nonzeros, emit_eob, dc_code, ac_code,
|
||||
bw);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (kMode == kStreamingModeTokens) {
|
||||
TokenArray* ta = &m->token_arrays[m->cur_token_array];
|
||||
ta->num_tokens = m->next_token - ta->tokens;
|
||||
ScanTokenInfo* sti = &m->scan_token_info[0];
|
||||
sti->num_tokens = m->total_num_tokens + ta->num_tokens;
|
||||
sti->restarts[0] = sti->num_tokens;
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeCoefficientsForiMCURow(j_compress_ptr cinfo) {
|
||||
ProcessiMCURow<kStreamingModeCoefficients>(cinfo);
|
||||
}
|
||||
|
||||
void ComputeTokensForiMCURow(j_compress_ptr cinfo) {
|
||||
ProcessiMCURow<kStreamingModeTokens>(cinfo);
|
||||
}
|
||||
|
||||
void WriteiMCURow(j_compress_ptr cinfo) {
|
||||
ProcessiMCURow<kStreamingModeBits>(cinfo);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
|
||||
#if HWY_ONCE
|
||||
namespace jpegli {
|
||||
HWY_EXPORT(ComputeCoefficientsForiMCURow);
|
||||
HWY_EXPORT(ComputeTokensForiMCURow);
|
||||
HWY_EXPORT(WriteiMCURow);
|
||||
|
||||
void ComputeCoefficientsForiMCURow(j_compress_ptr cinfo) {
|
||||
HWY_DYNAMIC_DISPATCH(ComputeCoefficientsForiMCURow)(cinfo);
|
||||
}
|
||||
|
||||
void ComputeTokensForiMCURow(j_compress_ptr cinfo) {
|
||||
HWY_DYNAMIC_DISPATCH(ComputeTokensForiMCURow)(cinfo);
|
||||
}
|
||||
|
||||
void WriteiMCURow(j_compress_ptr cinfo) {
|
||||
HWY_DYNAMIC_DISPATCH(WriteiMCURow)(cinfo);
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
#endif // HWY_ONCE
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_ENCODE_STREAMING_H_
|
||||
#define LIB_JPEGLI_ENCODE_STREAMING_H_
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void ComputeCoefficientsForiMCURow(j_compress_ptr cinfo);
|
||||
|
||||
void ComputeTokensForiMCURow(j_compress_ptr cinfo);
|
||||
|
||||
void WriteiMCURow(j_compress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_ENCODE_STREAMING_H_
|
||||
@@ -1,223 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
|
||||
#if defined(LIB_JPEGLI_ENTROPY_CODING_INL_H_) == defined(HWY_TARGET_TOGGLE)
|
||||
#ifdef LIB_JPEGLI_ENTROPY_CODING_INL_H_
|
||||
#undef LIB_JPEGLI_ENTROPY_CODING_INL_H_
|
||||
#else
|
||||
#define LIB_JPEGLI_ENTROPY_CODING_INL_H_
|
||||
#endif
|
||||
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/jxl/base/bits.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::And;
|
||||
using hwy::HWY_NAMESPACE::AndNot;
|
||||
using hwy::HWY_NAMESPACE::Compress;
|
||||
using hwy::HWY_NAMESPACE::CountTrue;
|
||||
using hwy::HWY_NAMESPACE::Eq;
|
||||
using hwy::HWY_NAMESPACE::GetLane;
|
||||
using hwy::HWY_NAMESPACE::MaskFromVec;
|
||||
using hwy::HWY_NAMESPACE::Max;
|
||||
using hwy::HWY_NAMESPACE::Not;
|
||||
using hwy::HWY_NAMESPACE::Or;
|
||||
using hwy::HWY_NAMESPACE::ShiftRight;
|
||||
using hwy::HWY_NAMESPACE::Shl;
|
||||
using hwy::HWY_NAMESPACE::Sub;
|
||||
|
||||
using DI = HWY_FULL(int32_t);
|
||||
constexpr DI di;
|
||||
|
||||
template <class V>
|
||||
JXL_INLINE V NumBits(const V x) {
|
||||
// TODO(szabadka) Add faster implementations for some specific architectures.
|
||||
const auto b1 = And(x, Set(di, 1));
|
||||
const auto b2 = And(x, Set(di, 2));
|
||||
const auto b3 = Sub((And(x, Set(di, 4))), Set(di, 1));
|
||||
const auto b4 = Sub((And(x, Set(di, 8))), Set(di, 4));
|
||||
const auto b5 = Sub((And(x, Set(di, 16))), Set(di, 11));
|
||||
const auto b6 = Sub((And(x, Set(di, 32))), Set(di, 26));
|
||||
const auto b7 = Sub((And(x, Set(di, 64))), Set(di, 57));
|
||||
const auto b8 = Sub((And(x, Set(di, 128))), Set(di, 120));
|
||||
const auto b9 = Sub((And(x, Set(di, 256))), Set(di, 247));
|
||||
const auto b10 = Sub((And(x, Set(di, 512))), Set(di, 502));
|
||||
const auto b11 = Sub((And(x, Set(di, 1024))), Set(di, 1013));
|
||||
const auto b12 = Sub((And(x, Set(di, 2048))), Set(di, 2036));
|
||||
return Max(Max(Max(Max(b1, b2), Max(b3, b4)), Max(Max(b5, b6), Max(b7, b8))),
|
||||
Max(Max(b9, b10), Max(b11, b12)));
|
||||
}
|
||||
|
||||
// Coefficient indexes pre-multiplied by 16 for the symbol calculation.
|
||||
HWY_ALIGN constexpr int32_t kIndexes[64] = {
|
||||
0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192,
|
||||
208, 224, 240, 256, 272, 288, 304, 320, 336, 352, 368, 384, 400,
|
||||
416, 432, 448, 464, 480, 496, 512, 528, 544, 560, 576, 592, 608,
|
||||
624, 640, 656, 672, 688, 704, 720, 736, 752, 768, 784, 800, 816,
|
||||
832, 848, 864, 880, 896, 912, 928, 944, 960, 976, 992, 1008,
|
||||
};
|
||||
|
||||
JXL_INLINE int CompactBlock(int32_t* JXL_RESTRICT block,
|
||||
int32_t* JXL_RESTRICT nonzero_idx) {
|
||||
const auto zero = Zero(di);
|
||||
HWY_ALIGN constexpr int32_t dc_mask_lanes[HWY_LANES(DI)] = {-1};
|
||||
const auto dc_mask = MaskFromVec(Load(di, dc_mask_lanes));
|
||||
int num_nonzeros = 0;
|
||||
int k = 0;
|
||||
{
|
||||
const auto coef = Load(di, block);
|
||||
const auto idx = Load(di, kIndexes);
|
||||
const auto nonzero_mask = Or(dc_mask, Not(Eq(coef, zero)));
|
||||
const auto nzero_coef = Compress(coef, nonzero_mask);
|
||||
const auto nzero_idx = Compress(idx, nonzero_mask);
|
||||
StoreU(nzero_coef, di, &block[num_nonzeros]);
|
||||
StoreU(nzero_idx, di, &nonzero_idx[num_nonzeros]);
|
||||
num_nonzeros += CountTrue(di, nonzero_mask);
|
||||
k += Lanes(di);
|
||||
}
|
||||
for (; k < DCTSIZE2; k += Lanes(di)) {
|
||||
const auto coef = Load(di, &block[k]);
|
||||
const auto idx = Load(di, &kIndexes[k]);
|
||||
const auto nonzero_mask = Not(Eq(coef, zero));
|
||||
const auto nzero_coef = Compress(coef, nonzero_mask);
|
||||
const auto nzero_idx = Compress(idx, nonzero_mask);
|
||||
StoreU(nzero_coef, di, &block[num_nonzeros]);
|
||||
StoreU(nzero_idx, di, &nonzero_idx[num_nonzeros]);
|
||||
num_nonzeros += CountTrue(di, nonzero_mask);
|
||||
}
|
||||
return num_nonzeros;
|
||||
}
|
||||
|
||||
JXL_INLINE void ComputeSymbols(const int num_nonzeros,
|
||||
int32_t* JXL_RESTRICT nonzero_idx,
|
||||
int32_t* JXL_RESTRICT block,
|
||||
int32_t* JXL_RESTRICT symbols) {
|
||||
nonzero_idx[-1] = -16;
|
||||
const auto one = Set(di, 1);
|
||||
const auto offset = Set(di, 16);
|
||||
for (int i = 0; i < num_nonzeros; i += Lanes(di)) {
|
||||
const auto idx = Load(di, &nonzero_idx[i]);
|
||||
const auto prev_idx = LoadU(di, &nonzero_idx[i - 1]);
|
||||
const auto coeff = Load(di, &block[i]);
|
||||
const auto nbits = NumBits(Abs(coeff));
|
||||
const auto mask = ShiftRight<8 * sizeof(int32_t) - 1>(coeff);
|
||||
const auto bits = And(Add(coeff, mask), Sub(Shl(one, nbits), one));
|
||||
const auto symbol = Sub(Add(nbits, idx), Add(prev_idx, offset));
|
||||
Store(symbol, di, symbols + i);
|
||||
Store(bits, di, block + i);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
int NumNonZero8x8ExceptDC(const T* block) {
|
||||
const HWY_CAPPED(T, 8) cdi;
|
||||
|
||||
const auto zero = Zero(cdi);
|
||||
// Add FFFF for every zero coefficient, negate to get #zeros.
|
||||
auto neg_sum_zero = zero;
|
||||
{
|
||||
// First row has DC, so mask
|
||||
const size_t y = 0;
|
||||
HWY_ALIGN const T dc_mask_lanes[8] = {-1};
|
||||
|
||||
for (size_t x = 0; x < 8; x += Lanes(cdi)) {
|
||||
const auto dc_mask = Load(cdi, dc_mask_lanes + x);
|
||||
|
||||
// DC counts as zero so we don't include it in nzeros.
|
||||
const auto coef = AndNot(dc_mask, Load(cdi, &block[y * 8 + x]));
|
||||
|
||||
neg_sum_zero = Add(neg_sum_zero, VecFromMask(cdi, Eq(coef, zero)));
|
||||
}
|
||||
}
|
||||
// Remaining rows: no mask
|
||||
for (size_t y = 1; y < 8; y++) {
|
||||
for (size_t x = 0; x < 8; x += Lanes(cdi)) {
|
||||
const auto coef = Load(cdi, &block[y * 8 + x]);
|
||||
neg_sum_zero = Add(neg_sum_zero, VecFromMask(cdi, Eq(coef, zero)));
|
||||
}
|
||||
}
|
||||
|
||||
// We want 64 - sum_zero, add because neg_sum_zero is already negated.
|
||||
return kDCTBlockSize + GetLane(SumOfLanes(cdi, neg_sum_zero));
|
||||
}
|
||||
|
||||
template <typename T, bool zig_zag_order>
|
||||
void ComputeTokensForBlock(const T* block, int last_dc, int dc_ctx, int ac_ctx,
|
||||
Token** tokens_ptr) {
|
||||
Token* next_token = *tokens_ptr;
|
||||
coeff_t temp2;
|
||||
coeff_t temp;
|
||||
temp = block[0] - last_dc;
|
||||
if (temp == 0) {
|
||||
*next_token++ = Token(dc_ctx, 0, 0);
|
||||
} else {
|
||||
temp2 = temp;
|
||||
if (temp < 0) {
|
||||
temp = -temp;
|
||||
temp2--;
|
||||
}
|
||||
int dc_nbits = jxl::FloorLog2Nonzero<uint32_t>(temp) + 1;
|
||||
int dc_mask = (1 << dc_nbits) - 1;
|
||||
*next_token++ = Token(dc_ctx, dc_nbits, temp2 & dc_mask);
|
||||
}
|
||||
int num_nonzeros = NumNonZero8x8ExceptDC(block);
|
||||
for (int k = 1; k < 64; ++k) {
|
||||
if (num_nonzeros == 0) {
|
||||
*next_token++ = Token(ac_ctx, 0, 0);
|
||||
break;
|
||||
}
|
||||
int r = 0;
|
||||
if (zig_zag_order) {
|
||||
while ((temp = block[k]) == 0) {
|
||||
r++;
|
||||
k++;
|
||||
}
|
||||
} else {
|
||||
while ((temp = block[kJPEGNaturalOrder[k]]) == 0) {
|
||||
r++;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
--num_nonzeros;
|
||||
if (temp < 0) {
|
||||
temp = -temp;
|
||||
temp2 = ~temp;
|
||||
} else {
|
||||
temp2 = temp;
|
||||
}
|
||||
while (r > 15) {
|
||||
*next_token++ = Token(ac_ctx, 0xf0, 0);
|
||||
r -= 16;
|
||||
}
|
||||
int ac_nbits = jxl::FloorLog2Nonzero<uint32_t>(temp) + 1;
|
||||
int ac_mask = (1 << ac_nbits) - 1;
|
||||
int symbol = (r << 4u) + ac_nbits;
|
||||
*next_token++ = Token(ac_ctx, symbol, temp2 & ac_mask);
|
||||
}
|
||||
*tokens_ptr = next_token;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
#endif // LIB_JPEGLI_ENTROPY_CODING_INL_H_
|
||||
@@ -1,849 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/entropy_coding.h"
|
||||
|
||||
#include <jxl/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jpegli/huffman.h"
|
||||
#include "lib/jpegli/memory_manager.h"
|
||||
#include "lib/jxl/base/bits.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
#undef HWY_TARGET_INCLUDE
|
||||
#define HWY_TARGET_INCLUDE "lib/jpegli/entropy_coding.cc"
|
||||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/jpegli/entropy_coding-inl.h"
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jpegli {
|
||||
namespace HWY_NAMESPACE {
|
||||
|
||||
void ComputeTokensSequential(const coeff_t* block, int last_dc, int dc_ctx,
|
||||
int ac_ctx, Token** tokens_ptr) {
|
||||
ComputeTokensForBlock<coeff_t, true>(block, last_dc, dc_ctx, ac_ctx,
|
||||
tokens_ptr);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
|
||||
#if HWY_ONCE
|
||||
namespace jpegli {
|
||||
|
||||
size_t MaxNumTokensPerMCURow(j_compress_ptr cinfo) {
|
||||
int MCUs_per_row = DivCeil(cinfo->image_width, 8 * cinfo->max_h_samp_factor);
|
||||
size_t blocks_per_mcu = 0;
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
blocks_per_mcu +=
|
||||
static_cast<size_t>(comp->h_samp_factor) * comp->v_samp_factor;
|
||||
}
|
||||
return kDCTBlockSize * blocks_per_mcu * MCUs_per_row;
|
||||
}
|
||||
|
||||
size_t EstimateNumTokens(j_compress_ptr cinfo, size_t mcu_y, size_t ysize_mcus,
|
||||
size_t num_tokens, size_t max_per_row) {
|
||||
size_t estimate;
|
||||
if (mcu_y == 0) {
|
||||
estimate = 16 * max_per_row;
|
||||
} else {
|
||||
estimate = (4 * ysize_mcus * num_tokens) / (3 * mcu_y);
|
||||
}
|
||||
size_t mcus_left = ysize_mcus - mcu_y;
|
||||
return std::min(mcus_left * max_per_row,
|
||||
std::max(max_per_row, estimate - num_tokens));
|
||||
}
|
||||
|
||||
namespace {
|
||||
HWY_EXPORT(ComputeTokensSequential);
|
||||
|
||||
void TokenizeProgressiveDC(const coeff_t* coeffs, int context, int Al,
|
||||
coeff_t* last_dc_coeff, Token** next_token) {
|
||||
coeff_t temp2;
|
||||
coeff_t temp;
|
||||
temp2 = coeffs[0] >> Al;
|
||||
temp = temp2 - *last_dc_coeff;
|
||||
*last_dc_coeff = temp2;
|
||||
temp2 = temp;
|
||||
if (temp < 0) {
|
||||
temp = -temp;
|
||||
temp2--;
|
||||
}
|
||||
int nbits = (temp == 0) ? 0 : (jxl::FloorLog2Nonzero<uint32_t>(temp) + 1);
|
||||
int bits = temp2 & ((1 << nbits) - 1);
|
||||
*(*next_token)++ = Token(context, nbits, bits);
|
||||
}
|
||||
|
||||
void TokenizeACProgressiveScan(j_compress_ptr cinfo, int scan_index,
|
||||
int context, ScanTokenInfo* sti) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
const jpeg_scan_info* scan_info = &cinfo->scan_info[scan_index];
|
||||
const int comp_idx = scan_info->component_index[0];
|
||||
const jpeg_component_info* comp = &cinfo->comp_info[comp_idx];
|
||||
const int Al = scan_info->Al;
|
||||
const int Ss = scan_info->Ss;
|
||||
const int Se = scan_info->Se;
|
||||
const size_t restart_interval = sti->restart_interval;
|
||||
int restarts_to_go = restart_interval;
|
||||
size_t num_blocks =
|
||||
static_cast<size_t>(comp->height_in_blocks) * comp->width_in_blocks;
|
||||
size_t num_restarts =
|
||||
restart_interval > 0 ? DivCeil(num_blocks, restart_interval) : 1;
|
||||
size_t restart_idx = 0;
|
||||
int eob_run = 0;
|
||||
TokenArray* ta = &m->token_arrays[m->cur_token_array];
|
||||
sti->token_offset = m->total_num_tokens + ta->num_tokens;
|
||||
sti->restarts = Allocate<size_t>(cinfo, num_restarts, JPOOL_IMAGE);
|
||||
const auto emit_eob_run = [&]() {
|
||||
int nbits = jxl::FloorLog2Nonzero<uint32_t>(eob_run);
|
||||
int symbol = nbits << 4u;
|
||||
*m->next_token++ = Token(context, symbol, eob_run & ((1 << nbits) - 1));
|
||||
eob_run = 0;
|
||||
};
|
||||
for (JDIMENSION by = 0; by < comp->height_in_blocks; ++by) {
|
||||
JBLOCKARRAY blocks = (*cinfo->mem->access_virt_barray)(
|
||||
reinterpret_cast<j_common_ptr>(cinfo), m->coeff_buffers[comp_idx], by,
|
||||
1, FALSE);
|
||||
// Each coefficient can appear in at most one token, but we have to reserve
|
||||
// one extra EOBrun token that was rolled over from the previous block-row
|
||||
// and has to be flushed at the end.
|
||||
int max_tokens_per_row = 1 + comp->width_in_blocks * (Se - Ss + 1);
|
||||
if (ta->num_tokens + max_tokens_per_row > m->num_tokens) {
|
||||
if (ta->tokens) {
|
||||
m->total_num_tokens += ta->num_tokens;
|
||||
++m->cur_token_array;
|
||||
ta = &m->token_arrays[m->cur_token_array];
|
||||
}
|
||||
m->num_tokens =
|
||||
EstimateNumTokens(cinfo, by, comp->height_in_blocks,
|
||||
m->total_num_tokens, max_tokens_per_row);
|
||||
ta->tokens = Allocate<Token>(cinfo, m->num_tokens, JPOOL_IMAGE);
|
||||
m->next_token = ta->tokens;
|
||||
}
|
||||
for (JDIMENSION bx = 0; bx < comp->width_in_blocks; ++bx) {
|
||||
if (restart_interval > 0 && restarts_to_go == 0) {
|
||||
if (eob_run > 0) emit_eob_run();
|
||||
ta->num_tokens = m->next_token - ta->tokens;
|
||||
sti->restarts[restart_idx++] = m->total_num_tokens + ta->num_tokens;
|
||||
restarts_to_go = restart_interval;
|
||||
}
|
||||
const coeff_t* block = &blocks[0][bx][0];
|
||||
coeff_t temp2;
|
||||
coeff_t temp;
|
||||
int r = 0;
|
||||
int num_nzeros = 0;
|
||||
int num_future_nzeros = 0;
|
||||
for (int k = Ss; k <= Se; ++k) {
|
||||
temp = block[k];
|
||||
if (temp == 0) {
|
||||
r++;
|
||||
continue;
|
||||
}
|
||||
if (temp < 0) {
|
||||
temp = -temp;
|
||||
temp >>= Al;
|
||||
temp2 = ~temp;
|
||||
} else {
|
||||
temp >>= Al;
|
||||
temp2 = temp;
|
||||
}
|
||||
if (temp == 0) {
|
||||
r++;
|
||||
num_future_nzeros++;
|
||||
continue;
|
||||
}
|
||||
if (eob_run > 0) emit_eob_run();
|
||||
while (r > 15) {
|
||||
*m->next_token++ = Token(context, 0xf0, 0);
|
||||
r -= 16;
|
||||
}
|
||||
int nbits = jxl::FloorLog2Nonzero<uint32_t>(temp) + 1;
|
||||
int symbol = (r << 4u) + nbits;
|
||||
*m->next_token++ = Token(context, symbol, temp2 & ((1 << nbits) - 1));
|
||||
++num_nzeros;
|
||||
r = 0;
|
||||
}
|
||||
if (r > 0) {
|
||||
++eob_run;
|
||||
if (eob_run == 0x7FFF) emit_eob_run();
|
||||
}
|
||||
sti->num_nonzeros += num_nzeros;
|
||||
sti->num_future_nonzeros += num_future_nzeros;
|
||||
--restarts_to_go;
|
||||
}
|
||||
ta->num_tokens = m->next_token - ta->tokens;
|
||||
}
|
||||
if (eob_run > 0) {
|
||||
emit_eob_run();
|
||||
++ta->num_tokens;
|
||||
}
|
||||
sti->num_tokens = m->total_num_tokens + ta->num_tokens - sti->token_offset;
|
||||
sti->restarts[restart_idx++] = m->total_num_tokens + ta->num_tokens;
|
||||
}
|
||||
|
||||
void TokenizeACRefinementScan(j_compress_ptr cinfo, int scan_index,
|
||||
ScanTokenInfo* sti) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
const jpeg_scan_info* scan_info = &cinfo->scan_info[scan_index];
|
||||
const int comp_idx = scan_info->component_index[0];
|
||||
const jpeg_component_info* comp = &cinfo->comp_info[comp_idx];
|
||||
const int Al = scan_info->Al;
|
||||
const int Ss = scan_info->Ss;
|
||||
const int Se = scan_info->Se;
|
||||
const size_t restart_interval = sti->restart_interval;
|
||||
int restarts_to_go = restart_interval;
|
||||
RefToken token;
|
||||
int eob_run = 0;
|
||||
int eob_refbits = 0;
|
||||
size_t num_blocks =
|
||||
static_cast<size_t>(comp->height_in_blocks) * comp->width_in_blocks;
|
||||
size_t num_restarts =
|
||||
restart_interval > 0 ? DivCeil(num_blocks, restart_interval) : 1;
|
||||
sti->tokens = m->next_refinement_token;
|
||||
sti->refbits = m->next_refinement_bit;
|
||||
sti->eobruns = Allocate<uint16_t>(cinfo, num_blocks / 2, JPOOL_IMAGE);
|
||||
sti->restarts = Allocate<size_t>(cinfo, num_restarts, JPOOL_IMAGE);
|
||||
RefToken* next_token = sti->tokens;
|
||||
RefToken* next_eob_token = next_token;
|
||||
uint8_t* next_ref_bit = sti->refbits;
|
||||
uint16_t* next_eobrun = sti->eobruns;
|
||||
size_t restart_idx = 0;
|
||||
for (JDIMENSION by = 0; by < comp->height_in_blocks; ++by) {
|
||||
JBLOCKARRAY blocks = (*cinfo->mem->access_virt_barray)(
|
||||
reinterpret_cast<j_common_ptr>(cinfo), m->coeff_buffers[comp_idx], by,
|
||||
1, FALSE);
|
||||
for (JDIMENSION bx = 0; bx < comp->width_in_blocks; ++bx) {
|
||||
if (restart_interval > 0 && restarts_to_go == 0) {
|
||||
sti->restarts[restart_idx++] = next_token - sti->tokens;
|
||||
restarts_to_go = restart_interval;
|
||||
next_eob_token = next_token;
|
||||
eob_run = eob_refbits = 0;
|
||||
}
|
||||
const coeff_t* block = &blocks[0][bx][0];
|
||||
int num_eob_refinement_bits = 0;
|
||||
int num_refinement_bits = 0;
|
||||
int num_nzeros = 0;
|
||||
int r = 0;
|
||||
for (int k = Ss; k <= Se; ++k) {
|
||||
int absval = block[k];
|
||||
if (absval == 0) {
|
||||
r++;
|
||||
continue;
|
||||
}
|
||||
const int mask = absval >> (8 * sizeof(int) - 1);
|
||||
absval += mask;
|
||||
absval ^= mask;
|
||||
absval >>= Al;
|
||||
if (absval == 0) {
|
||||
r++;
|
||||
continue;
|
||||
}
|
||||
while (r > 15) {
|
||||
token.symbol = 0xf0;
|
||||
token.refbits = num_refinement_bits;
|
||||
*next_token++ = token;
|
||||
r -= 16;
|
||||
num_eob_refinement_bits += num_refinement_bits;
|
||||
num_refinement_bits = 0;
|
||||
}
|
||||
if (absval > 1) {
|
||||
*next_ref_bit++ = absval & 1u;
|
||||
++num_refinement_bits;
|
||||
continue;
|
||||
}
|
||||
int symbol = (r << 4u) + 1 + ((mask + 1) << 1);
|
||||
token.symbol = symbol;
|
||||
token.refbits = num_refinement_bits;
|
||||
*next_token++ = token;
|
||||
++num_nzeros;
|
||||
num_refinement_bits = 0;
|
||||
num_eob_refinement_bits = 0;
|
||||
r = 0;
|
||||
next_eob_token = next_token;
|
||||
eob_run = eob_refbits = 0;
|
||||
}
|
||||
if (r > 0 || num_eob_refinement_bits + num_refinement_bits > 0) {
|
||||
++eob_run;
|
||||
eob_refbits += num_eob_refinement_bits + num_refinement_bits;
|
||||
if (eob_refbits > 255) {
|
||||
++next_eob_token;
|
||||
eob_refbits = num_eob_refinement_bits + num_refinement_bits;
|
||||
eob_run = 1;
|
||||
}
|
||||
next_token = next_eob_token;
|
||||
next_token->refbits = eob_refbits;
|
||||
if (eob_run == 1) {
|
||||
next_token->symbol = 0;
|
||||
} else if (eob_run == 2) {
|
||||
next_token->symbol = 16;
|
||||
*next_eobrun++ = 0;
|
||||
} else if ((eob_run & (eob_run - 1)) == 0) {
|
||||
next_token->symbol += 16;
|
||||
next_eobrun[-1] = 0;
|
||||
} else {
|
||||
++next_eobrun[-1];
|
||||
}
|
||||
++next_token;
|
||||
if (eob_run == 0x7fff) {
|
||||
next_eob_token = next_token;
|
||||
eob_run = eob_refbits = 0;
|
||||
}
|
||||
}
|
||||
sti->num_nonzeros += num_nzeros;
|
||||
--restarts_to_go;
|
||||
}
|
||||
}
|
||||
sti->num_tokens = next_token - sti->tokens;
|
||||
sti->restarts[restart_idx++] = sti->num_tokens;
|
||||
m->next_refinement_token = next_token;
|
||||
m->next_refinement_bit = next_ref_bit;
|
||||
}
|
||||
|
||||
void TokenizeScan(j_compress_ptr cinfo, size_t scan_index, int ac_ctx_offset,
|
||||
ScanTokenInfo* sti) {
|
||||
const jpeg_scan_info* scan_info = &cinfo->scan_info[scan_index];
|
||||
if (scan_info->Ss > 0) {
|
||||
if (scan_info->Ah == 0) {
|
||||
TokenizeACProgressiveScan(cinfo, scan_index, ac_ctx_offset, sti);
|
||||
} else {
|
||||
TokenizeACRefinementScan(cinfo, scan_index, sti);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
size_t restart_interval = sti->restart_interval;
|
||||
int restarts_to_go = restart_interval;
|
||||
coeff_t last_dc_coeff[MAX_COMPS_IN_SCAN] = {0};
|
||||
|
||||
// "Non-interleaved" means color data comes in separate scans, in other words
|
||||
// each scan can contain only one color component.
|
||||
const bool is_interleaved = (scan_info->comps_in_scan > 1);
|
||||
const bool is_progressive = FROM_JXL_BOOL(cinfo->progressive_mode);
|
||||
const int Ah = scan_info->Ah;
|
||||
const int Al = scan_info->Al;
|
||||
HWY_ALIGN constexpr coeff_t kSinkBlock[DCTSIZE2] = {0};
|
||||
|
||||
size_t restart_idx = 0;
|
||||
TokenArray* ta = &m->token_arrays[m->cur_token_array];
|
||||
sti->token_offset = Ah > 0 ? 0 : m->total_num_tokens + ta->num_tokens;
|
||||
|
||||
if (Ah > 0) {
|
||||
sti->refbits = Allocate<uint8_t>(cinfo, sti->num_blocks, JPOOL_IMAGE);
|
||||
} else if (cinfo->progressive_mode) {
|
||||
if (ta->num_tokens + sti->num_blocks > m->num_tokens) {
|
||||
if (ta->tokens) {
|
||||
m->total_num_tokens += ta->num_tokens;
|
||||
++m->cur_token_array;
|
||||
ta = &m->token_arrays[m->cur_token_array];
|
||||
}
|
||||
m->num_tokens = sti->num_blocks;
|
||||
ta->tokens = Allocate<Token>(cinfo, m->num_tokens, JPOOL_IMAGE);
|
||||
m->next_token = ta->tokens;
|
||||
}
|
||||
}
|
||||
|
||||
JBLOCKARRAY blocks[MAX_COMPS_IN_SCAN];
|
||||
size_t block_idx = 0;
|
||||
for (size_t mcu_y = 0; mcu_y < sti->MCU_rows_in_scan; ++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);
|
||||
blocks[i] = (*cinfo->mem->access_virt_barray)(
|
||||
reinterpret_cast<j_common_ptr>(cinfo), m->coeff_buffers[comp_idx],
|
||||
by0, max_block_rows, FALSE);
|
||||
}
|
||||
if (!cinfo->progressive_mode) {
|
||||
int max_tokens_per_mcu_row = MaxNumTokensPerMCURow(cinfo);
|
||||
if (ta->num_tokens + max_tokens_per_mcu_row > m->num_tokens) {
|
||||
if (ta->tokens) {
|
||||
m->total_num_tokens += ta->num_tokens;
|
||||
++m->cur_token_array;
|
||||
ta = &m->token_arrays[m->cur_token_array];
|
||||
}
|
||||
m->num_tokens =
|
||||
EstimateNumTokens(cinfo, mcu_y, sti->MCU_rows_in_scan,
|
||||
m->total_num_tokens, max_tokens_per_mcu_row);
|
||||
ta->tokens = Allocate<Token>(cinfo, m->num_tokens, JPOOL_IMAGE);
|
||||
m->next_token = ta->tokens;
|
||||
}
|
||||
}
|
||||
for (size_t mcu_x = 0; mcu_x < sti->MCUs_per_row; ++mcu_x) {
|
||||
// Possibly emit a restart marker.
|
||||
if (restart_interval > 0 && restarts_to_go == 0) {
|
||||
restarts_to_go = restart_interval;
|
||||
memset(last_dc_coeff, 0, sizeof(last_dc_coeff));
|
||||
ta->num_tokens = m->next_token - ta->tokens;
|
||||
sti->restarts[restart_idx++] =
|
||||
Ah > 0 ? block_idx : m->total_num_tokens + ta->num_tokens;
|
||||
}
|
||||
// Encode one MCU
|
||||
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 n_blocks_x = is_interleaved ? comp->h_samp_factor : 1;
|
||||
for (int iy = 0; iy < n_blocks_y; ++iy) {
|
||||
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;
|
||||
const coeff_t* block;
|
||||
if (block_x >= comp->width_in_blocks ||
|
||||
block_y >= comp->height_in_blocks) {
|
||||
block = kSinkBlock;
|
||||
} else {
|
||||
block = &blocks[i][iy][block_x][0];
|
||||
}
|
||||
if (!is_progressive) {
|
||||
HWY_DYNAMIC_DISPATCH(ComputeTokensSequential)
|
||||
(block, last_dc_coeff[i], comp_idx, ac_ctx_offset + i,
|
||||
&m->next_token);
|
||||
last_dc_coeff[i] = block[0];
|
||||
} else {
|
||||
if (Ah == 0) {
|
||||
TokenizeProgressiveDC(block, comp_idx, Al, last_dc_coeff + i,
|
||||
&m->next_token);
|
||||
} else {
|
||||
sti->refbits[block_idx] = (block[0] >> Al) & 1;
|
||||
}
|
||||
}
|
||||
++block_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
--restarts_to_go;
|
||||
}
|
||||
ta->num_tokens = m->next_token - ta->tokens;
|
||||
}
|
||||
JXL_DASSERT(block_idx == sti->num_blocks);
|
||||
sti->num_tokens =
|
||||
Ah > 0 ? sti->num_blocks
|
||||
: m->total_num_tokens + ta->num_tokens - sti->token_offset;
|
||||
sti->restarts[restart_idx++] =
|
||||
Ah > 0 ? sti->num_blocks : m->total_num_tokens + ta->num_tokens;
|
||||
if (Ah == 0 && cinfo->progressive_mode) {
|
||||
JXL_DASSERT(sti->num_blocks == sti->num_tokens);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void TokenizeJpeg(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
std::vector<int> processed(cinfo->num_scans);
|
||||
size_t max_refinement_tokens = 0;
|
||||
size_t num_refinement_bits = 0;
|
||||
int num_refinement_scans[kMaxComponents][DCTSIZE2] = {};
|
||||
int max_num_refinement_scans = 0;
|
||||
for (int i = 0; i < cinfo->num_scans; ++i) {
|
||||
const jpeg_scan_info* si = &cinfo->scan_info[i];
|
||||
ScanTokenInfo* sti = &m->scan_token_info[i];
|
||||
if (si->Ss > 0 && si->Ah == 0 && si->Al > 0) {
|
||||
int offset = m->ac_ctx_offset[i];
|
||||
int comp_idx = si->component_index[0];
|
||||
TokenizeScan(cinfo, i, offset, sti);
|
||||
processed[i] = 1;
|
||||
max_refinement_tokens += sti->num_future_nonzeros;
|
||||
for (int k = si->Ss; k <= si->Se; ++k) {
|
||||
num_refinement_scans[comp_idx][k] = si->Al;
|
||||
}
|
||||
max_num_refinement_scans = std::max(max_num_refinement_scans, si->Al);
|
||||
num_refinement_bits += sti->num_nonzeros;
|
||||
}
|
||||
if (si->Ss > 0 && si->Ah > 0) {
|
||||
int comp_idx = si->component_index[0];
|
||||
const jpeg_component_info* comp = &cinfo->comp_info[comp_idx];
|
||||
size_t num_blocks =
|
||||
static_cast<size_t>(comp->width_in_blocks) * comp->height_in_blocks;
|
||||
max_refinement_tokens += (1 + (si->Se - si->Ss) / 16) * num_blocks;
|
||||
}
|
||||
}
|
||||
if (max_refinement_tokens > 0) {
|
||||
m->next_refinement_token =
|
||||
Allocate<RefToken>(cinfo, max_refinement_tokens, JPOOL_IMAGE);
|
||||
}
|
||||
for (int j = 0; j < max_num_refinement_scans; ++j) {
|
||||
uint8_t* refinement_bits =
|
||||
Allocate<uint8_t>(cinfo, num_refinement_bits, JPOOL_IMAGE);
|
||||
m->next_refinement_bit = refinement_bits;
|
||||
size_t new_refinement_bits = 0;
|
||||
for (int i = 0; i < cinfo->num_scans; ++i) {
|
||||
const jpeg_scan_info* si = &cinfo->scan_info[i];
|
||||
int comp_idx = si->component_index[0];
|
||||
ScanTokenInfo* sti = &m->scan_token_info[i];
|
||||
if (si->Ss > 0 && si->Ah > 0 &&
|
||||
si->Ah == num_refinement_scans[comp_idx][si->Ss] - j) {
|
||||
int offset = m->ac_ctx_offset[i];
|
||||
TokenizeScan(cinfo, i, offset, sti);
|
||||
processed[i] = 1;
|
||||
new_refinement_bits += sti->num_nonzeros;
|
||||
}
|
||||
}
|
||||
JXL_DASSERT(m->next_refinement_bit <=
|
||||
refinement_bits + num_refinement_bits);
|
||||
num_refinement_bits += new_refinement_bits;
|
||||
}
|
||||
for (int i = 0; i < cinfo->num_scans; ++i) {
|
||||
if (processed[i]) {
|
||||
continue;
|
||||
}
|
||||
int offset = m->ac_ctx_offset[i];
|
||||
TokenizeScan(cinfo, i, offset, &m->scan_token_info[i]);
|
||||
processed[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct Histogram {
|
||||
int count[kJpegHuffmanAlphabetSize];
|
||||
Histogram() { memset(count, 0, sizeof(count)); }
|
||||
};
|
||||
|
||||
void BuildHistograms(j_compress_ptr cinfo, Histogram* histograms) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
size_t num_token_arrays = m->cur_token_array + 1;
|
||||
for (size_t i = 0; i < num_token_arrays; ++i) {
|
||||
Token* tokens = m->token_arrays[i].tokens;
|
||||
size_t num_tokens = m->token_arrays[i].num_tokens;
|
||||
for (size_t j = 0; j < num_tokens; ++j) {
|
||||
Token t = tokens[j];
|
||||
++histograms[t.context].count[t.symbol];
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < cinfo->num_scans; ++i) {
|
||||
const jpeg_scan_info& si = cinfo->scan_info[i];
|
||||
const ScanTokenInfo& sti = m->scan_token_info[i];
|
||||
if (si.Ss > 0 && si.Ah > 0) {
|
||||
int context = m->ac_ctx_offset[i];
|
||||
int* ac_histo = &histograms[context].count[0];
|
||||
for (size_t j = 0; j < sti.num_tokens; ++j) {
|
||||
++ac_histo[sti.tokens[j].symbol & 253];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct JpegClusteredHistograms {
|
||||
std::vector<Histogram> histograms;
|
||||
std::vector<uint32_t> histogram_indexes;
|
||||
std::vector<uint32_t> slot_ids;
|
||||
};
|
||||
|
||||
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.count[i];
|
||||
}
|
||||
counts[kJpegHuffmanAlphabetSize] = 1;
|
||||
CreateHuffmanTree(counts.data(), counts.size(), kJpegHuffmanMaxBitLength,
|
||||
depths.data());
|
||||
size_t header_bits = (1 + kJpegHuffmanMaxBitLength) * 8;
|
||||
size_t data_bits = 0;
|
||||
for (size_t i = 0; i < kJpegHuffmanAlphabetSize; ++i) {
|
||||
if (depths[i] > 0) {
|
||||
header_bits += 8;
|
||||
data_bits += static_cast<size_t>(counts[i]) * depths[i];
|
||||
}
|
||||
}
|
||||
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 (int count : histo.count) {
|
||||
if (count) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClusterJpegHistograms(j_compress_ptr cinfo, const Histogram* histograms,
|
||||
size_t num, JpegClusteredHistograms* clusters) {
|
||||
clusters->histogram_indexes.resize(num);
|
||||
std::vector<uint32_t> slot_histograms;
|
||||
std::vector<float> slot_costs;
|
||||
// Since not all jpeg decoders support the extended sequential mode, i.e. the
|
||||
// 0xff 0xc1 SOF marker, we will limit the number of clusters to 2 in
|
||||
// sequential mode, unless the quantization tables already require the
|
||||
// extended sequential mode.
|
||||
const bool force_baseline =
|
||||
!cinfo->progressive_mode && cinfo->master->force_baseline;
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
const Histogram& cur = histograms[i];
|
||||
if (IsEmptyHistogram(cur)) {
|
||||
continue;
|
||||
}
|
||||
size_t best_slot = slot_histograms.size();
|
||||
float best_cost = force_baseline && best_slot > 1
|
||||
? std::numeric_limits<float>::max()
|
||||
: HistogramCost(cur);
|
||||
for (size_t j = 0; j < slot_histograms.size(); ++j) {
|
||||
size_t prev_idx = slot_histograms[j];
|
||||
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) {
|
||||
best_cost = cost;
|
||||
best_slot = j;
|
||||
}
|
||||
}
|
||||
if (best_slot == slot_histograms.size()) {
|
||||
// Create new histogram.
|
||||
size_t histogram_index = clusters->histograms.size();
|
||||
clusters->histograms.push_back(cur);
|
||||
clusters->histogram_indexes[i] = histogram_index;
|
||||
if (best_slot < 4) {
|
||||
// We have a free slot, so we put the new histogram there.
|
||||
slot_histograms.push_back(histogram_index);
|
||||
slot_costs.push_back(best_cost);
|
||||
} else {
|
||||
// TODO(szabadka) Find the best histogram to replace.
|
||||
best_slot = (clusters->slot_ids.back() + 1) % 4;
|
||||
}
|
||||
slot_histograms[best_slot] = histogram_index;
|
||||
slot_costs[best_slot] = best_cost;
|
||||
clusters->slot_ids.push_back(best_slot);
|
||||
} else {
|
||||
// Merge this histogram with a previous one.
|
||||
size_t histogram_index = slot_histograms[best_slot];
|
||||
const Histogram& prev = clusters->histograms[histogram_index];
|
||||
AddHistograms(prev, cur, &clusters->histograms[histogram_index]);
|
||||
clusters->histogram_indexes[i] = histogram_index;
|
||||
JPEGLI_CHECK(clusters->slot_ids[histogram_index] == best_slot);
|
||||
slot_costs[best_slot] += best_cost;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CopyHuffmanTable(j_compress_ptr cinfo, int index, bool is_dc,
|
||||
int* inv_slot_map, uint8_t* slot_id_map,
|
||||
JHUFF_TBL* huffman_tables, size_t* num_huffman_tables) {
|
||||
const char* type = is_dc ? "DC" : "AC";
|
||||
if (index < 0 || index >= NUM_HUFF_TBLS) {
|
||||
JPEGLI_ERROR("Invalid %s Huffman table index %d", type, index);
|
||||
}
|
||||
// Check if we have already copied this Huffman table.
|
||||
int slot_idx = index + (is_dc ? 0 : NUM_HUFF_TBLS);
|
||||
if (inv_slot_map[slot_idx] != -1) {
|
||||
return;
|
||||
}
|
||||
inv_slot_map[slot_idx] = *num_huffman_tables;
|
||||
// Look up and validate Huffman table.
|
||||
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", type, index);
|
||||
}
|
||||
ValidateHuffmanTable(reinterpret_cast<j_common_ptr>(cinfo), table, is_dc);
|
||||
// Copy Huffman table to the end of the list and save slot id.
|
||||
slot_id_map[*num_huffman_tables] = index + (is_dc ? 0 : 0x10);
|
||||
memcpy(&huffman_tables[*num_huffman_tables], table, sizeof(JHUFF_TBL));
|
||||
++(*num_huffman_tables);
|
||||
}
|
||||
|
||||
void BuildJpegHuffmanTable(const Histogram& histo, JHUFF_TBL* table) {
|
||||
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.count[j];
|
||||
}
|
||||
counts[kJpegHuffmanAlphabetSize] = 1;
|
||||
CreateHuffmanTree(counts.data(), counts.size(), kJpegHuffmanMaxBitLength,
|
||||
depths.data());
|
||||
memset(table, 0, sizeof(JHUFF_TBL));
|
||||
for (size_t i = 0; i < kJpegHuffmanAlphabetSize; ++i) {
|
||||
if (depths[i] > 0) {
|
||||
++table->bits[depths[i]];
|
||||
}
|
||||
}
|
||||
int offset[kJpegHuffmanMaxBitLength + 1] = {0};
|
||||
for (size_t i = 1; i <= kJpegHuffmanMaxBitLength; ++i) {
|
||||
offset[i] = offset[i - 1] + table->bits[i - 1];
|
||||
}
|
||||
for (size_t i = 0; i < kJpegHuffmanAlphabetSize; ++i) {
|
||||
if (depths[i] > 0) {
|
||||
table->huffval[offset[depths[i]]++] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CopyHuffmanTables(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
size_t max_huff_tables = 2 * cinfo->num_components;
|
||||
// Copy Huffman tables and save slot ids.
|
||||
m->huffman_tables = Allocate<JHUFF_TBL>(cinfo, max_huff_tables, JPOOL_IMAGE);
|
||||
m->slot_id_map = Allocate<uint8_t>(cinfo, max_huff_tables, JPOOL_IMAGE);
|
||||
m->num_huffman_tables = 0;
|
||||
int inv_slot_map[8] = {-1, -1, -1, -1, -1, -1, -1, -1};
|
||||
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, &inv_slot_map[0],
|
||||
m->slot_id_map, m->huffman_tables, &m->num_huffman_tables);
|
||||
CopyHuffmanTable(cinfo, comp->ac_tbl_no, /*is_dc=*/false, &inv_slot_map[0],
|
||||
m->slot_id_map, m->huffman_tables, &m->num_huffman_tables);
|
||||
}
|
||||
// Compute context map.
|
||||
m->context_map = Allocate<uint8_t>(cinfo, 8, JPOOL_IMAGE);
|
||||
memset(m->context_map, 0, 8);
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
m->context_map[c] = inv_slot_map[cinfo->comp_info[c].dc_tbl_no];
|
||||
}
|
||||
int ac_ctx = 4;
|
||||
for (int i = 0; i < cinfo->num_scans; ++i) {
|
||||
const jpeg_scan_info* si = &cinfo->scan_info[i];
|
||||
if (si->Se > 0) {
|
||||
for (int j = 0; j < si->comps_in_scan; ++j) {
|
||||
int c = si->component_index[j];
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
m->context_map[ac_ctx++] = inv_slot_map[comp->ac_tbl_no + 4];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OptimizeHuffmanCodes(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
// Build DC and AC histograms.
|
||||
std::vector<Histogram> histograms(m->num_contexts);
|
||||
BuildHistograms(cinfo, histograms.data());
|
||||
|
||||
// Cluster DC histograms.
|
||||
JpegClusteredHistograms dc_clusters;
|
||||
ClusterJpegHistograms(cinfo, histograms.data(), cinfo->num_components,
|
||||
&dc_clusters);
|
||||
|
||||
// Cluster AC histograms.
|
||||
JpegClusteredHistograms ac_clusters;
|
||||
ClusterJpegHistograms(cinfo, histograms.data() + 4, m->num_contexts - 4,
|
||||
&ac_clusters);
|
||||
|
||||
// Create Huffman tables and slot ids clusters.
|
||||
size_t num_dc_huff = dc_clusters.histograms.size();
|
||||
m->num_huffman_tables = num_dc_huff + ac_clusters.histograms.size();
|
||||
m->huffman_tables =
|
||||
Allocate<JHUFF_TBL>(cinfo, m->num_huffman_tables, JPOOL_IMAGE);
|
||||
m->slot_id_map = Allocate<uint8_t>(cinfo, m->num_huffman_tables, JPOOL_IMAGE);
|
||||
for (size_t i = 0; i < m->num_huffman_tables; ++i) {
|
||||
JHUFF_TBL huff_table = {};
|
||||
if (i < dc_clusters.histograms.size()) {
|
||||
m->slot_id_map[i] = i;
|
||||
BuildJpegHuffmanTable(dc_clusters.histograms[i], &huff_table);
|
||||
} else {
|
||||
m->slot_id_map[i] = 16 + ac_clusters.slot_ids[i - num_dc_huff];
|
||||
BuildJpegHuffmanTable(ac_clusters.histograms[i - num_dc_huff],
|
||||
&huff_table);
|
||||
}
|
||||
memcpy(&m->huffman_tables[i], &huff_table, sizeof(huff_table));
|
||||
}
|
||||
|
||||
// Create context map from clustered histogram indexes.
|
||||
m->context_map = Allocate<uint8_t>(cinfo, m->num_contexts, JPOOL_IMAGE);
|
||||
memset(m->context_map, 0, m->num_contexts);
|
||||
for (size_t i = 0; i < m->num_contexts; ++i) {
|
||||
if (i < static_cast<size_t>(cinfo->num_components)) {
|
||||
m->context_map[i] = dc_clusters.histogram_indexes[i];
|
||||
} else if (i >= 4) {
|
||||
m->context_map[i] = num_dc_huff + ac_clusters.histogram_indexes[i - 4];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint8_t kNumExtraBits[256] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
3, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
4, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
6, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
7, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
11, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
13, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
14, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, //
|
||||
};
|
||||
|
||||
void BuildHuffmanCodeTable(const JHUFF_TBL& table, HuffmanCodeTable* code) {
|
||||
int huff_code[kJpegHuffmanAlphabetSize];
|
||||
// +1 for a sentinel element.
|
||||
uint32_t huff_size[kJpegHuffmanAlphabetSize + 1];
|
||||
int p = 0;
|
||||
for (size_t l = 1; l <= kJpegHuffmanMaxBitLength; ++l) {
|
||||
int i = table.bits[l];
|
||||
while (i--) huff_size[p++] = l;
|
||||
}
|
||||
|
||||
// Reuse sentinel element.
|
||||
int last_p = p;
|
||||
huff_size[last_p] = 0;
|
||||
|
||||
int next_code = 0;
|
||||
uint32_t si = huff_size[0];
|
||||
p = 0;
|
||||
while (huff_size[p]) {
|
||||
while ((huff_size[p]) == si) {
|
||||
huff_code[p++] = next_code;
|
||||
next_code++;
|
||||
}
|
||||
next_code <<= 1;
|
||||
si++;
|
||||
}
|
||||
for (p = 0; p < last_p; p++) {
|
||||
int i = table.huffval[p];
|
||||
int nbits = kNumExtraBits[i];
|
||||
code->depth[i] = huff_size[p] + nbits;
|
||||
code->code[i] = huff_code[p] << nbits;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitEntropyCoder(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
m->coding_tables =
|
||||
Allocate<HuffmanCodeTable>(cinfo, m->num_huffman_tables, JPOOL_IMAGE);
|
||||
for (size_t i = 0; i < m->num_huffman_tables; ++i) {
|
||||
BuildHuffmanCodeTable(m->huffman_tables[i], &m->coding_tables[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
#endif // HWY_ONCE
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_ENTROPY_CODING_H_
|
||||
#define LIB_JPEGLI_ENTROPY_CODING_H_
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
size_t MaxNumTokensPerMCURow(j_compress_ptr cinfo);
|
||||
|
||||
size_t EstimateNumTokens(j_compress_ptr cinfo, size_t mcu_y, size_t ysize_mcus,
|
||||
size_t num_tokens, size_t max_per_row);
|
||||
|
||||
void TokenizeJpeg(j_compress_ptr cinfo);
|
||||
|
||||
void CopyHuffmanTables(j_compress_ptr cinfo);
|
||||
|
||||
void OptimizeHuffmanCodes(j_compress_ptr cinfo);
|
||||
|
||||
void InitEntropyCoder(j_compress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_ENTROPY_CODING_H_
|
||||
@@ -1,102 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/error.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
const char* const kErrorMessageTable[] = {
|
||||
"Message codes are not supported, error message is in msg_parm.s string",
|
||||
};
|
||||
|
||||
bool FormatString(char* buffer, const char* format, ...) {
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf(buffer, JMSG_STR_PARM_MAX, format, args); // notypo
|
||||
va_end(args);
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExitWithAbort(j_common_ptr cinfo) {
|
||||
(*cinfo->err->output_message)(cinfo);
|
||||
jpegli_destroy(cinfo);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void EmitMessage(j_common_ptr cinfo, int msg_level) {
|
||||
if (msg_level < 0) {
|
||||
if (cinfo->err->num_warnings <= 5 || cinfo->err->trace_level >= 3) {
|
||||
(*cinfo->err->output_message)(cinfo);
|
||||
}
|
||||
++cinfo->err->num_warnings;
|
||||
} else if (cinfo->err->trace_level >= msg_level) {
|
||||
(*cinfo->err->output_message)(cinfo);
|
||||
}
|
||||
}
|
||||
|
||||
void OutputMessage(j_common_ptr cinfo) {
|
||||
char buffer[JMSG_LENGTH_MAX];
|
||||
(*cinfo->err->format_message)(cinfo, buffer);
|
||||
fprintf(stderr, "%s\n", buffer);
|
||||
}
|
||||
|
||||
void FormatMessage(j_common_ptr cinfo, char* buffer) {
|
||||
jpeg_error_mgr* err = cinfo->err;
|
||||
int code = err->msg_code;
|
||||
if (code == 0) {
|
||||
memcpy(buffer, cinfo->err->msg_parm.s, JMSG_STR_PARM_MAX);
|
||||
} else if (err->addon_message_table != nullptr &&
|
||||
code >= err->first_addon_message &&
|
||||
code <= err->last_addon_message) {
|
||||
std::string msg(err->addon_message_table[code - err->first_addon_message]);
|
||||
if (msg.find("%s") != std::string::npos) {
|
||||
snprintf(buffer, JMSG_LENGTH_MAX, msg.data(), err->msg_parm.s);
|
||||
} else {
|
||||
snprintf(buffer, JMSG_LENGTH_MAX, msg.data(), err->msg_parm.i[0],
|
||||
err->msg_parm.i[1], err->msg_parm.i[2], err->msg_parm.i[3],
|
||||
err->msg_parm.i[4], err->msg_parm.i[5], err->msg_parm.i[6],
|
||||
err->msg_parm.i[7]);
|
||||
}
|
||||
} else {
|
||||
snprintf(buffer, JMSG_LENGTH_MAX, "%s", kErrorMessageTable[0]);
|
||||
}
|
||||
}
|
||||
|
||||
void ResetErrorManager(j_common_ptr cinfo) {
|
||||
memset(cinfo->err->msg_parm.s, 0, JMSG_STR_PARM_MAX);
|
||||
cinfo->err->msg_code = 0;
|
||||
cinfo->err->num_warnings = 0;
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
struct jpeg_error_mgr* jpegli_std_error(struct jpeg_error_mgr* err) {
|
||||
err->error_exit = jpegli::ExitWithAbort;
|
||||
err->emit_message = jpegli::EmitMessage;
|
||||
err->output_message = jpegli::OutputMessage;
|
||||
err->format_message = jpegli::FormatMessage;
|
||||
err->reset_error_mgr = jpegli::ResetErrorManager;
|
||||
memset(err->msg_parm.s, 0, JMSG_STR_PARM_MAX);
|
||||
err->trace_level = 0;
|
||||
err->num_warnings = 0;
|
||||
// We don't support message codes and message table, but we define one here
|
||||
// in case the application has a custom format_message and tries to access
|
||||
// these fields there.
|
||||
err->msg_code = 0;
|
||||
err->jpeg_message_table = jpegli::kErrorMessageTable;
|
||||
err->last_jpeg_message = 0;
|
||||
err->addon_message_table = nullptr;
|
||||
err->first_addon_message = 0;
|
||||
err->last_addon_message = 0;
|
||||
return err;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_ERROR_H_
|
||||
#define LIB_JPEGLI_ERROR_H_
|
||||
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
bool FormatString(char* buffer, const char* format, ...);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
// `error_exit` should be no-return; but let's add some guarantees on our side.
|
||||
#define JPEGLI_ERROR(format, ...) \
|
||||
jpegli::FormatString(cinfo->err->msg_parm.s, ("%s:%d: " format), __FILE__, \
|
||||
__LINE__, ##__VA_ARGS__), \
|
||||
(*cinfo->err->error_exit)(reinterpret_cast<j_common_ptr>(cinfo)), \
|
||||
JXL_CRASH()
|
||||
|
||||
#define JPEGLI_WARN(format, ...) \
|
||||
jpegli::FormatString(cinfo->err->msg_parm.s, ("%s:%d: " format), __FILE__, \
|
||||
__LINE__, ##__VA_ARGS__), \
|
||||
(*cinfo->err->emit_message)(reinterpret_cast<j_common_ptr>(cinfo), -1)
|
||||
|
||||
#define JPEGLI_TRACE(level, format, ...) \
|
||||
if (cinfo->err->trace_level >= (level)) \
|
||||
jpegli::FormatString(cinfo->err->msg_parm.s, ("%s:%d: " format), __FILE__, \
|
||||
__LINE__, ##__VA_ARGS__), \
|
||||
(*cinfo->err->emit_message)(reinterpret_cast<j_common_ptr>(cinfo), \
|
||||
(level))
|
||||
|
||||
#define JPEGLI_CHECK(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
JPEGLI_ERROR("JPEGLI_CHECK: %s", #condition); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif // LIB_JPEGLI_ERROR_H_
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,22 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_FUZZTEST_H_
|
||||
#define LIB_JPEGLI_FUZZTEST_H_
|
||||
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
#if !defined(FUZZ_TEST)
|
||||
struct FuzzTestSink {
|
||||
template <typename F>
|
||||
FuzzTestSink WithSeeds(F /*f*/) {
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
#define FUZZ_TEST(A, B) \
|
||||
const JXL_MAYBE_UNUSED FuzzTestSink unused##A##B = FuzzTestSink()
|
||||
#endif
|
||||
|
||||
#endif // LIB_JPEGLI_FUZZTEST_H_
|
||||
@@ -1,332 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/huffman.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
// Returns the table width of the next 2nd level table, count is the histogram
|
||||
// of bit lengths for the remaining symbols, len is the code length of the next
|
||||
// processed symbol.
|
||||
static inline int NextTableBitSize(const int* count, int len) {
|
||||
int left = 1 << (len - kJpegHuffmanRootTableBits);
|
||||
while (len < static_cast<int>(kJpegHuffmanMaxBitLength)) {
|
||||
left -= count[len];
|
||||
if (left <= 0) break;
|
||||
++len;
|
||||
left <<= 1;
|
||||
}
|
||||
return len - kJpegHuffmanRootTableBits;
|
||||
}
|
||||
|
||||
void BuildJpegHuffmanTable(const uint32_t* count, const uint32_t* symbols,
|
||||
HuffmanTableEntry* lut) {
|
||||
HuffmanTableEntry code; // current table entry
|
||||
HuffmanTableEntry* table; // next available space in table
|
||||
int len; // current code length
|
||||
int idx; // symbol index
|
||||
int key; // prefix code
|
||||
int reps; // number of replicate key values in current table
|
||||
int low; // low bits for current root entry
|
||||
int table_bits; // key length of current table
|
||||
int table_size; // size of current table
|
||||
|
||||
// Make a local copy of the input bit length histogram.
|
||||
int tmp_count[kJpegHuffmanMaxBitLength + 1] = {0};
|
||||
int total_count = 0;
|
||||
for (len = 1; len <= static_cast<int>(kJpegHuffmanMaxBitLength); ++len) {
|
||||
tmp_count[len] = count[len];
|
||||
total_count += tmp_count[len];
|
||||
}
|
||||
|
||||
table = lut;
|
||||
table_bits = kJpegHuffmanRootTableBits;
|
||||
table_size = 1 << table_bits;
|
||||
|
||||
// Special case code with only one value.
|
||||
if (total_count == 1) {
|
||||
code.bits = 0;
|
||||
code.value = symbols[0];
|
||||
for (key = 0; key < table_size; ++key) {
|
||||
table[key] = code;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill in root table.
|
||||
key = 0;
|
||||
idx = 0;
|
||||
for (len = 1; len <= kJpegHuffmanRootTableBits; ++len) {
|
||||
for (; tmp_count[len] > 0; --tmp_count[len]) {
|
||||
code.bits = len;
|
||||
code.value = symbols[idx++];
|
||||
reps = 1 << (kJpegHuffmanRootTableBits - len);
|
||||
while (reps--) {
|
||||
table[key++] = code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in 2nd level tables and add pointers to root table.
|
||||
table += table_size;
|
||||
table_size = 0;
|
||||
low = 0;
|
||||
for (len = kJpegHuffmanRootTableBits + 1;
|
||||
len <= static_cast<int>(kJpegHuffmanMaxBitLength); ++len) {
|
||||
for (; tmp_count[len] > 0; --tmp_count[len]) {
|
||||
// Start a new sub-table if the previous one is full.
|
||||
if (low >= table_size) {
|
||||
table += table_size;
|
||||
table_bits = NextTableBitSize(tmp_count, len);
|
||||
table_size = 1 << table_bits;
|
||||
low = 0;
|
||||
lut[key].bits = table_bits + kJpegHuffmanRootTableBits;
|
||||
lut[key].value = (table - lut) - key;
|
||||
++key;
|
||||
}
|
||||
code.bits = len - kJpegHuffmanRootTableBits;
|
||||
code.value = symbols[idx++];
|
||||
reps = 1 << (table_bits - code.bits);
|
||||
while (reps--) {
|
||||
table[low++] = code;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A node of a Huffman tree.
|
||||
struct HuffmanTree {
|
||||
HuffmanTree(uint32_t count, int16_t left, int16_t right)
|
||||
: total_count(count), index_left(left), index_right_or_value(right) {}
|
||||
uint32_t total_count;
|
||||
int16_t index_left;
|
||||
int16_t index_right_or_value;
|
||||
};
|
||||
|
||||
void SetDepth(const HuffmanTree& p, HuffmanTree* pool, uint8_t* depth,
|
||||
uint8_t level) {
|
||||
if (p.index_left >= 0) {
|
||||
++level;
|
||||
SetDepth(pool[p.index_left], pool, depth, level);
|
||||
SetDepth(pool[p.index_right_or_value], pool, depth, level);
|
||||
} else {
|
||||
depth[p.index_right_or_value] = level;
|
||||
}
|
||||
}
|
||||
|
||||
// Compare the root nodes, least popular first; indices are in decreasing order
|
||||
// before sorting is applied.
|
||||
static JXL_INLINE bool Compare(const HuffmanTree& v0, const HuffmanTree& v1) {
|
||||
return v0.total_count != v1.total_count
|
||||
? v0.total_count < v1.total_count
|
||||
: v0.index_right_or_value > v1.index_right_or_value;
|
||||
}
|
||||
|
||||
// This function will create a Huffman tree.
|
||||
//
|
||||
// The catch here is that the tree cannot be arbitrarily deep.
|
||||
// Brotli specifies a maximum depth of 15 bits for "code trees"
|
||||
// and 7 bits for "code length code trees."
|
||||
//
|
||||
// count_limit is the value that is to be faked as the minimum value
|
||||
// and this minimum value is raised until the tree matches the
|
||||
// maximum length requirement.
|
||||
//
|
||||
// This algorithm is not of excellent performance for very long data blocks,
|
||||
// especially when population counts are longer than 2**tree_limit, but
|
||||
// we are not planning to use this with extremely long blocks.
|
||||
//
|
||||
// See http://en.wikipedia.org/wiki/Huffman_coding
|
||||
void CreateHuffmanTree(const uint32_t* data, const size_t length,
|
||||
const int tree_limit, uint8_t* depth) {
|
||||
// For block sizes below 64 kB, we never need to do a second iteration
|
||||
// of this loop. Probably all of our block sizes will be smaller than
|
||||
// that, so this loop is mostly of academic interest. If we actually
|
||||
// would need this, we would be better off with the Katajainen algorithm.
|
||||
for (uint32_t count_limit = 1;; count_limit *= 2) {
|
||||
std::vector<HuffmanTree> tree;
|
||||
tree.reserve(2 * length + 1);
|
||||
|
||||
for (size_t i = length; i != 0;) {
|
||||
--i;
|
||||
if (data[i]) {
|
||||
const uint32_t count = std::max(data[i], count_limit - 1);
|
||||
tree.emplace_back(count, -1, static_cast<int16_t>(i));
|
||||
}
|
||||
}
|
||||
|
||||
const size_t n = tree.size();
|
||||
if (n == 1) {
|
||||
// Fake value; will be fixed on upper level.
|
||||
depth[tree[0].index_right_or_value] = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
std::sort(tree.begin(), tree.end(), Compare);
|
||||
|
||||
// The nodes are:
|
||||
// [0, n): the sorted leaf nodes that we start with.
|
||||
// [n]: we add a sentinel here.
|
||||
// [n + 1, 2n): new parent nodes are added here, starting from
|
||||
// (n+1). These are naturally in ascending order.
|
||||
// [2n]: we add a sentinel at the end as well.
|
||||
// There will be (2n+1) elements at the end.
|
||||
const HuffmanTree sentinel(std::numeric_limits<uint32_t>::max(), -1, -1);
|
||||
tree.push_back(sentinel);
|
||||
tree.push_back(sentinel);
|
||||
|
||||
size_t i = 0; // Points to the next leaf node.
|
||||
size_t j = n + 1; // Points to the next non-leaf node.
|
||||
for (size_t k = n - 1; k != 0; --k) {
|
||||
size_t left;
|
||||
size_t right;
|
||||
if (tree[i].total_count <= tree[j].total_count) {
|
||||
left = i;
|
||||
++i;
|
||||
} else {
|
||||
left = j;
|
||||
++j;
|
||||
}
|
||||
if (tree[i].total_count <= tree[j].total_count) {
|
||||
right = i;
|
||||
++i;
|
||||
} else {
|
||||
right = j;
|
||||
++j;
|
||||
}
|
||||
|
||||
// The sentinel node becomes the parent node.
|
||||
size_t j_end = tree.size() - 1;
|
||||
tree[j_end].total_count =
|
||||
tree[left].total_count + tree[right].total_count;
|
||||
tree[j_end].index_left = static_cast<int16_t>(left);
|
||||
tree[j_end].index_right_or_value = static_cast<int16_t>(right);
|
||||
|
||||
// Add back the last sentinel node.
|
||||
tree.push_back(sentinel);
|
||||
}
|
||||
JXL_DASSERT(tree.size() == 2 * n + 1);
|
||||
SetDepth(tree[2 * n - 1], tree.data(), depth, 0);
|
||||
|
||||
// We need to pack the Huffman tree in tree_limit bits.
|
||||
// If this was not successful, add fake entities to the lowest values
|
||||
// and retry.
|
||||
if (*std::max_element(&depth[0], &depth[length]) <= tree_limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ValidateHuffmanTable(j_common_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 AddStandardHuffmanTables(j_common_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;
|
||||
if (cinfo->is_decompressor) {
|
||||
j_decompress_ptr cinfo_d = reinterpret_cast<j_decompress_ptr>(cinfo);
|
||||
tables = is_dc ? cinfo_d->dc_huff_tbl_ptrs : cinfo_d->ac_huff_tbl_ptrs;
|
||||
} else {
|
||||
j_compress_ptr cinfo_c = reinterpret_cast<j_compress_ptr>(cinfo);
|
||||
tables = is_dc ? cinfo_c->dc_huff_tbl_ptrs : cinfo_c->ac_huff_tbl_ptrs;
|
||||
}
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
if (tables[i] == nullptr) {
|
||||
tables[i] = jpegli_alloc_huff_table(cinfo);
|
||||
memcpy(tables[i], &std_tables[i], sizeof(JHUFF_TBL));
|
||||
ValidateHuffmanTable(cinfo, tables[i], is_dc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_HUFFMAN_H_
|
||||
#define LIB_JPEGLI_HUFFMAN_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
constexpr int kJpegHuffmanRootTableBits = 8;
|
||||
// Maximum huffman lookup table size.
|
||||
// Requirements: alphabet of 257 symbols (256 + 1 special symbol for the all 1s
|
||||
// code) and max bit length 16, the root table has 8 bits.
|
||||
// zlib/examples/enough.c works with an assumption that Huffman code is
|
||||
// "complete". Input JPEGs might have this assumption broken, hence the
|
||||
// following sum is used as estimate:
|
||||
// + number of 1-st level cells
|
||||
// + number of symbols
|
||||
// + asymptotic amount of repeated 2nd level cells
|
||||
// The third number is 1 + 3 + ... + 255 i.e. it is assumed that sub-table of
|
||||
// each "size" might be almost completely be filled with repetitions.
|
||||
// Total sum is slightly less than 1024,...
|
||||
constexpr int kJpegHuffmanLutSize = 1024;
|
||||
|
||||
struct HuffmanTableEntry {
|
||||
uint8_t bits; // number of bits used for this symbol
|
||||
uint16_t value; // symbol value or table offset
|
||||
};
|
||||
|
||||
void BuildJpegHuffmanTable(const uint32_t* count, const uint32_t* symbols,
|
||||
HuffmanTableEntry* lut);
|
||||
|
||||
// This function will create a Huffman tree.
|
||||
//
|
||||
// The (data,length) contains the population counts.
|
||||
// The tree_limit is the maximum bit depth of the Huffman codes.
|
||||
//
|
||||
// The depth contains the tree, i.e., how many bits are used for
|
||||
// the symbol.
|
||||
//
|
||||
// See http://en.wikipedia.org/wiki/Huffman_coding
|
||||
void CreateHuffmanTree(const uint32_t* data, size_t length, int tree_limit,
|
||||
uint8_t* depth);
|
||||
|
||||
void ValidateHuffmanTable(j_common_ptr cinfo, const JHUFF_TBL* table,
|
||||
bool is_dc);
|
||||
|
||||
void AddStandardHuffmanTables(j_common_ptr cinfo, bool is_dc);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_HUFFMAN_H_
|
||||
@@ -1,707 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/idct.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/decode_internal.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
#undef HWY_TARGET_INCLUDE
|
||||
#define HWY_TARGET_INCLUDE "lib/jpegli/idct.cc"
|
||||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/jpegli/transpose-inl.h"
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jpegli {
|
||||
namespace HWY_NAMESPACE {
|
||||
|
||||
// These templates are not found via ADL.
|
||||
using hwy::HWY_NAMESPACE::Abs;
|
||||
using hwy::HWY_NAMESPACE::Add;
|
||||
using hwy::HWY_NAMESPACE::Gt;
|
||||
using hwy::HWY_NAMESPACE::IfThenElseZero;
|
||||
using hwy::HWY_NAMESPACE::Mul;
|
||||
using hwy::HWY_NAMESPACE::MulAdd;
|
||||
using hwy::HWY_NAMESPACE::NegMulAdd;
|
||||
using hwy::HWY_NAMESPACE::Rebind;
|
||||
using hwy::HWY_NAMESPACE::Sub;
|
||||
using hwy::HWY_NAMESPACE::Vec;
|
||||
using hwy::HWY_NAMESPACE::Xor;
|
||||
|
||||
using D = HWY_FULL(float);
|
||||
using DI = HWY_FULL(int32_t);
|
||||
constexpr D d;
|
||||
constexpr DI di;
|
||||
|
||||
using D8 = HWY_CAPPED(float, 8);
|
||||
constexpr D8 d8;
|
||||
|
||||
void DequantBlock(const int16_t* JXL_RESTRICT qblock,
|
||||
const float* JXL_RESTRICT dequant,
|
||||
const float* JXL_RESTRICT biases, float* JXL_RESTRICT block) {
|
||||
for (size_t k = 0; k < 64; k += Lanes(d)) {
|
||||
const auto mul = Load(d, dequant + k);
|
||||
const auto bias = Load(d, biases + k);
|
||||
const Rebind<int16_t, DI> di16;
|
||||
const Vec<DI> quant_i = PromoteTo(di, Load(di16, qblock + k));
|
||||
const Rebind<float, DI> df;
|
||||
const auto quant = ConvertTo(df, quant_i);
|
||||
const auto abs_quant = Abs(quant);
|
||||
const auto not_0 = Gt(abs_quant, Zero(df));
|
||||
const auto sign_quant = Xor(quant, abs_quant);
|
||||
const auto biased_quant = Sub(quant, Xor(bias, sign_quant));
|
||||
const auto deq = IfThenElseZero(not_0, Mul(biased_quant, mul));
|
||||
Store(deq, d, block + k);
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
void ForwardEvenOdd(const float* JXL_RESTRICT a_in, size_t a_in_stride,
|
||||
float* JXL_RESTRICT a_out) {
|
||||
for (size_t i = 0; i < N / 2; i++) {
|
||||
auto in1 = LoadU(d8, a_in + 2 * i * a_in_stride);
|
||||
Store(in1, d8, a_out + i * 8);
|
||||
}
|
||||
for (size_t i = N / 2; i < N; i++) {
|
||||
auto in1 = LoadU(d8, a_in + (2 * (i - N / 2) + 1) * a_in_stride);
|
||||
Store(in1, d8, a_out + i * 8);
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
void BTranspose(float* JXL_RESTRICT coeff) {
|
||||
for (size_t i = N - 1; i > 0; i--) {
|
||||
auto in1 = Load(d8, coeff + i * 8);
|
||||
auto in2 = Load(d8, coeff + (i - 1) * 8);
|
||||
Store(Add(in1, in2), d8, coeff + i * 8);
|
||||
}
|
||||
constexpr float kSqrt2 = 1.41421356237f;
|
||||
auto sqrt2 = Set(d8, kSqrt2);
|
||||
auto in1 = Load(d8, coeff);
|
||||
Store(Mul(in1, sqrt2), d8, coeff);
|
||||
}
|
||||
|
||||
// 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,
|
||||
};
|
||||
};
|
||||
|
||||
#if JXL_CXX_LANG < JXL_CXX_17
|
||||
constexpr float WcMultipliers<4>::kMultipliers[];
|
||||
constexpr float WcMultipliers<8>::kMultipliers[];
|
||||
#endif
|
||||
|
||||
template <size_t N>
|
||||
void MultiplyAndAdd(const float* JXL_RESTRICT coeff, float* JXL_RESTRICT out,
|
||||
size_t out_stride) {
|
||||
for (size_t i = 0; i < N / 2; i++) {
|
||||
auto mul = Set(d8, WcMultipliers<N>::kMultipliers[i]);
|
||||
auto in1 = Load(d8, coeff + i * 8);
|
||||
auto in2 = Load(d8, coeff + (N / 2 + i) * 8);
|
||||
auto out1 = MulAdd(mul, in2, in1);
|
||||
auto out2 = NegMulAdd(mul, in2, in1);
|
||||
StoreU(out1, d8, out + i * out_stride);
|
||||
StoreU(out2, d8, out + (N - i - 1) * out_stride);
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
struct IDCT1DImpl;
|
||||
|
||||
template <>
|
||||
struct IDCT1DImpl<1> {
|
||||
JXL_INLINE void operator()(const float* from, size_t from_stride, float* to,
|
||||
size_t to_stride) {
|
||||
StoreU(LoadU(d8, from), d8, to);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct IDCT1DImpl<2> {
|
||||
JXL_INLINE void operator()(const float* from, size_t from_stride, float* to,
|
||||
size_t to_stride) {
|
||||
JXL_DASSERT(from_stride >= 8);
|
||||
JXL_DASSERT(to_stride >= 8);
|
||||
auto in1 = LoadU(d8, from);
|
||||
auto in2 = LoadU(d8, from + from_stride);
|
||||
StoreU(Add(in1, in2), d8, to);
|
||||
StoreU(Sub(in1, in2), d8, to + to_stride);
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
struct IDCT1DImpl {
|
||||
void operator()(const float* from, size_t from_stride, float* to,
|
||||
size_t to_stride) {
|
||||
JXL_DASSERT(from_stride >= 8);
|
||||
JXL_DASSERT(to_stride >= 8);
|
||||
HWY_ALIGN float tmp[64];
|
||||
ForwardEvenOdd<N>(from, from_stride, tmp);
|
||||
IDCT1DImpl<N / 2>()(tmp, 8, tmp, 8);
|
||||
BTranspose<N / 2>(tmp + N * 4);
|
||||
IDCT1DImpl<N / 2>()(tmp + N * 4, 8, tmp + N * 4, 8);
|
||||
MultiplyAndAdd<N>(tmp, to, to_stride);
|
||||
}
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
void IDCT1D(float* JXL_RESTRICT from, float* JXL_RESTRICT output,
|
||||
size_t output_stride) {
|
||||
for (size_t i = 0; i < 8; i += Lanes(d8)) {
|
||||
IDCT1DImpl<N>()(from + i, 8, output + i, output_stride);
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeScaledIDCT(float* JXL_RESTRICT block0, float* JXL_RESTRICT block1,
|
||||
float* JXL_RESTRICT output, size_t output_stride) {
|
||||
Transpose8x8Block(block0, block1);
|
||||
IDCT1D<8>(block1, block0, 8);
|
||||
Transpose8x8Block(block0, block1);
|
||||
IDCT1D<8>(block1, output, output_stride);
|
||||
}
|
||||
|
||||
void InverseTransformBlock8x8(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) {
|
||||
float* JXL_RESTRICT block0 = scratch_space;
|
||||
float* JXL_RESTRICT block1 = scratch_space + DCTSIZE2;
|
||||
DequantBlock(qblock, dequant, biases, block0);
|
||||
ComputeScaledIDCT(block0, block1, output, output_stride);
|
||||
}
|
||||
|
||||
// Computes the N-point IDCT of in[], and stores the result in out[]. The in[]
|
||||
// array is at most 8 values long, values in[8:N-1] are assumed to be 0.
|
||||
void Compute1dIDCT(const float* in, float* out, size_t N) {
|
||||
switch (N) {
|
||||
case 3: {
|
||||
static constexpr float kC3[3] = {
|
||||
1.414213562373,
|
||||
1.224744871392,
|
||||
0.707106781187,
|
||||
};
|
||||
float even0 = in[0] + kC3[2] * in[2];
|
||||
float even1 = in[0] - kC3[0] * in[2];
|
||||
float odd0 = kC3[1] * in[1];
|
||||
out[0] = even0 + odd0;
|
||||
out[2] = even0 - odd0;
|
||||
out[1] = even1;
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
static constexpr float kC5[5] = {
|
||||
1.414213562373, 1.344997023928, 1.144122805635,
|
||||
0.831253875555, 0.437016024449,
|
||||
};
|
||||
float even0 = in[0] + kC5[2] * in[2] + kC5[4] * in[4];
|
||||
float even1 = in[0] - kC5[4] * in[2] - kC5[2] * in[4];
|
||||
float even2 = in[0] - kC5[0] * in[2] + kC5[0] * in[4];
|
||||
float odd0 = kC5[1] * in[1] + kC5[3] * in[3];
|
||||
float odd1 = kC5[3] * in[1] - kC5[1] * in[3];
|
||||
out[0] = even0 + odd0;
|
||||
out[4] = even0 - odd0;
|
||||
out[1] = even1 + odd1;
|
||||
out[3] = even1 - odd1;
|
||||
out[2] = even2;
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
static constexpr float kC6[6] = {
|
||||
1.414213562373, 1.366025403784, 1.224744871392,
|
||||
1.000000000000, 0.707106781187, 0.366025403784,
|
||||
};
|
||||
float even0 = in[0] + kC6[2] * in[2] + kC6[4] * in[4];
|
||||
float even1 = in[0] - kC6[0] * in[4];
|
||||
float even2 = in[0] - kC6[2] * in[2] + kC6[4] * in[4];
|
||||
float odd0 = kC6[1] * in[1] + kC6[3] * in[3] + kC6[5] * in[5];
|
||||
float odd1 = kC6[3] * in[1] - kC6[3] * in[3] - kC6[3] * in[5];
|
||||
float odd2 = kC6[5] * in[1] - kC6[3] * in[3] + kC6[1] * in[5];
|
||||
out[0] = even0 + odd0;
|
||||
out[5] = even0 - odd0;
|
||||
out[1] = even1 + odd1;
|
||||
out[4] = even1 - odd1;
|
||||
out[2] = even2 + odd2;
|
||||
out[3] = even2 - odd2;
|
||||
break;
|
||||
}
|
||||
case 7: {
|
||||
static constexpr float kC7[7] = {
|
||||
1.414213562373, 1.378756275744, 1.274162392264, 1.105676685997,
|
||||
0.881747733790, 0.613604268353, 0.314692122713,
|
||||
};
|
||||
float even0 = in[0] + kC7[2] * in[2] + kC7[4] * in[4] + kC7[6] * in[6];
|
||||
float even1 = in[0] + kC7[6] * in[2] - kC7[2] * in[4] - kC7[4] * in[6];
|
||||
float even2 = in[0] - kC7[4] * in[2] - kC7[6] * in[4] + kC7[2] * in[6];
|
||||
float even3 = in[0] - kC7[0] * in[2] + kC7[0] * in[4] - kC7[0] * in[6];
|
||||
float odd0 = kC7[1] * in[1] + kC7[3] * in[3] + kC7[5] * in[5];
|
||||
float odd1 = kC7[3] * in[1] - kC7[5] * in[3] - kC7[1] * in[5];
|
||||
float odd2 = kC7[5] * in[1] - kC7[1] * in[3] + kC7[3] * in[5];
|
||||
out[0] = even0 + odd0;
|
||||
out[6] = even0 - odd0;
|
||||
out[1] = even1 + odd1;
|
||||
out[5] = even1 - odd1;
|
||||
out[2] = even2 + odd2;
|
||||
out[4] = even2 - odd2;
|
||||
out[3] = even3;
|
||||
break;
|
||||
}
|
||||
case 9: {
|
||||
static constexpr float kC9[9] = {
|
||||
1.414213562373, 1.392728480640, 1.328926048777,
|
||||
1.224744871392, 1.083350440839, 0.909038955344,
|
||||
0.707106781187, 0.483689525296, 0.245575607938,
|
||||
};
|
||||
float even0 = in[0] + kC9[2] * in[2] + kC9[4] * in[4] + kC9[6] * in[6];
|
||||
float even1 = in[0] + kC9[6] * in[2] - kC9[6] * in[4] - kC9[0] * in[6];
|
||||
float even2 = in[0] - kC9[8] * in[2] - kC9[2] * in[4] + kC9[6] * in[6];
|
||||
float even3 = in[0] - kC9[4] * in[2] + kC9[8] * in[4] + kC9[6] * in[6];
|
||||
float even4 = in[0] - kC9[0] * in[2] + kC9[0] * in[4] - kC9[0] * in[6];
|
||||
float odd0 =
|
||||
kC9[1] * in[1] + kC9[3] * in[3] + kC9[5] * in[5] + kC9[7] * in[7];
|
||||
float odd1 = kC9[3] * in[1] - kC9[3] * in[5] - kC9[3] * in[7];
|
||||
float odd2 =
|
||||
kC9[5] * in[1] - kC9[3] * in[3] - kC9[7] * in[5] + kC9[1] * in[7];
|
||||
float odd3 =
|
||||
kC9[7] * in[1] - kC9[3] * in[3] + kC9[1] * in[5] - kC9[5] * in[7];
|
||||
out[0] = even0 + odd0;
|
||||
out[8] = even0 - odd0;
|
||||
out[1] = even1 + odd1;
|
||||
out[7] = even1 - odd1;
|
||||
out[2] = even2 + odd2;
|
||||
out[6] = even2 - odd2;
|
||||
out[3] = even3 + odd3;
|
||||
out[5] = even3 - odd3;
|
||||
out[4] = even4;
|
||||
break;
|
||||
}
|
||||
case 10: {
|
||||
static constexpr float kC10[10] = {
|
||||
1.414213562373, 1.396802246667, 1.344997023928, 1.260073510670,
|
||||
1.144122805635, 1.000000000000, 0.831253875555, 0.642039521920,
|
||||
0.437016024449, 0.221231742082,
|
||||
};
|
||||
float even0 = in[0] + kC10[2] * in[2] + kC10[4] * in[4] + kC10[6] * in[6];
|
||||
float even1 = in[0] + kC10[6] * in[2] - kC10[8] * in[4] - kC10[2] * in[6];
|
||||
float even2 = in[0] - kC10[0] * in[4];
|
||||
float even3 = in[0] - kC10[6] * in[2] - kC10[8] * in[4] + kC10[2] * in[6];
|
||||
float even4 = in[0] - kC10[2] * in[2] + kC10[4] * in[4] - kC10[6] * in[6];
|
||||
float odd0 =
|
||||
kC10[1] * in[1] + kC10[3] * in[3] + kC10[5] * in[5] + kC10[7] * in[7];
|
||||
float odd1 =
|
||||
kC10[3] * in[1] + kC10[9] * in[3] - kC10[5] * in[5] - kC10[1] * in[7];
|
||||
float odd2 =
|
||||
kC10[5] * in[1] - kC10[5] * in[3] - kC10[5] * in[5] + kC10[5] * in[7];
|
||||
float odd3 =
|
||||
kC10[7] * in[1] - kC10[1] * in[3] + kC10[5] * in[5] + kC10[9] * in[7];
|
||||
float odd4 =
|
||||
kC10[9] * in[1] - kC10[7] * in[3] + kC10[5] * in[5] - kC10[3] * in[7];
|
||||
out[0] = even0 + odd0;
|
||||
out[9] = even0 - odd0;
|
||||
out[1] = even1 + odd1;
|
||||
out[8] = even1 - odd1;
|
||||
out[2] = even2 + odd2;
|
||||
out[7] = even2 - odd2;
|
||||
out[3] = even3 + odd3;
|
||||
out[6] = even3 - odd3;
|
||||
out[4] = even4 + odd4;
|
||||
out[5] = even4 - odd4;
|
||||
break;
|
||||
}
|
||||
case 11: {
|
||||
static constexpr float kC11[11] = {
|
||||
1.414213562373, 1.399818907436, 1.356927976287, 1.286413904599,
|
||||
1.189712155524, 1.068791297809, 0.926112931411, 0.764581576418,
|
||||
0.587485545401, 0.398430002847, 0.201263574413,
|
||||
};
|
||||
float even0 = in[0] + kC11[2] * in[2] + kC11[4] * in[4] + kC11[6] * in[6];
|
||||
float even1 =
|
||||
in[0] + kC11[6] * in[2] - kC11[10] * in[4] - kC11[4] * in[6];
|
||||
float even2 =
|
||||
in[0] + kC11[10] * in[2] - kC11[2] * in[4] - kC11[8] * in[6];
|
||||
float even3 = in[0] - kC11[8] * in[2] - kC11[6] * in[4] + kC11[2] * in[6];
|
||||
float even4 =
|
||||
in[0] - kC11[4] * in[2] + kC11[8] * in[4] + kC11[10] * in[6];
|
||||
float even5 = in[0] - kC11[0] * in[2] + kC11[0] * in[4] - kC11[0] * in[6];
|
||||
float odd0 =
|
||||
kC11[1] * in[1] + kC11[3] * in[3] + kC11[5] * in[5] + kC11[7] * in[7];
|
||||
float odd1 =
|
||||
kC11[3] * in[1] + kC11[9] * in[3] - kC11[7] * in[5] - kC11[1] * in[7];
|
||||
float odd2 =
|
||||
kC11[5] * in[1] - kC11[7] * in[3] - kC11[3] * in[5] + kC11[9] * in[7];
|
||||
float odd3 =
|
||||
kC11[7] * in[1] - kC11[1] * in[3] + kC11[9] * in[5] + kC11[5] * in[7];
|
||||
float odd4 =
|
||||
kC11[9] * in[1] - kC11[5] * in[3] + kC11[1] * in[5] - kC11[3] * in[7];
|
||||
out[0] = even0 + odd0;
|
||||
out[10] = even0 - odd0;
|
||||
out[1] = even1 + odd1;
|
||||
out[9] = even1 - odd1;
|
||||
out[2] = even2 + odd2;
|
||||
out[8] = even2 - odd2;
|
||||
out[3] = even3 + odd3;
|
||||
out[7] = even3 - odd3;
|
||||
out[4] = even4 + odd4;
|
||||
out[6] = even4 - odd4;
|
||||
out[5] = even5;
|
||||
break;
|
||||
}
|
||||
case 12: {
|
||||
static constexpr float kC12[12] = {
|
||||
1.414213562373, 1.402114769300, 1.366025403784, 1.306562964876,
|
||||
1.224744871392, 1.121971053594, 1.000000000000, 0.860918669154,
|
||||
0.707106781187, 0.541196100146, 0.366025403784, 0.184591911283,
|
||||
};
|
||||
float even0 = in[0] + kC12[2] * in[2] + kC12[4] * in[4] + kC12[6] * in[6];
|
||||
float even1 = in[0] + kC12[6] * in[2] - kC12[6] * in[6];
|
||||
float even2 =
|
||||
in[0] + kC12[10] * in[2] - kC12[4] * in[4] - kC12[6] * in[6];
|
||||
float even3 =
|
||||
in[0] - kC12[10] * in[2] - kC12[4] * in[4] + kC12[6] * in[6];
|
||||
float even4 = in[0] - kC12[6] * in[2] + kC12[6] * in[6];
|
||||
float even5 = in[0] - kC12[2] * in[2] + kC12[4] * in[4] - kC12[6] * in[6];
|
||||
float odd0 =
|
||||
kC12[1] * in[1] + kC12[3] * in[3] + kC12[5] * in[5] + kC12[7] * in[7];
|
||||
float odd1 =
|
||||
kC12[3] * in[1] + kC12[9] * in[3] - kC12[9] * in[5] - kC12[3] * in[7];
|
||||
float odd2 = kC12[5] * in[1] - kC12[9] * in[3] - kC12[1] * in[5] -
|
||||
kC12[11] * in[7];
|
||||
float odd3 = kC12[7] * in[1] - kC12[3] * in[3] - kC12[11] * in[5] +
|
||||
kC12[1] * in[7];
|
||||
float odd4 =
|
||||
kC12[9] * in[1] - kC12[3] * in[3] + kC12[3] * in[5] - kC12[9] * in[7];
|
||||
float odd5 = kC12[11] * in[1] - kC12[9] * in[3] + kC12[7] * in[5] -
|
||||
kC12[5] * in[7];
|
||||
out[0] = even0 + odd0;
|
||||
out[11] = even0 - odd0;
|
||||
out[1] = even1 + odd1;
|
||||
out[10] = even1 - odd1;
|
||||
out[2] = even2 + odd2;
|
||||
out[9] = even2 - odd2;
|
||||
out[3] = even3 + odd3;
|
||||
out[8] = even3 - odd3;
|
||||
out[4] = even4 + odd4;
|
||||
out[7] = even4 - odd4;
|
||||
out[5] = even5 + odd5;
|
||||
out[6] = even5 - odd5;
|
||||
break;
|
||||
}
|
||||
case 13: {
|
||||
static constexpr float kC13[13] = {
|
||||
1.414213562373, 1.403902353238, 1.373119086479, 1.322312651445,
|
||||
1.252223920364, 1.163874944761, 1.058554051646, 0.937797056801,
|
||||
0.803364869133, 0.657217812653, 0.501487040539, 0.338443458124,
|
||||
0.170464607981,
|
||||
};
|
||||
float even0 = in[0] + kC13[2] * in[2] + kC13[4] * in[4] + kC13[6] * in[6];
|
||||
float even1 =
|
||||
in[0] + kC13[6] * in[2] + kC13[12] * in[4] - kC13[8] * in[6];
|
||||
float even2 =
|
||||
in[0] + kC13[10] * in[2] - kC13[6] * in[4] - kC13[4] * in[6];
|
||||
float even3 =
|
||||
in[0] - kC13[12] * in[2] - kC13[2] * in[4] + kC13[10] * in[6];
|
||||
float even4 =
|
||||
in[0] - kC13[8] * in[2] - kC13[10] * in[4] + kC13[2] * in[6];
|
||||
float even5 =
|
||||
in[0] - kC13[4] * in[2] + kC13[8] * in[4] - kC13[12] * in[6];
|
||||
float even6 = in[0] - kC13[0] * in[2] + kC13[0] * in[4] - kC13[0] * in[6];
|
||||
float odd0 =
|
||||
kC13[1] * in[1] + kC13[3] * in[3] + kC13[5] * in[5] + kC13[7] * in[7];
|
||||
float odd1 = kC13[3] * in[1] + kC13[9] * in[3] - kC13[11] * in[5] -
|
||||
kC13[5] * in[7];
|
||||
float odd2 = kC13[5] * in[1] - kC13[11] * in[3] - kC13[1] * in[5] -
|
||||
kC13[9] * in[7];
|
||||
float odd3 =
|
||||
kC13[7] * in[1] - kC13[5] * in[3] - kC13[9] * in[5] + kC13[3] * in[7];
|
||||
float odd4 = kC13[9] * in[1] - kC13[1] * in[3] + kC13[7] * in[5] +
|
||||
kC13[11] * in[7];
|
||||
float odd5 = kC13[11] * in[1] - kC13[7] * in[3] + kC13[3] * in[5] -
|
||||
kC13[1] * in[7];
|
||||
out[0] = even0 + odd0;
|
||||
out[12] = even0 - odd0;
|
||||
out[1] = even1 + odd1;
|
||||
out[11] = even1 - odd1;
|
||||
out[2] = even2 + odd2;
|
||||
out[10] = even2 - odd2;
|
||||
out[3] = even3 + odd3;
|
||||
out[9] = even3 - odd3;
|
||||
out[4] = even4 + odd4;
|
||||
out[8] = even4 - odd4;
|
||||
out[5] = even5 + odd5;
|
||||
out[7] = even5 - odd5;
|
||||
out[6] = even6;
|
||||
break;
|
||||
}
|
||||
case 14: {
|
||||
static constexpr float kC14[14] = {
|
||||
1.414213562373, 1.405321284327, 1.378756275744, 1.334852607020,
|
||||
1.274162392264, 1.197448846138, 1.105676685997, 1.000000000000,
|
||||
0.881747733790, 0.752406978226, 0.613604268353, 0.467085128785,
|
||||
0.314692122713, 0.158341680609,
|
||||
};
|
||||
float even0 = in[0] + kC14[2] * in[2] + kC14[4] * in[4] + kC14[6] * in[6];
|
||||
float even1 =
|
||||
in[0] + kC14[6] * in[2] + kC14[12] * in[4] - kC14[10] * in[6];
|
||||
float even2 =
|
||||
in[0] + kC14[10] * in[2] - kC14[8] * in[4] - kC14[2] * in[6];
|
||||
float even3 = in[0] - kC14[0] * in[4];
|
||||
float even4 =
|
||||
in[0] - kC14[10] * in[2] - kC14[8] * in[4] + kC14[2] * in[6];
|
||||
float even5 =
|
||||
in[0] - kC14[6] * in[2] + kC14[12] * in[4] + kC14[10] * in[6];
|
||||
float even6 = in[0] - kC14[2] * in[2] + kC14[4] * in[4] - kC14[6] * in[6];
|
||||
float odd0 =
|
||||
kC14[1] * in[1] + kC14[3] * in[3] + kC14[5] * in[5] + kC14[7] * in[7];
|
||||
float odd1 = kC14[3] * in[1] + kC14[9] * in[3] - kC14[13] * in[5] -
|
||||
kC14[7] * in[7];
|
||||
float odd2 = kC14[5] * in[1] - kC14[13] * in[3] - kC14[3] * in[5] -
|
||||
kC14[7] * in[7];
|
||||
float odd3 =
|
||||
kC14[7] * in[1] - kC14[7] * in[3] - kC14[7] * in[5] + kC14[7] * in[7];
|
||||
float odd4 = kC14[9] * in[1] - kC14[1] * in[3] + kC14[11] * in[5] +
|
||||
kC14[7] * in[7];
|
||||
float odd5 = kC14[11] * in[1] - kC14[5] * in[3] + kC14[1] * in[5] -
|
||||
kC14[7] * in[7];
|
||||
float odd6 = kC14[13] * in[1] - kC14[11] * in[3] + kC14[9] * in[5] -
|
||||
kC14[7] * in[7];
|
||||
out[0] = even0 + odd0;
|
||||
out[13] = even0 - odd0;
|
||||
out[1] = even1 + odd1;
|
||||
out[12] = even1 - odd1;
|
||||
out[2] = even2 + odd2;
|
||||
out[11] = even2 - odd2;
|
||||
out[3] = even3 + odd3;
|
||||
out[10] = even3 - odd3;
|
||||
out[4] = even4 + odd4;
|
||||
out[9] = even4 - odd4;
|
||||
out[5] = even5 + odd5;
|
||||
out[8] = even5 - odd5;
|
||||
out[6] = even6 + odd6;
|
||||
out[7] = even6 - odd6;
|
||||
break;
|
||||
}
|
||||
case 15: {
|
||||
static constexpr float kC15[15] = {
|
||||
1.414213562373, 1.406466352507, 1.383309602960, 1.344997023928,
|
||||
1.291948376043, 1.224744871392, 1.144122805635, 1.050965490998,
|
||||
0.946293578512, 0.831253875555, 0.707106781187, 0.575212476952,
|
||||
0.437016024449, 0.294031532930, 0.147825570407,
|
||||
};
|
||||
float even0 = in[0] + kC15[2] * in[2] + kC15[4] * in[4] + kC15[6] * in[6];
|
||||
float even1 =
|
||||
in[0] + kC15[6] * in[2] + kC15[12] * in[4] - kC15[12] * in[6];
|
||||
float even2 =
|
||||
in[0] + kC15[10] * in[2] - kC15[10] * in[4] - kC15[0] * in[6];
|
||||
float even3 =
|
||||
in[0] + kC15[14] * in[2] - kC15[2] * in[4] - kC15[12] * in[6];
|
||||
float even4 =
|
||||
in[0] - kC15[12] * in[2] - kC15[6] * in[4] + kC15[6] * in[6];
|
||||
float even5 =
|
||||
in[0] - kC15[8] * in[2] - kC15[14] * in[4] + kC15[6] * in[6];
|
||||
float even6 =
|
||||
in[0] - kC15[4] * in[2] + kC15[8] * in[4] - kC15[12] * in[6];
|
||||
float even7 = in[0] - kC15[0] * in[2] + kC15[0] * in[4] - kC15[0] * in[6];
|
||||
float odd0 =
|
||||
kC15[1] * in[1] + kC15[3] * in[3] + kC15[5] * in[5] + kC15[7] * in[7];
|
||||
float odd1 = kC15[3] * in[1] + kC15[9] * in[3] - kC15[9] * in[7];
|
||||
float odd2 = kC15[5] * in[1] - kC15[5] * in[5] - kC15[5] * in[7];
|
||||
float odd3 = kC15[7] * in[1] - kC15[9] * in[3] - kC15[5] * in[5] +
|
||||
kC15[11] * in[7];
|
||||
float odd4 = kC15[9] * in[1] - kC15[3] * in[3] + kC15[3] * in[7];
|
||||
float odd5 = kC15[11] * in[1] - kC15[3] * in[3] + kC15[5] * in[5] -
|
||||
kC15[13] * in[7];
|
||||
float odd6 = kC15[13] * in[1] - kC15[9] * in[3] + kC15[5] * in[5] -
|
||||
kC15[1] * in[7];
|
||||
out[0] = even0 + odd0;
|
||||
out[14] = even0 - odd0;
|
||||
out[1] = even1 + odd1;
|
||||
out[13] = even1 - odd1;
|
||||
out[2] = even2 + odd2;
|
||||
out[12] = even2 - odd2;
|
||||
out[3] = even3 + odd3;
|
||||
out[11] = even3 - odd3;
|
||||
out[4] = even4 + odd4;
|
||||
out[10] = even4 - odd4;
|
||||
out[5] = even5 + odd5;
|
||||
out[9] = even5 - odd5;
|
||||
out[6] = even6 + odd6;
|
||||
out[8] = even6 - odd6;
|
||||
out[7] = even7;
|
||||
break;
|
||||
}
|
||||
case 16: {
|
||||
static constexpr float kC16[16] = {
|
||||
1.414213562373, 1.407403737526, 1.387039845322, 1.353318001174,
|
||||
1.306562964876, 1.247225012987, 1.175875602419, 1.093201867002,
|
||||
1.000000000000, 0.897167586343, 0.785694958387, 0.666655658478,
|
||||
0.541196100146, 0.410524527522, 0.275899379283, 0.138617169199,
|
||||
};
|
||||
float even0 = in[0] + kC16[2] * in[2] + kC16[4] * in[4] + kC16[6] * in[6];
|
||||
float even1 =
|
||||
in[0] + kC16[6] * in[2] + kC16[12] * in[4] - kC16[14] * in[6];
|
||||
float even2 =
|
||||
in[0] + kC16[10] * in[2] - kC16[12] * in[4] - kC16[2] * in[6];
|
||||
float even3 =
|
||||
in[0] + kC16[14] * in[2] - kC16[4] * in[4] - kC16[10] * in[6];
|
||||
float even4 =
|
||||
in[0] - kC16[14] * in[2] - kC16[4] * in[4] + kC16[10] * in[6];
|
||||
float even5 =
|
||||
in[0] - kC16[10] * in[2] - kC16[12] * in[4] + kC16[2] * in[6];
|
||||
float even6 =
|
||||
in[0] - kC16[6] * in[2] + kC16[12] * in[4] + kC16[14] * in[6];
|
||||
float even7 = in[0] - kC16[2] * in[2] + kC16[4] * in[4] - kC16[6] * in[6];
|
||||
float odd0 = (kC16[1] * in[1] + kC16[3] * in[3] + kC16[5] * in[5] +
|
||||
kC16[7] * in[7]);
|
||||
float odd1 = (kC16[3] * in[1] + kC16[9] * in[3] + kC16[15] * in[5] -
|
||||
kC16[11] * in[7]);
|
||||
float odd2 = (kC16[5] * in[1] + kC16[15] * in[3] - kC16[7] * in[5] -
|
||||
kC16[3] * in[7]);
|
||||
float odd3 = (kC16[7] * in[1] - kC16[11] * in[3] - kC16[3] * in[5] +
|
||||
kC16[15] * in[7]);
|
||||
float odd4 = (kC16[9] * in[1] - kC16[5] * in[3] - kC16[13] * in[5] +
|
||||
kC16[1] * in[7]);
|
||||
float odd5 = (kC16[11] * in[1] - kC16[1] * in[3] + kC16[9] * in[5] +
|
||||
kC16[13] * in[7]);
|
||||
float odd6 = (kC16[13] * in[1] - kC16[7] * in[3] + kC16[1] * in[5] -
|
||||
kC16[5] * in[7]);
|
||||
float odd7 = (kC16[15] * in[1] - kC16[13] * in[3] + kC16[11] * in[5] -
|
||||
kC16[9] * in[7]);
|
||||
out[0] = even0 + odd0;
|
||||
out[15] = even0 - odd0;
|
||||
out[1] = even1 + odd1;
|
||||
out[14] = even1 - odd1;
|
||||
out[2] = even2 + odd2;
|
||||
out[13] = even2 - odd2;
|
||||
out[3] = even3 + odd3;
|
||||
out[12] = even3 - odd3;
|
||||
out[4] = even4 + odd4;
|
||||
out[11] = even4 - odd4;
|
||||
out[5] = even5 + odd5;
|
||||
out[10] = even5 - odd5;
|
||||
out[6] = even6 + odd6;
|
||||
out[9] = even6 - odd6;
|
||||
out[7] = even7 + odd7;
|
||||
out[8] = even7 - odd7;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
JXL_DEBUG_ABORT("Unreachable");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void InverseTransformBlockGeneric(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) {
|
||||
float* JXL_RESTRICT block0 = scratch_space;
|
||||
float* JXL_RESTRICT block1 = scratch_space + DCTSIZE2;
|
||||
DequantBlock(qblock, dequant, biases, block0);
|
||||
if (dctsize == 1) {
|
||||
*output = *block0;
|
||||
} else if (dctsize == 2 || dctsize == 4) {
|
||||
float* JXL_RESTRICT block2 = scratch_space + 2 * DCTSIZE2;
|
||||
ComputeScaledIDCT(block0, block1, block2, 8);
|
||||
if (dctsize == 4) {
|
||||
for (size_t iy = 0; iy < 4; ++iy) {
|
||||
for (size_t ix = 0; ix < 4; ++ix) {
|
||||
float* block = &block2[16 * iy + 2 * ix];
|
||||
output[iy * output_stride + ix] =
|
||||
0.25f * (block[0] + block[1] + block[8] + block[9]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (size_t iy = 0; iy < 2; ++iy) {
|
||||
for (size_t ix = 0; ix < 2; ++ix) {
|
||||
float* block = &block2[32 * iy + 4 * ix];
|
||||
output[iy * output_stride + ix] =
|
||||
0.0625f *
|
||||
(block[0] + block[1] + block[2] + block[3] + block[8] + block[9] +
|
||||
block[10] + block[11] + block[16] + block[17] + block[18] +
|
||||
block[19] + block[24] + block[25] + block[26] + block[27]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
float dctin[DCTSIZE];
|
||||
float dctout[DCTSIZE * 2];
|
||||
size_t insize = std::min<size_t>(dctsize, DCTSIZE);
|
||||
for (size_t ix = 0; ix < insize; ++ix) {
|
||||
for (size_t iy = 0; iy < insize; ++iy) {
|
||||
dctin[iy] = block0[iy * DCTSIZE + ix];
|
||||
}
|
||||
Compute1dIDCT(dctin, dctout, dctsize);
|
||||
for (size_t iy = 0; iy < dctsize; ++iy) {
|
||||
block1[iy * dctsize + ix] = dctout[iy];
|
||||
}
|
||||
}
|
||||
for (size_t iy = 0; iy < dctsize; ++iy) {
|
||||
Compute1dIDCT(block1 + iy * dctsize, output + iy * output_stride,
|
||||
dctsize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
|
||||
#if HWY_ONCE
|
||||
namespace jpegli {
|
||||
|
||||
HWY_EXPORT(InverseTransformBlock8x8);
|
||||
HWY_EXPORT(InverseTransformBlockGeneric);
|
||||
|
||||
jxl::Status ChooseInverseTransform(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
int dct_size = m->scaled_dct_size[c];
|
||||
if (dct_size < 1 || dct_size > 16) {
|
||||
return JXL_FAILURE("Compute1dIDCT does not support N=%d", dct_size);
|
||||
}
|
||||
if (dct_size == DCTSIZE) {
|
||||
m->inverse_transform[c] = HWY_DYNAMIC_DISPATCH(InverseTransformBlock8x8);
|
||||
} else {
|
||||
m->inverse_transform[c] =
|
||||
HWY_DYNAMIC_DISPATCH(InverseTransformBlockGeneric);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
#endif // HWY_ONCE
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_IDCT_H_
|
||||
#define LIB_JPEGLI_IDCT_H_
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
jxl::Status ChooseInverseTransform(j_decompress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_IDCT_H_
|
||||
@@ -1,421 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/input.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/types.h"
|
||||
|
||||
#undef HWY_TARGET_INCLUDE
|
||||
#define HWY_TARGET_INCLUDE "lib/jpegli/input.cc"
|
||||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jxl/base/byte_order.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jpegli {
|
||||
namespace HWY_NAMESPACE {
|
||||
|
||||
using hwy::HWY_NAMESPACE::Mul;
|
||||
using hwy::HWY_NAMESPACE::Rebind;
|
||||
using hwy::HWY_NAMESPACE::Vec;
|
||||
|
||||
using D = HWY_FULL(float);
|
||||
using DU = HWY_FULL(uint32_t);
|
||||
using DU8 = Rebind<uint8_t, D>;
|
||||
using DU16 = Rebind<uint16_t, D>;
|
||||
|
||||
constexpr D d;
|
||||
constexpr DU du;
|
||||
constexpr DU8 du8;
|
||||
constexpr DU16 du16;
|
||||
|
||||
static constexpr double kMul16 = 1.0 / 257.0;
|
||||
static constexpr double kMulFloat = 255.0;
|
||||
|
||||
template <size_t C>
|
||||
void ReadUint8Row(const uint8_t* row_in, size_t x0, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
for (size_t x = x0; x < len; ++x) {
|
||||
for (size_t c = 0; c < C; ++c) {
|
||||
row_out[c][x] = row_in[C * x + c];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t C, bool swap_endianness = false>
|
||||
void ReadUint16Row(const uint8_t* row_in, size_t x0, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
const uint16_t* row16 = reinterpret_cast<const uint16_t*>(row_in);
|
||||
for (size_t x = x0; x < len; ++x) {
|
||||
for (size_t c = 0; c < C; ++c) {
|
||||
uint16_t val = row16[C * x + c];
|
||||
if (swap_endianness) val = JXL_BSWAP16(val);
|
||||
row_out[c][x] = val * kMul16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t C, bool swap_endianness = false>
|
||||
void ReadFloatRow(const uint8_t* row_in, size_t x0, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
const float* rowf = reinterpret_cast<const float*>(row_in);
|
||||
for (size_t x = x0; x < len; ++x) {
|
||||
for (size_t c = 0; c < C; ++c) {
|
||||
float val = rowf[C * x + c];
|
||||
if (swap_endianness) val = BSwapFloat(val);
|
||||
row_out[c][x] = val * kMulFloat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReadUint8RowSingle(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t simd_len = len & (~(N - 1));
|
||||
float* JXL_RESTRICT const row0 = row_out[0];
|
||||
for (size_t x = 0; x < simd_len; x += N) {
|
||||
Store(ConvertTo(d, PromoteTo(du, LoadU(du8, row_in + x))), d, row0 + x);
|
||||
}
|
||||
ReadUint8Row<1>(row_in, simd_len, len, row_out);
|
||||
}
|
||||
|
||||
void ReadUint8RowInterleaved2(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t simd_len = len & (~(N - 1));
|
||||
float* JXL_RESTRICT const row0 = row_out[0];
|
||||
float* JXL_RESTRICT const row1 = row_out[1];
|
||||
Vec<DU8> out0, out1; // NOLINT
|
||||
for (size_t x = 0; x < simd_len; x += N) {
|
||||
LoadInterleaved2(du8, row_in + 2 * x, out0, out1);
|
||||
Store(ConvertTo(d, PromoteTo(du, out0)), d, row0 + x);
|
||||
Store(ConvertTo(d, PromoteTo(du, out1)), d, row1 + x);
|
||||
}
|
||||
ReadUint8Row<2>(row_in, simd_len, len, row_out);
|
||||
}
|
||||
|
||||
void ReadUint8RowInterleaved3(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t simd_len = len & (~(N - 1));
|
||||
float* JXL_RESTRICT const row0 = row_out[0];
|
||||
float* JXL_RESTRICT const row1 = row_out[1];
|
||||
float* JXL_RESTRICT const row2 = row_out[2];
|
||||
Vec<DU8> out0, out1, out2; // NOLINT
|
||||
for (size_t x = 0; x < simd_len; x += N) {
|
||||
LoadInterleaved3(du8, row_in + 3 * x, out0, out1, out2);
|
||||
Store(ConvertTo(d, PromoteTo(du, out0)), d, row0 + x);
|
||||
Store(ConvertTo(d, PromoteTo(du, out1)), d, row1 + x);
|
||||
Store(ConvertTo(d, PromoteTo(du, out2)), d, row2 + x);
|
||||
}
|
||||
ReadUint8Row<3>(row_in, simd_len, len, row_out);
|
||||
}
|
||||
|
||||
void ReadUint8RowInterleaved4(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t simd_len = len & (~(N - 1));
|
||||
float* JXL_RESTRICT const row0 = row_out[0];
|
||||
float* JXL_RESTRICT const row1 = row_out[1];
|
||||
float* JXL_RESTRICT const row2 = row_out[2];
|
||||
float* JXL_RESTRICT const row3 = row_out[3];
|
||||
Vec<DU8> out0, out1, out2, out3; // NOLINT
|
||||
for (size_t x = 0; x < simd_len; x += N) {
|
||||
LoadInterleaved4(du8, row_in + 4 * x, out0, out1, out2, out3);
|
||||
Store(ConvertTo(d, PromoteTo(du, out0)), d, row0 + x);
|
||||
Store(ConvertTo(d, PromoteTo(du, out1)), d, row1 + x);
|
||||
Store(ConvertTo(d, PromoteTo(du, out2)), d, row2 + x);
|
||||
Store(ConvertTo(d, PromoteTo(du, out3)), d, row3 + x);
|
||||
}
|
||||
ReadUint8Row<4>(row_in, simd_len, len, row_out);
|
||||
}
|
||||
|
||||
void ReadUint16RowSingle(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t simd_len = len & (~(N - 1));
|
||||
const auto mul = Set(d, kMul16);
|
||||
const uint16_t* JXL_RESTRICT const row =
|
||||
reinterpret_cast<const uint16_t*>(row_in);
|
||||
float* JXL_RESTRICT const row0 = row_out[0];
|
||||
for (size_t x = 0; x < simd_len; x += N) {
|
||||
Store(Mul(mul, ConvertTo(d, PromoteTo(du, LoadU(du16, row + x)))), d,
|
||||
row0 + x);
|
||||
}
|
||||
ReadUint16Row<1>(row_in, simd_len, len, row_out);
|
||||
}
|
||||
|
||||
void ReadUint16RowInterleaved2(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t simd_len = len & (~(N - 1));
|
||||
const auto mul = Set(d, kMul16);
|
||||
const uint16_t* JXL_RESTRICT const row =
|
||||
reinterpret_cast<const uint16_t*>(row_in);
|
||||
float* JXL_RESTRICT const row0 = row_out[0];
|
||||
float* JXL_RESTRICT const row1 = row_out[1];
|
||||
Vec<DU16> out0, out1; // NOLINT
|
||||
for (size_t x = 0; x < simd_len; x += N) {
|
||||
LoadInterleaved2(du16, row + 2 * x, out0, out1);
|
||||
Store(Mul(mul, ConvertTo(d, PromoteTo(du, out0))), d, row0 + x);
|
||||
Store(Mul(mul, ConvertTo(d, PromoteTo(du, out1))), d, row1 + x);
|
||||
}
|
||||
ReadUint16Row<2>(row_in, simd_len, len, row_out);
|
||||
}
|
||||
|
||||
void ReadUint16RowInterleaved3(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t simd_len = len & (~(N - 1));
|
||||
const auto mul = Set(d, kMul16);
|
||||
const uint16_t* JXL_RESTRICT const row =
|
||||
reinterpret_cast<const uint16_t*>(row_in);
|
||||
float* JXL_RESTRICT const row0 = row_out[0];
|
||||
float* JXL_RESTRICT const row1 = row_out[1];
|
||||
float* JXL_RESTRICT const row2 = row_out[2];
|
||||
Vec<DU16> out0, out1, out2; // NOLINT
|
||||
for (size_t x = 0; x < simd_len; x += N) {
|
||||
LoadInterleaved3(du16, row + 3 * x, out0, out1, out2);
|
||||
Store(Mul(mul, ConvertTo(d, PromoteTo(du, out0))), d, row0 + x);
|
||||
Store(Mul(mul, ConvertTo(d, PromoteTo(du, out1))), d, row1 + x);
|
||||
Store(Mul(mul, ConvertTo(d, PromoteTo(du, out2))), d, row2 + x);
|
||||
}
|
||||
ReadUint16Row<3>(row_in, simd_len, len, row_out);
|
||||
}
|
||||
|
||||
void ReadUint16RowInterleaved4(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t simd_len = len & (~(N - 1));
|
||||
const auto mul = Set(d, kMul16);
|
||||
const uint16_t* JXL_RESTRICT const row =
|
||||
reinterpret_cast<const uint16_t*>(row_in);
|
||||
float* JXL_RESTRICT const row0 = row_out[0];
|
||||
float* JXL_RESTRICT const row1 = row_out[1];
|
||||
float* JXL_RESTRICT const row2 = row_out[2];
|
||||
float* JXL_RESTRICT const row3 = row_out[3];
|
||||
Vec<DU16> out0, out1, out2, out3; // NOLINT
|
||||
for (size_t x = 0; x < simd_len; x += N) {
|
||||
LoadInterleaved4(du16, row + 4 * x, out0, out1, out2, out3);
|
||||
Store(Mul(mul, ConvertTo(d, PromoteTo(du, out0))), d, row0 + x);
|
||||
Store(Mul(mul, ConvertTo(d, PromoteTo(du, out1))), d, row1 + x);
|
||||
Store(Mul(mul, ConvertTo(d, PromoteTo(du, out2))), d, row2 + x);
|
||||
Store(Mul(mul, ConvertTo(d, PromoteTo(du, out3))), d, row3 + x);
|
||||
}
|
||||
ReadUint16Row<4>(row_in, simd_len, len, row_out);
|
||||
}
|
||||
|
||||
void ReadUint16RowSingleSwap(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
ReadUint16Row<1, true>(row_in, 0, len, row_out);
|
||||
}
|
||||
|
||||
void ReadUint16RowInterleaved2Swap(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
ReadUint16Row<2, true>(row_in, 0, len, row_out);
|
||||
}
|
||||
|
||||
void ReadUint16RowInterleaved3Swap(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
ReadUint16Row<3, true>(row_in, 0, len, row_out);
|
||||
}
|
||||
|
||||
void ReadUint16RowInterleaved4Swap(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
ReadUint16Row<4, true>(row_in, 0, len, row_out);
|
||||
}
|
||||
|
||||
void ReadFloatRowSingle(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t simd_len = len & (~(N - 1));
|
||||
const auto mul = Set(d, kMulFloat);
|
||||
const float* JXL_RESTRICT const row = reinterpret_cast<const float*>(row_in);
|
||||
float* JXL_RESTRICT const row0 = row_out[0];
|
||||
for (size_t x = 0; x < simd_len; x += N) {
|
||||
Store(Mul(mul, LoadU(d, row + x)), d, row0 + x);
|
||||
}
|
||||
ReadFloatRow<1>(row_in, simd_len, len, row_out);
|
||||
}
|
||||
|
||||
void ReadFloatRowInterleaved2(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t simd_len = len & (~(N - 1));
|
||||
const auto mul = Set(d, kMulFloat);
|
||||
const float* JXL_RESTRICT const row = reinterpret_cast<const float*>(row_in);
|
||||
float* JXL_RESTRICT const row0 = row_out[0];
|
||||
float* JXL_RESTRICT const row1 = row_out[1];
|
||||
Vec<D> out0, out1; // NOLINT
|
||||
for (size_t x = 0; x < simd_len; x += N) {
|
||||
LoadInterleaved2(d, row + 2 * x, out0, out1);
|
||||
Store(Mul(mul, out0), d, row0 + x);
|
||||
Store(Mul(mul, out1), d, row1 + x);
|
||||
}
|
||||
ReadFloatRow<2>(row_in, simd_len, len, row_out);
|
||||
}
|
||||
|
||||
void ReadFloatRowInterleaved3(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t simd_len = len & (~(N - 1));
|
||||
const auto mul = Set(d, kMulFloat);
|
||||
const float* JXL_RESTRICT const row = reinterpret_cast<const float*>(row_in);
|
||||
float* JXL_RESTRICT const row0 = row_out[0];
|
||||
float* JXL_RESTRICT const row1 = row_out[1];
|
||||
float* JXL_RESTRICT const row2 = row_out[2];
|
||||
Vec<D> out0, out1, out2; // NOLINT
|
||||
for (size_t x = 0; x < simd_len; x += N) {
|
||||
LoadInterleaved3(d, row + 3 * x, out0, out1, out2);
|
||||
Store(Mul(mul, out0), d, row0 + x);
|
||||
Store(Mul(mul, out1), d, row1 + x);
|
||||
Store(Mul(mul, out2), d, row2 + x);
|
||||
}
|
||||
ReadFloatRow<3>(row_in, simd_len, len, row_out);
|
||||
}
|
||||
|
||||
void ReadFloatRowInterleaved4(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
const size_t N = Lanes(d);
|
||||
const size_t simd_len = len & (~(N - 1));
|
||||
const auto mul = Set(d, kMulFloat);
|
||||
const float* JXL_RESTRICT const row = reinterpret_cast<const float*>(row_in);
|
||||
float* JXL_RESTRICT const row0 = row_out[0];
|
||||
float* JXL_RESTRICT const row1 = row_out[1];
|
||||
float* JXL_RESTRICT const row2 = row_out[2];
|
||||
float* JXL_RESTRICT const row3 = row_out[3];
|
||||
Vec<D> out0, out1, out2, out3; // NOLINT
|
||||
for (size_t x = 0; x < simd_len; x += N) {
|
||||
LoadInterleaved4(d, row + 4 * x, out0, out1, out2, out3);
|
||||
Store(Mul(mul, out0), d, row0 + x);
|
||||
Store(Mul(mul, out1), d, row1 + x);
|
||||
Store(Mul(mul, out2), d, row2 + x);
|
||||
Store(Mul(mul, out3), d, row3 + x);
|
||||
}
|
||||
ReadFloatRow<4>(row_in, simd_len, len, row_out);
|
||||
}
|
||||
|
||||
void ReadFloatRowSingleSwap(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
ReadFloatRow<1, true>(row_in, 0, len, row_out);
|
||||
}
|
||||
|
||||
void ReadFloatRowInterleaved2Swap(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
ReadFloatRow<2, true>(row_in, 0, len, row_out);
|
||||
}
|
||||
|
||||
void ReadFloatRowInterleaved3Swap(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
ReadFloatRow<3, true>(row_in, 0, len, row_out);
|
||||
}
|
||||
|
||||
void ReadFloatRowInterleaved4Swap(const uint8_t* row_in, size_t len,
|
||||
float* row_out[kMaxComponents]) {
|
||||
ReadFloatRow<4, true>(row_in, 0, len, row_out);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
|
||||
#if HWY_ONCE
|
||||
namespace jpegli {
|
||||
|
||||
HWY_EXPORT(ReadUint8RowSingle);
|
||||
HWY_EXPORT(ReadUint8RowInterleaved2);
|
||||
HWY_EXPORT(ReadUint8RowInterleaved3);
|
||||
HWY_EXPORT(ReadUint8RowInterleaved4);
|
||||
HWY_EXPORT(ReadUint16RowSingle);
|
||||
HWY_EXPORT(ReadUint16RowInterleaved2);
|
||||
HWY_EXPORT(ReadUint16RowInterleaved3);
|
||||
HWY_EXPORT(ReadUint16RowInterleaved4);
|
||||
HWY_EXPORT(ReadUint16RowSingleSwap);
|
||||
HWY_EXPORT(ReadUint16RowInterleaved2Swap);
|
||||
HWY_EXPORT(ReadUint16RowInterleaved3Swap);
|
||||
HWY_EXPORT(ReadUint16RowInterleaved4Swap);
|
||||
HWY_EXPORT(ReadFloatRowSingle);
|
||||
HWY_EXPORT(ReadFloatRowInterleaved2);
|
||||
HWY_EXPORT(ReadFloatRowInterleaved3);
|
||||
HWY_EXPORT(ReadFloatRowInterleaved4);
|
||||
HWY_EXPORT(ReadFloatRowSingleSwap);
|
||||
HWY_EXPORT(ReadFloatRowInterleaved2Swap);
|
||||
HWY_EXPORT(ReadFloatRowInterleaved3Swap);
|
||||
HWY_EXPORT(ReadFloatRowInterleaved4Swap);
|
||||
|
||||
void ChooseInputMethod(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
bool swap_endianness =
|
||||
(m->endianness == JPEGLI_LITTLE_ENDIAN && !IsLittleEndian()) ||
|
||||
(m->endianness == JPEGLI_BIG_ENDIAN && IsLittleEndian());
|
||||
m->input_method = nullptr;
|
||||
if (m->data_type == JPEGLI_TYPE_UINT8) {
|
||||
if (cinfo->raw_data_in || cinfo->input_components == 1) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint8RowSingle);
|
||||
} else if (cinfo->input_components == 2) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint8RowInterleaved2);
|
||||
} else if (cinfo->input_components == 3) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint8RowInterleaved3);
|
||||
} else if (cinfo->input_components == 4) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint8RowInterleaved4);
|
||||
}
|
||||
} else if (m->data_type == JPEGLI_TYPE_UINT16 && !swap_endianness) {
|
||||
if (cinfo->raw_data_in || cinfo->input_components == 1) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowSingle);
|
||||
} else if (cinfo->input_components == 2) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowInterleaved2);
|
||||
} else if (cinfo->input_components == 3) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowInterleaved3);
|
||||
} else if (cinfo->input_components == 4) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowInterleaved4);
|
||||
}
|
||||
} else if (m->data_type == JPEGLI_TYPE_UINT16 && swap_endianness) {
|
||||
if (cinfo->raw_data_in || cinfo->input_components == 1) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowSingleSwap);
|
||||
} else if (cinfo->input_components == 2) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowInterleaved2Swap);
|
||||
} else if (cinfo->input_components == 3) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowInterleaved3Swap);
|
||||
} else if (cinfo->input_components == 4) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadUint16RowInterleaved4Swap);
|
||||
}
|
||||
} else if (m->data_type == JPEGLI_TYPE_FLOAT && !swap_endianness) {
|
||||
if (cinfo->raw_data_in || cinfo->input_components == 1) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowSingle);
|
||||
} else if (cinfo->input_components == 2) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowInterleaved2);
|
||||
} else if (cinfo->input_components == 3) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowInterleaved3);
|
||||
} else if (cinfo->input_components == 4) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowInterleaved4);
|
||||
}
|
||||
} else if (m->data_type == JPEGLI_TYPE_FLOAT && swap_endianness) {
|
||||
if (cinfo->raw_data_in || cinfo->input_components == 1) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowSingleSwap);
|
||||
} else if (cinfo->input_components == 2) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowInterleaved2Swap);
|
||||
} else if (cinfo->input_components == 3) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowInterleaved3Swap);
|
||||
} else if (cinfo->input_components == 4) {
|
||||
m->input_method = HWY_DYNAMIC_DISPATCH(ReadFloatRowInterleaved4Swap);
|
||||
}
|
||||
}
|
||||
if (m->input_method == nullptr) {
|
||||
JPEGLI_ERROR("Could not find input method.");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
#endif // HWY_ONCE
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_INPUT_H_
|
||||
#define LIB_JPEGLI_INPUT_H_
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void ChooseInputMethod(j_compress_ptr cinfo);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_INPUT_H_
|
||||
@@ -1,633 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <jxl/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/decode.h"
|
||||
#include "lib/jpegli/libjpeg_test_util.h"
|
||||
#include "lib/jpegli/test_params.h"
|
||||
#include "lib/jpegli/test_utils.h"
|
||||
#include "lib/jpegli/testing.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jpegli {
|
||||
namespace {
|
||||
|
||||
constexpr uint8_t kFakeEoiMarker[2] = {0xff, 0xd9};
|
||||
|
||||
struct SourceManager {
|
||||
SourceManager(const uint8_t* data, size_t len, size_t max_chunk_size,
|
||||
bool is_partial_file)
|
||||
: data_(data),
|
||||
len_(len),
|
||||
pos_(0),
|
||||
max_chunk_size_(max_chunk_size),
|
||||
is_partial_file_(is_partial_file) {
|
||||
pub_.init_source = init_source;
|
||||
pub_.fill_input_buffer = fill_input_buffer;
|
||||
pub_.next_input_byte = nullptr;
|
||||
pub_.bytes_in_buffer = 0;
|
||||
pub_.skip_input_data = skip_input_data;
|
||||
pub_.resync_to_restart = jpegli_resync_to_restart;
|
||||
pub_.term_source = term_source;
|
||||
if (max_chunk_size_ == 0) max_chunk_size_ = len;
|
||||
}
|
||||
|
||||
~SourceManager() {
|
||||
EXPECT_EQ(0u, pub_.bytes_in_buffer);
|
||||
if (!is_partial_file_) {
|
||||
EXPECT_EQ(len_, pos_);
|
||||
}
|
||||
}
|
||||
|
||||
bool LoadNextChunk() {
|
||||
if (pos_ >= len_ && !is_partial_file_) {
|
||||
return false;
|
||||
}
|
||||
if (pub_.bytes_in_buffer > 0) {
|
||||
EXPECT_LE(pub_.bytes_in_buffer, buffer_.size());
|
||||
memmove(buffer_.data(), pub_.next_input_byte, pub_.bytes_in_buffer);
|
||||
}
|
||||
size_t chunk_size =
|
||||
pos_ < len_ ? std::min(len_ - pos_, max_chunk_size_) : 2;
|
||||
buffer_.resize(pub_.bytes_in_buffer + chunk_size);
|
||||
memcpy(&buffer_[pub_.bytes_in_buffer],
|
||||
pos_ < len_ ? data_ + pos_ : kFakeEoiMarker, chunk_size);
|
||||
pub_.next_input_byte = buffer_.data();
|
||||
pub_.bytes_in_buffer += chunk_size;
|
||||
pos_ += chunk_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
jpeg_source_mgr pub_;
|
||||
std::vector<uint8_t> buffer_;
|
||||
const uint8_t* data_;
|
||||
size_t len_;
|
||||
size_t pos_;
|
||||
size_t max_chunk_size_;
|
||||
bool is_partial_file_;
|
||||
|
||||
static void init_source(j_decompress_ptr cinfo) {
|
||||
auto* src = reinterpret_cast<SourceManager*>(cinfo->src);
|
||||
src->pub_.next_input_byte = nullptr;
|
||||
src->pub_.bytes_in_buffer = 0;
|
||||
}
|
||||
|
||||
static boolean fill_input_buffer(j_decompress_ptr cinfo) { return FALSE; }
|
||||
|
||||
static void skip_input_data(j_decompress_ptr cinfo,
|
||||
long num_bytes /* NOLINT*/) {
|
||||
auto* src = reinterpret_cast<SourceManager*>(cinfo->src);
|
||||
if (num_bytes <= 0) {
|
||||
return;
|
||||
}
|
||||
if (src->pub_.bytes_in_buffer >= static_cast<size_t>(num_bytes)) {
|
||||
src->pub_.bytes_in_buffer -= num_bytes;
|
||||
src->pub_.next_input_byte += num_bytes;
|
||||
} else {
|
||||
src->pos_ += num_bytes - src->pub_.bytes_in_buffer;
|
||||
src->pub_.bytes_in_buffer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void term_source(j_decompress_ptr cinfo) {}
|
||||
};
|
||||
|
||||
uint8_t markers_seen[kMarkerSequenceLen];
|
||||
size_t num_markers_seen = 0;
|
||||
|
||||
uint8_t get_next_byte(j_decompress_ptr cinfo) {
|
||||
cinfo->src->bytes_in_buffer--;
|
||||
return *cinfo->src->next_input_byte++;
|
||||
}
|
||||
|
||||
boolean test_marker_processor(j_decompress_ptr cinfo) {
|
||||
markers_seen[num_markers_seen] = cinfo->unread_marker;
|
||||
if (cinfo->src->bytes_in_buffer < 2) {
|
||||
return FALSE;
|
||||
}
|
||||
size_t marker_len = (get_next_byte(cinfo) << 8) + get_next_byte(cinfo);
|
||||
EXPECT_EQ(2u + ((num_markers_seen + 2u) % sizeof(kMarkerData)), marker_len);
|
||||
if (marker_len > 2) {
|
||||
(*cinfo->src->skip_input_data)(cinfo, marker_len - 2);
|
||||
}
|
||||
++num_markers_seen;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
jxl::Status ReadOutputImage(const DecompressParams& dparams,
|
||||
j_decompress_ptr cinfo, SourceManager* src,
|
||||
TestImage* output) {
|
||||
output->ysize = cinfo->output_height;
|
||||
output->xsize = cinfo->output_width;
|
||||
output->components = cinfo->num_components;
|
||||
if (cinfo->raw_data_out) {
|
||||
output->color_space = cinfo->jpeg_color_space;
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE;
|
||||
size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE;
|
||||
std::vector<uint8_t> plane(ysize * xsize);
|
||||
output->raw_data.emplace_back(std::move(plane));
|
||||
}
|
||||
} else {
|
||||
output->color_space = cinfo->out_color_space;
|
||||
output->AllocatePixels();
|
||||
}
|
||||
size_t total_output_lines = 0;
|
||||
while (cinfo->output_scanline < cinfo->output_height) {
|
||||
size_t max_lines;
|
||||
size_t num_output_lines;
|
||||
if (cinfo->raw_data_out) {
|
||||
size_t iMCU_height = cinfo->max_v_samp_factor * DCTSIZE;
|
||||
EXPECT_EQ(cinfo->output_scanline, cinfo->output_iMCU_row * iMCU_height);
|
||||
max_lines = iMCU_height;
|
||||
std::vector<std::vector<JSAMPROW>> rowdata(cinfo->num_components);
|
||||
std::vector<JSAMPARRAY> data(cinfo->num_components);
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE;
|
||||
size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE;
|
||||
size_t num_lines = cinfo->comp_info[c].v_samp_factor * DCTSIZE;
|
||||
rowdata[c].resize(num_lines);
|
||||
size_t y0 = cinfo->output_iMCU_row * num_lines;
|
||||
for (size_t i = 0; i < num_lines; ++i) {
|
||||
rowdata[c][i] =
|
||||
y0 + i < ysize ? &output->raw_data[c][(y0 + i) * xsize] : nullptr;
|
||||
}
|
||||
data[c] = rowdata[c].data();
|
||||
}
|
||||
while ((num_output_lines =
|
||||
jpegli_read_raw_data(cinfo, data.data(), max_lines)) == 0) {
|
||||
JXL_ENSURE(src && src->LoadNextChunk());
|
||||
}
|
||||
} else {
|
||||
size_t max_output_lines = dparams.max_output_lines;
|
||||
if (max_output_lines == 0) max_output_lines = cinfo->output_height;
|
||||
size_t lines_left = cinfo->output_height - cinfo->output_scanline;
|
||||
max_lines = std::min<size_t>(max_output_lines, lines_left);
|
||||
size_t stride = cinfo->output_width * cinfo->num_components;
|
||||
std::vector<JSAMPROW> scanlines(max_lines);
|
||||
for (size_t i = 0; i < max_lines; ++i) {
|
||||
size_t yidx = cinfo->output_scanline + i;
|
||||
scanlines[i] = &output->pixels[yidx * stride];
|
||||
}
|
||||
while ((num_output_lines = jpegli_read_scanlines(cinfo, scanlines.data(),
|
||||
max_lines)) == 0) {
|
||||
JXL_ENSURE(src && src->LoadNextChunk());
|
||||
}
|
||||
}
|
||||
total_output_lines += num_output_lines;
|
||||
EXPECT_EQ(total_output_lines, cinfo->output_scanline);
|
||||
if (num_output_lines < max_lines) {
|
||||
JXL_ENSURE(src && src->LoadNextChunk());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct TestConfig {
|
||||
std::string fn;
|
||||
std::string fn_desc;
|
||||
TestImage input;
|
||||
CompressParams jparams;
|
||||
DecompressParams dparams;
|
||||
float max_rms_dist = 1.0f;
|
||||
};
|
||||
|
||||
jxl::StatusOr<std::vector<uint8_t>> GetTestJpegData(TestConfig& config) {
|
||||
std::vector<uint8_t> compressed;
|
||||
if (!config.fn.empty()) {
|
||||
JXL_ASSIGN_OR_RETURN(compressed, ReadTestData(config.fn));
|
||||
} else {
|
||||
GeneratePixels(&config.input);
|
||||
JXL_RETURN_IF_ERROR(
|
||||
EncodeWithJpegli(config.input, config.jparams, &compressed));
|
||||
}
|
||||
return compressed;
|
||||
}
|
||||
|
||||
bool IsSequential(const TestConfig& config) {
|
||||
if (!config.fn.empty()) {
|
||||
return config.fn_desc.find("PROGR") == std::string::npos;
|
||||
}
|
||||
return config.jparams.progressive_mode <= 0;
|
||||
}
|
||||
|
||||
class InputSuspensionTestParam : public ::testing::TestWithParam<TestConfig> {};
|
||||
|
||||
TEST_P(InputSuspensionTestParam, InputOutputLockStepNonBuffered) {
|
||||
TestConfig config = GetParam();
|
||||
const DecompressParams& dparams = config.dparams;
|
||||
JXL_ASSIGN_OR_QUIT(std::vector<uint8_t> compressed, GetTestJpegData(config),
|
||||
"Failed to create test data.");
|
||||
bool is_partial = config.dparams.size_factor < 1.0f;
|
||||
if (is_partial) {
|
||||
compressed.resize(compressed.size() * config.dparams.size_factor);
|
||||
}
|
||||
SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size,
|
||||
is_partial);
|
||||
TestImage output0;
|
||||
jpeg_decompress_struct cinfo;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_decompress(&cinfo);
|
||||
cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
|
||||
|
||||
if (config.jparams.add_marker) {
|
||||
jpegli_save_markers(&cinfo, kSpecialMarker0, 0xffff);
|
||||
jpegli_save_markers(&cinfo, kSpecialMarker1, 0xffff);
|
||||
num_markers_seen = 0;
|
||||
jpegli_set_marker_processor(&cinfo, 0xe6, test_marker_processor);
|
||||
jpegli_set_marker_processor(&cinfo, 0xe7, test_marker_processor);
|
||||
jpegli_set_marker_processor(&cinfo, 0xe8, test_marker_processor);
|
||||
}
|
||||
while (jpegli_read_header(&cinfo, TRUE) == JPEG_SUSPENDED) {
|
||||
JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk());
|
||||
}
|
||||
SetDecompressParams(dparams, &cinfo);
|
||||
jpegli_set_output_format(&cinfo, dparams.data_type, dparams.endianness);
|
||||
if (config.jparams.add_marker) {
|
||||
EXPECT_EQ(num_markers_seen, kMarkerSequenceLen);
|
||||
EXPECT_EQ(0, memcmp(markers_seen, kMarkerSequence, num_markers_seen));
|
||||
}
|
||||
VerifyHeader(config.jparams, &cinfo);
|
||||
cinfo.raw_data_out = TO_JXL_BOOL(dparams.output_mode == RAW_DATA);
|
||||
|
||||
if (dparams.output_mode == COEFFICIENTS) {
|
||||
jvirt_barray_ptr* coef_arrays;
|
||||
while ((coef_arrays = jpegli_read_coefficients(&cinfo)) == nullptr) {
|
||||
JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk());
|
||||
}
|
||||
CopyCoefficients(&cinfo, coef_arrays, &output0);
|
||||
} else {
|
||||
while (!jpegli_start_decompress(&cinfo)) {
|
||||
JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk());
|
||||
}
|
||||
JPEGLI_TEST_ENSURE_TRUE(ReadOutputImage(dparams, &cinfo, &src, &output0));
|
||||
}
|
||||
|
||||
while (!jpegli_finish_decompress(&cinfo)) {
|
||||
JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk());
|
||||
}
|
||||
return true;
|
||||
};
|
||||
ASSERT_TRUE(try_catch_block());
|
||||
jpegli_destroy_decompress(&cinfo);
|
||||
|
||||
TestImage output1;
|
||||
DecodeWithLibjpeg(config.jparams, dparams, compressed, &output1);
|
||||
VerifyOutputImage(output1, output0, config.max_rms_dist);
|
||||
}
|
||||
|
||||
TEST_P(InputSuspensionTestParam, InputOutputLockStepBuffered) {
|
||||
TestConfig config = GetParam();
|
||||
if (config.jparams.add_marker) return;
|
||||
const DecompressParams& dparams = config.dparams;
|
||||
JXL_ASSIGN_OR_QUIT(std::vector<uint8_t> compressed, GetTestJpegData(config),
|
||||
"Failed to create test data.");
|
||||
bool is_partial = config.dparams.size_factor < 1.0f;
|
||||
if (is_partial) {
|
||||
compressed.resize(compressed.size() * config.dparams.size_factor);
|
||||
}
|
||||
SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size,
|
||||
is_partial);
|
||||
std::vector<TestImage> output_progression0;
|
||||
jpeg_decompress_struct cinfo;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_decompress(&cinfo);
|
||||
|
||||
cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
|
||||
|
||||
while (jpegli_read_header(&cinfo, TRUE) == JPEG_SUSPENDED) {
|
||||
JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk());
|
||||
}
|
||||
SetDecompressParams(dparams, &cinfo);
|
||||
jpegli_set_output_format(&cinfo, dparams.data_type, dparams.endianness);
|
||||
|
||||
cinfo.buffered_image = TRUE;
|
||||
cinfo.raw_data_out = TO_JXL_BOOL(dparams.output_mode == RAW_DATA);
|
||||
|
||||
EXPECT_TRUE(jpegli_start_decompress(&cinfo));
|
||||
EXPECT_FALSE(jpegli_input_complete(&cinfo));
|
||||
EXPECT_EQ(0, cinfo.output_scan_number);
|
||||
|
||||
int sos_marker_cnt = 1; // read_header reads the first SOS marker
|
||||
while (!jpegli_input_complete(&cinfo)) {
|
||||
EXPECT_EQ(cinfo.input_scan_number, sos_marker_cnt);
|
||||
EXPECT_TRUE(jpegli_start_output(&cinfo, cinfo.input_scan_number));
|
||||
// start output sets output_scan_number, but does not change
|
||||
// input_scan_number
|
||||
EXPECT_EQ(cinfo.output_scan_number, cinfo.input_scan_number);
|
||||
EXPECT_EQ(cinfo.input_scan_number, sos_marker_cnt);
|
||||
TestImage output;
|
||||
JPEGLI_TEST_ENSURE_TRUE(ReadOutputImage(dparams, &cinfo, &src, &output));
|
||||
output_progression0.emplace_back(std::move(output));
|
||||
// read scanlines/read raw data does not change input/output scan number
|
||||
EXPECT_EQ(cinfo.input_scan_number, sos_marker_cnt);
|
||||
EXPECT_EQ(cinfo.output_scan_number, cinfo.input_scan_number);
|
||||
while (!jpegli_finish_output(&cinfo)) {
|
||||
JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk());
|
||||
}
|
||||
++sos_marker_cnt; // finish output reads the next SOS marker or EOI
|
||||
if (dparams.output_mode == COEFFICIENTS) {
|
||||
jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(&cinfo);
|
||||
JPEGLI_TEST_ENSURE_TRUE(coef_arrays != nullptr);
|
||||
CopyCoefficients(&cinfo, coef_arrays, &output_progression0.back());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_TRUE(jpegli_finish_decompress(&cinfo));
|
||||
return true;
|
||||
};
|
||||
ASSERT_TRUE(try_catch_block());
|
||||
jpegli_destroy_decompress(&cinfo);
|
||||
|
||||
std::vector<TestImage> output_progression1;
|
||||
DecodeAllScansWithLibjpeg(config.jparams, dparams, compressed,
|
||||
&output_progression1);
|
||||
ASSERT_EQ(output_progression0.size(), output_progression1.size());
|
||||
for (size_t i = 0; i < output_progression0.size(); ++i) {
|
||||
const TestImage& output = output_progression0[i];
|
||||
const TestImage& expected = output_progression1[i];
|
||||
VerifyOutputImage(expected, output, config.max_rms_dist);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(InputSuspensionTestParam, PreConsumeInputBuffered) {
|
||||
TestConfig config = GetParam();
|
||||
if (config.jparams.add_marker) return;
|
||||
const DecompressParams& dparams = config.dparams;
|
||||
JXL_ASSIGN_OR_QUIT(std::vector<uint8_t> compressed, GetTestJpegData(config),
|
||||
"Failed to create test data.");
|
||||
bool is_partial = config.dparams.size_factor < 1.0f;
|
||||
if (is_partial) {
|
||||
compressed.resize(compressed.size() * config.dparams.size_factor);
|
||||
}
|
||||
std::vector<TestImage> output_progression1;
|
||||
DecodeAllScansWithLibjpeg(config.jparams, dparams, compressed,
|
||||
&output_progression1);
|
||||
SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size,
|
||||
is_partial);
|
||||
TestImage output0;
|
||||
jpeg_decompress_struct cinfo;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_decompress(&cinfo);
|
||||
cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
|
||||
|
||||
int status;
|
||||
while ((status = jpegli_consume_input(&cinfo)) != JPEG_REACHED_SOS) {
|
||||
if (status == JPEG_SUSPENDED) {
|
||||
JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk());
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(JPEG_REACHED_SOS, jpegli_consume_input(&cinfo));
|
||||
cinfo.buffered_image = TRUE;
|
||||
cinfo.raw_data_out = TO_JXL_BOOL(dparams.output_mode == RAW_DATA);
|
||||
cinfo.do_block_smoothing = TO_JXL_BOOL(dparams.do_block_smoothing);
|
||||
|
||||
EXPECT_TRUE(jpegli_start_decompress(&cinfo));
|
||||
EXPECT_FALSE(jpegli_input_complete(&cinfo));
|
||||
EXPECT_EQ(1, cinfo.input_scan_number);
|
||||
EXPECT_EQ(0, cinfo.output_scan_number);
|
||||
|
||||
while ((status = jpegli_consume_input(&cinfo)) != JPEG_REACHED_EOI) {
|
||||
if (status == JPEG_SUSPENDED) {
|
||||
JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk());
|
||||
}
|
||||
}
|
||||
|
||||
size_t input_scan_number = cinfo.input_scan_number;
|
||||
EXPECT_TRUE(jpegli_input_complete(&cinfo));
|
||||
EXPECT_EQ(output_progression1.size(), input_scan_number);
|
||||
EXPECT_EQ(0, cinfo.output_scan_number);
|
||||
|
||||
EXPECT_TRUE(jpegli_start_output(&cinfo, cinfo.input_scan_number));
|
||||
EXPECT_EQ(output_progression1.size(), input_scan_number);
|
||||
EXPECT_EQ(cinfo.output_scan_number, cinfo.input_scan_number);
|
||||
|
||||
JPEGLI_TEST_ENSURE_TRUE(
|
||||
ReadOutputImage(dparams, &cinfo, nullptr, &output0));
|
||||
EXPECT_EQ(output_progression1.size(), input_scan_number);
|
||||
EXPECT_EQ(cinfo.output_scan_number, cinfo.input_scan_number);
|
||||
|
||||
EXPECT_TRUE(jpegli_finish_output(&cinfo));
|
||||
if (dparams.output_mode == COEFFICIENTS) {
|
||||
jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(&cinfo);
|
||||
JPEGLI_TEST_ENSURE_TRUE(coef_arrays != nullptr);
|
||||
CopyCoefficients(&cinfo, coef_arrays, &output0);
|
||||
}
|
||||
EXPECT_TRUE(jpegli_finish_decompress(&cinfo));
|
||||
return true;
|
||||
};
|
||||
ASSERT_TRUE(try_catch_block());
|
||||
jpegli_destroy_decompress(&cinfo);
|
||||
|
||||
VerifyOutputImage(output_progression1.back(), output0, config.max_rms_dist);
|
||||
}
|
||||
|
||||
TEST_P(InputSuspensionTestParam, PreConsumeInputNonBuffered) {
|
||||
TestConfig config = GetParam();
|
||||
if (config.jparams.add_marker || IsSequential(config)) return;
|
||||
const DecompressParams& dparams = config.dparams;
|
||||
JXL_ASSIGN_OR_QUIT(std::vector<uint8_t> compressed, GetTestJpegData(config),
|
||||
"Failed to create test data.");
|
||||
bool is_partial = config.dparams.size_factor < 1.0f;
|
||||
if (is_partial) {
|
||||
compressed.resize(compressed.size() * config.dparams.size_factor);
|
||||
}
|
||||
SourceManager src(compressed.data(), compressed.size(), dparams.chunk_size,
|
||||
is_partial);
|
||||
TestImage output0;
|
||||
jpeg_decompress_struct cinfo;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_decompress(&cinfo);
|
||||
cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
|
||||
|
||||
int status;
|
||||
while ((status = jpegli_consume_input(&cinfo)) != JPEG_REACHED_SOS) {
|
||||
if (status == JPEG_SUSPENDED) {
|
||||
JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk());
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(JPEG_REACHED_SOS, jpegli_consume_input(&cinfo));
|
||||
cinfo.raw_data_out = TO_JXL_BOOL(dparams.output_mode == RAW_DATA);
|
||||
cinfo.do_block_smoothing = TO_JXL_BOOL(dparams.do_block_smoothing);
|
||||
|
||||
if (dparams.output_mode == COEFFICIENTS) {
|
||||
jpegli_read_coefficients(&cinfo);
|
||||
} else {
|
||||
while (!jpegli_start_decompress(&cinfo)) {
|
||||
JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk());
|
||||
}
|
||||
}
|
||||
|
||||
while ((status = jpegli_consume_input(&cinfo)) != JPEG_REACHED_EOI) {
|
||||
if (status == JPEG_SUSPENDED) {
|
||||
JPEGLI_TEST_ENSURE_TRUE(src.LoadNextChunk());
|
||||
}
|
||||
}
|
||||
|
||||
if (dparams.output_mode == COEFFICIENTS) {
|
||||
jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(&cinfo);
|
||||
JPEGLI_TEST_ENSURE_TRUE(coef_arrays != nullptr);
|
||||
CopyCoefficients(&cinfo, coef_arrays, &output0);
|
||||
} else {
|
||||
JPEGLI_TEST_ENSURE_TRUE(
|
||||
ReadOutputImage(dparams, &cinfo, nullptr, &output0));
|
||||
}
|
||||
|
||||
EXPECT_TRUE(jpegli_finish_decompress(&cinfo));
|
||||
return true;
|
||||
};
|
||||
ASSERT_TRUE(try_catch_block());
|
||||
jpegli_destroy_decompress(&cinfo);
|
||||
|
||||
TestImage output1;
|
||||
DecodeWithLibjpeg(config.jparams, dparams, compressed, &output1);
|
||||
VerifyOutputImage(output1, output0, config.max_rms_dist);
|
||||
}
|
||||
|
||||
std::vector<TestConfig> GenerateTests() {
|
||||
std::vector<TestConfig> all_tests;
|
||||
std::vector<std::pair<std::string, std::string>> testfiles({
|
||||
{"jxl/flower/flower.png.im_q85_444.jpg", "Q85YUV444"},
|
||||
{"jxl/flower/flower.png.im_q85_420_R13B.jpg", "Q85YUV420R13B"},
|
||||
{"jxl/flower/flower.png.im_q85_420_progr.jpg", "Q85YUV420PROGR"},
|
||||
});
|
||||
for (const auto& it : testfiles) {
|
||||
for (size_t chunk_size : {1, 64, 65536}) {
|
||||
for (size_t max_output_lines : {0, 1, 8, 16}) {
|
||||
TestConfig config;
|
||||
config.fn = it.first;
|
||||
config.fn_desc = it.second;
|
||||
config.dparams.chunk_size = chunk_size;
|
||||
config.dparams.max_output_lines = max_output_lines;
|
||||
all_tests.push_back(config);
|
||||
if (max_output_lines == 16) {
|
||||
config.dparams.output_mode = RAW_DATA;
|
||||
all_tests.push_back(config);
|
||||
config.dparams.output_mode = COEFFICIENTS;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (size_t r : {1, 17, 1024}) {
|
||||
for (size_t chunk_size : {1, 65536}) {
|
||||
TestConfig config;
|
||||
config.dparams.chunk_size = chunk_size;
|
||||
config.jparams.progressive_mode = 2;
|
||||
config.jparams.restart_interval = r;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
for (size_t chunk_size : {1, 4, 1024}) {
|
||||
TestConfig config;
|
||||
config.input.xsize = 256;
|
||||
config.input.ysize = 256;
|
||||
config.dparams.chunk_size = chunk_size;
|
||||
config.jparams.add_marker = true;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
// Tests for partial input.
|
||||
for (float size_factor : {0.1f, 0.33f, 0.5f, 0.75f}) {
|
||||
for (int progr : {0, 1, 3}) {
|
||||
for (int samp : {1, 2}) {
|
||||
for (JpegIOMode output_mode : {PIXELS, RAW_DATA}) {
|
||||
TestConfig config;
|
||||
config.input.xsize = 517;
|
||||
config.input.ysize = 523;
|
||||
config.jparams.h_sampling = {samp, 1, 1};
|
||||
config.jparams.v_sampling = {samp, 1, 1};
|
||||
config.jparams.progressive_mode = progr;
|
||||
config.dparams.size_factor = size_factor;
|
||||
config.dparams.output_mode = output_mode;
|
||||
// The last partially available block can behave differently.
|
||||
// TODO(szabadka) Figure out if we can make the behaviour more
|
||||
// similar.
|
||||
config.max_rms_dist = samp == 1 ? 1.75f : 3.0f;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Tests for block smoothing.
|
||||
for (float size_factor : {0.1f, 0.33f, 0.5f, 0.75f, 1.0f}) {
|
||||
for (int samp : {1, 2}) {
|
||||
TestConfig config;
|
||||
config.input.xsize = 517;
|
||||
config.input.ysize = 523;
|
||||
config.jparams.h_sampling = {samp, 1, 1};
|
||||
config.jparams.v_sampling = {samp, 1, 1};
|
||||
config.jparams.progressive_mode = 2;
|
||||
config.dparams.size_factor = size_factor;
|
||||
config.dparams.do_block_smoothing = true;
|
||||
// libjpeg does smoothing for incomplete scans differently at
|
||||
// the border between current and previous scans.
|
||||
config.max_rms_dist = 8.0f;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
return all_tests;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const TestConfig& c) {
|
||||
if (!c.fn.empty()) {
|
||||
os << c.fn_desc;
|
||||
} else {
|
||||
os << c.input;
|
||||
}
|
||||
os << c.jparams;
|
||||
if (c.dparams.chunk_size == 0) {
|
||||
os << "CompleteInput";
|
||||
} else {
|
||||
os << "InputChunks" << c.dparams.chunk_size;
|
||||
}
|
||||
if (c.dparams.size_factor < 1.0f) {
|
||||
os << "Partial" << static_cast<int>(c.dparams.size_factor * 100) << "p";
|
||||
}
|
||||
if (c.dparams.max_output_lines == 0) {
|
||||
os << "CompleteOutput";
|
||||
} else {
|
||||
os << "OutputLines" << c.dparams.max_output_lines;
|
||||
}
|
||||
if (c.dparams.output_mode == RAW_DATA) {
|
||||
os << "RawDataOut";
|
||||
} else if (c.dparams.output_mode == COEFFICIENTS) {
|
||||
os << "CoeffsOut";
|
||||
}
|
||||
if (c.dparams.do_block_smoothing) {
|
||||
os << "BlockSmoothing";
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string TestDescription(
|
||||
const testing::TestParamInfo<InputSuspensionTestParam::ParamType>& info) {
|
||||
std::stringstream name;
|
||||
name << info.param;
|
||||
return name.str();
|
||||
}
|
||||
|
||||
JPEGLI_INSTANTIATE_TEST_SUITE_P(InputSuspensionTest, InputSuspensionTestParam,
|
||||
testing::ValuesIn(GenerateTests()),
|
||||
TestDescription);
|
||||
|
||||
} // namespace
|
||||
} // namespace jpegli
|
||||
@@ -1,11 +0,0 @@
|
||||
LIBJPEG_6.2 {
|
||||
global:
|
||||
jpeg*;
|
||||
};
|
||||
|
||||
LIBJPEGTURBO_6.2 {
|
||||
global:
|
||||
jpeg_mem_src*;
|
||||
jpeg_mem_dest*;
|
||||
tj*;
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
LIBJPEG_8.0 {
|
||||
global:
|
||||
jpeg*;
|
||||
};
|
||||
|
||||
LIBJPEGTURBO_8.0 {
|
||||
global:
|
||||
tj*;
|
||||
};
|
||||
@@ -1,278 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/libjpeg_test_util.h"
|
||||
|
||||
#include <csetjmp>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/test_params.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/sanitizers.h"
|
||||
|
||||
#define J_TEST_UTILS jpeg_test_utils
|
||||
#include "lib/jpegli/test_utils-inl.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
namespace {
|
||||
|
||||
void Check(bool ok) {
|
||||
if (!ok) {
|
||||
JXL_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadOutputPass(j_decompress_ptr cinfo, const DecompressParams& dparams,
|
||||
TestImage* output) {
|
||||
JDIMENSION xoffset = 0;
|
||||
JDIMENSION yoffset = 0;
|
||||
JDIMENSION xsize_cropped = cinfo->output_width;
|
||||
JDIMENSION ysize_cropped = cinfo->output_height;
|
||||
if (dparams.crop_output) {
|
||||
xoffset = xsize_cropped = cinfo->output_width / 3;
|
||||
yoffset = ysize_cropped = cinfo->output_height / 3;
|
||||
jpeg_crop_scanline(cinfo, &xoffset, &xsize_cropped);
|
||||
Check(xsize_cropped == cinfo->output_width);
|
||||
}
|
||||
output->xsize = xsize_cropped;
|
||||
output->ysize = ysize_cropped;
|
||||
output->components = cinfo->out_color_components;
|
||||
if (cinfo->quantize_colors) {
|
||||
JSAMPLE** colormap = cinfo->colormap;
|
||||
jxl::msan::UnpoisonMemory(reinterpret_cast<void*>(colormap),
|
||||
cinfo->out_color_components * sizeof(JSAMPLE*));
|
||||
for (int c = 0; c < cinfo->out_color_components; ++c) {
|
||||
jxl::msan::UnpoisonMemory(
|
||||
reinterpret_cast<void*>(colormap[c]),
|
||||
cinfo->actual_number_of_colors * sizeof(JSAMPLE));
|
||||
}
|
||||
}
|
||||
if (!cinfo->raw_data_out) {
|
||||
size_t stride = output->xsize * output->components;
|
||||
output->pixels.resize(output->ysize * stride);
|
||||
output->color_space = cinfo->out_color_space;
|
||||
if (yoffset > 0) {
|
||||
jpeg_skip_scanlines(cinfo, yoffset);
|
||||
}
|
||||
for (size_t y = 0; y < output->ysize; ++y) {
|
||||
JSAMPROW rows[] = {
|
||||
reinterpret_cast<JSAMPLE*>(&output->pixels[y * stride])};
|
||||
Check(1 == jpeg_read_scanlines(cinfo, rows, 1));
|
||||
jxl::msan::UnpoisonMemory(
|
||||
rows[0], sizeof(JSAMPLE) * cinfo->output_components * output->xsize);
|
||||
if (cinfo->quantize_colors) {
|
||||
J_TEST_UTILS::UnmapColors(rows[0], cinfo->output_width,
|
||||
cinfo->out_color_components, cinfo->colormap,
|
||||
cinfo->actual_number_of_colors);
|
||||
}
|
||||
}
|
||||
if (cinfo->output_scanline < cinfo->output_height) {
|
||||
jpeg_skip_scanlines(cinfo, cinfo->output_height - cinfo->output_scanline);
|
||||
}
|
||||
} else {
|
||||
output->color_space = cinfo->jpeg_color_space;
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE;
|
||||
size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE;
|
||||
std::vector<uint8_t> plane(ysize * xsize);
|
||||
output->raw_data.emplace_back(std::move(plane));
|
||||
}
|
||||
while (cinfo->output_scanline < cinfo->output_height) {
|
||||
size_t iMCU_height = cinfo->max_v_samp_factor * DCTSIZE;
|
||||
Check(cinfo->output_scanline == cinfo->output_iMCU_row * iMCU_height);
|
||||
std::vector<std::vector<JSAMPROW>> rowdata(cinfo->num_components);
|
||||
std::vector<JSAMPARRAY> data(cinfo->num_components);
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
size_t xsize = cinfo->comp_info[c].width_in_blocks * DCTSIZE;
|
||||
size_t ysize = cinfo->comp_info[c].height_in_blocks * DCTSIZE;
|
||||
size_t num_lines = cinfo->comp_info[c].v_samp_factor * DCTSIZE;
|
||||
rowdata[c].resize(num_lines);
|
||||
size_t y0 = cinfo->output_iMCU_row * num_lines;
|
||||
for (size_t i = 0; i < num_lines; ++i) {
|
||||
rowdata[c][i] =
|
||||
y0 + i < ysize ? &output->raw_data[c][(y0 + i) * xsize] : nullptr;
|
||||
}
|
||||
data[c] = rowdata[c].data();
|
||||
}
|
||||
Check(iMCU_height == jpeg_read_raw_data(cinfo, data.data(), iMCU_height));
|
||||
}
|
||||
}
|
||||
Check(cinfo->total_iMCU_rows ==
|
||||
DivCeil(cinfo->image_height, cinfo->max_v_samp_factor * DCTSIZE));
|
||||
}
|
||||
|
||||
void DecodeWithLibjpeg(const CompressParams& jparams,
|
||||
const DecompressParams& dparams, j_decompress_ptr cinfo,
|
||||
TestImage* output) {
|
||||
if (jparams.add_marker) {
|
||||
jpeg_save_markers(cinfo, kSpecialMarker0, 0xffff);
|
||||
jpeg_save_markers(cinfo, kSpecialMarker1, 0xffff);
|
||||
}
|
||||
if (!jparams.icc.empty()) {
|
||||
jpeg_save_markers(cinfo, JPEG_APP0 + 2, 0xffff);
|
||||
}
|
||||
Check(JPEG_REACHED_SOS == jpeg_read_header(cinfo, /*require_image=*/TRUE));
|
||||
if (!jparams.icc.empty()) {
|
||||
uint8_t* icc_data = nullptr;
|
||||
unsigned int icc_len = 0; // "unpoison" via initialization
|
||||
Check(jpeg_read_icc_profile(cinfo, &icc_data, &icc_len));
|
||||
Check(icc_data);
|
||||
jxl::msan::UnpoisonMemory(icc_data, icc_len);
|
||||
Check(0 == memcmp(jparams.icc.data(), icc_data, icc_len));
|
||||
free(icc_data);
|
||||
}
|
||||
J_TEST_UTILS::SetDecompressParams(dparams, cinfo);
|
||||
J_TEST_UTILS::VerifyHeader(jparams, cinfo);
|
||||
if (dparams.output_mode == COEFFICIENTS) {
|
||||
jvirt_barray_ptr* coef_arrays = jpeg_read_coefficients(cinfo);
|
||||
Check(coef_arrays != nullptr);
|
||||
jxl::msan::UnpoisonMemory(coef_arrays,
|
||||
cinfo->num_components * sizeof(jvirt_barray_ptr));
|
||||
J_TEST_UTILS::CopyCoefficients(cinfo, coef_arrays, output);
|
||||
} else {
|
||||
Check(jpeg_start_decompress(cinfo));
|
||||
J_TEST_UTILS::VerifyScanHeader(jparams, cinfo);
|
||||
ReadOutputPass(cinfo, dparams, output);
|
||||
}
|
||||
Check(jpeg_finish_decompress(cinfo));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Verifies that an image encoded with libjpegli can be decoded with libjpeg,
|
||||
// and checks that the jpeg coding metadata matches jparams.
|
||||
void DecodeAllScansWithLibjpeg(const CompressParams& jparams,
|
||||
const DecompressParams& dparams,
|
||||
const std::vector<uint8_t>& compressed,
|
||||
std::vector<TestImage>* output_progression) {
|
||||
jpeg_decompress_struct cinfo = {};
|
||||
const auto try_catch_block = [&]() {
|
||||
jpeg_error_mgr jerr;
|
||||
jmp_buf env;
|
||||
cinfo.err = jpeg_std_error(&jerr);
|
||||
if (setjmp(env)) {
|
||||
return false;
|
||||
}
|
||||
cinfo.client_data = reinterpret_cast<void*>(&env);
|
||||
cinfo.err->error_exit = [](j_common_ptr cinfo) {
|
||||
(*cinfo->err->output_message)(cinfo);
|
||||
jmp_buf* env = reinterpret_cast<jmp_buf*>(cinfo->client_data);
|
||||
jpeg_destroy(cinfo);
|
||||
longjmp(*env, 1);
|
||||
};
|
||||
jpeg_create_decompress(&cinfo);
|
||||
jpeg_mem_src(&cinfo, compressed.data(), compressed.size());
|
||||
if (jparams.add_marker) {
|
||||
jpeg_save_markers(&cinfo, kSpecialMarker0, 0xffff);
|
||||
jpeg_save_markers(&cinfo, kSpecialMarker1, 0xffff);
|
||||
}
|
||||
Check(JPEG_REACHED_SOS == jpeg_read_header(&cinfo, /*require_image=*/TRUE));
|
||||
cinfo.buffered_image = TRUE;
|
||||
J_TEST_UTILS::SetDecompressParams(dparams, &cinfo);
|
||||
J_TEST_UTILS::VerifyHeader(jparams, &cinfo);
|
||||
Check(jpeg_start_decompress(&cinfo));
|
||||
// start decompress should not read the whole input in buffered image mode
|
||||
Check(!jpeg_input_complete(&cinfo));
|
||||
Check(cinfo.output_scan_number == 0);
|
||||
int sos_marker_cnt = 1; // read header reads the first SOS marker
|
||||
while (!jpeg_input_complete(&cinfo)) {
|
||||
Check(cinfo.input_scan_number == sos_marker_cnt);
|
||||
if (dparams.skip_scans && (cinfo.input_scan_number % 2) != 1) {
|
||||
int result = JPEG_SUSPENDED;
|
||||
while (result != JPEG_REACHED_SOS && result != JPEG_REACHED_EOI) {
|
||||
result = jpeg_consume_input(&cinfo);
|
||||
}
|
||||
if (result == JPEG_REACHED_SOS) ++sos_marker_cnt;
|
||||
continue;
|
||||
}
|
||||
J_TEST_UTILS::SetScanDecompressParams(dparams, &cinfo,
|
||||
cinfo.input_scan_number);
|
||||
Check(jpeg_start_output(&cinfo, cinfo.input_scan_number));
|
||||
// start output sets output_scan_number, but does not change
|
||||
// input_scan_number
|
||||
Check(cinfo.output_scan_number == cinfo.input_scan_number);
|
||||
Check(cinfo.input_scan_number == sos_marker_cnt);
|
||||
J_TEST_UTILS::VerifyScanHeader(jparams, &cinfo);
|
||||
TestImage output;
|
||||
ReadOutputPass(&cinfo, dparams, &output);
|
||||
output_progression->emplace_back(std::move(output));
|
||||
// read scanlines/read raw data does not change input/output scan number
|
||||
if (!cinfo.progressive_mode) {
|
||||
Check(cinfo.input_scan_number == sos_marker_cnt);
|
||||
Check(cinfo.output_scan_number == cinfo.input_scan_number);
|
||||
}
|
||||
Check(jpeg_finish_output(&cinfo));
|
||||
++sos_marker_cnt; // finish output reads the next SOS marker or EOI
|
||||
if (dparams.output_mode == COEFFICIENTS) {
|
||||
jvirt_barray_ptr* coef_arrays = jpeg_read_coefficients(&cinfo);
|
||||
Check(coef_arrays != nullptr);
|
||||
jxl::msan::UnpoisonMemory(
|
||||
coef_arrays, cinfo.num_components * sizeof(jvirt_barray_ptr));
|
||||
J_TEST_UTILS::CopyCoefficients(&cinfo, coef_arrays,
|
||||
&output_progression->back());
|
||||
}
|
||||
}
|
||||
Check(jpeg_finish_decompress(&cinfo));
|
||||
return true;
|
||||
};
|
||||
Check(try_catch_block());
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
}
|
||||
|
||||
// Returns the number of bytes read from compressed.
|
||||
size_t DecodeWithLibjpeg(const CompressParams& jparams,
|
||||
const DecompressParams& dparams,
|
||||
const uint8_t* table_stream, size_t table_stream_size,
|
||||
const uint8_t* compressed, size_t len,
|
||||
TestImage* output) {
|
||||
jpeg_decompress_struct cinfo = {};
|
||||
size_t bytes_read;
|
||||
const auto try_catch_block = [&]() {
|
||||
jpeg_error_mgr jerr;
|
||||
jmp_buf env;
|
||||
cinfo.err = jpeg_std_error(&jerr);
|
||||
if (setjmp(env)) {
|
||||
return false;
|
||||
}
|
||||
cinfo.client_data = reinterpret_cast<void*>(&env);
|
||||
cinfo.err->error_exit = [](j_common_ptr cinfo) {
|
||||
(*cinfo->err->output_message)(cinfo);
|
||||
jmp_buf* env = reinterpret_cast<jmp_buf*>(cinfo->client_data);
|
||||
jpeg_destroy(cinfo);
|
||||
longjmp(*env, 1);
|
||||
};
|
||||
jpeg_create_decompress(&cinfo);
|
||||
if (table_stream != nullptr) {
|
||||
jpeg_mem_src(&cinfo, table_stream, table_stream_size);
|
||||
jpeg_read_header(&cinfo, FALSE);
|
||||
}
|
||||
jpeg_mem_src(&cinfo, compressed, len);
|
||||
DecodeWithLibjpeg(jparams, dparams, &cinfo, output);
|
||||
jxl::msan::UnpoisonMemory(cinfo.src, sizeof(jpeg_source_mgr));
|
||||
bytes_read = len - cinfo.src->bytes_in_buffer;
|
||||
return true;
|
||||
};
|
||||
Check(try_catch_block());
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
void DecodeWithLibjpeg(const CompressParams& jparams,
|
||||
const DecompressParams& dparams,
|
||||
const std::vector<uint8_t>& compressed,
|
||||
TestImage* output) {
|
||||
DecodeWithLibjpeg(jparams, dparams, nullptr, 0, compressed.data(),
|
||||
compressed.size(), output);
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#undef J_TEST_UTILS
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_LIBJPEG_TEST_UTIL_H_
|
||||
#define LIB_JPEGLI_LIBJPEG_TEST_UTIL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/test_params.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
// Verifies that an image encoded with libjpegli can be decoded with libjpeg,
|
||||
// and checks that the jpeg coding metadata matches jparams.
|
||||
void DecodeAllScansWithLibjpeg(const CompressParams& jparams,
|
||||
const DecompressParams& dparams,
|
||||
const std::vector<uint8_t>& compressed,
|
||||
std::vector<TestImage>* output_progression);
|
||||
// Returns the number of bytes read from compressed.
|
||||
size_t DecodeWithLibjpeg(const CompressParams& jparams,
|
||||
const DecompressParams& dparams,
|
||||
const uint8_t* table_stream, size_t table_stream_size,
|
||||
const uint8_t* compressed, size_t len,
|
||||
TestImage* output);
|
||||
void DecodeWithLibjpeg(const CompressParams& jparams,
|
||||
const DecompressParams& dparams,
|
||||
const std::vector<uint8_t>& compressed,
|
||||
TestImage* output);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_LIBJPEG_TEST_UTIL_H_
|
||||
@@ -1,257 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//
|
||||
// This file contains wrapper-functions that are used to build the libjpeg.so
|
||||
// shared library that is API- and ABI-compatible with libjpeg-turbo's version
|
||||
// of libjpeg.so.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/decode.h"
|
||||
#include "lib/jpegli/encode.h"
|
||||
|
||||
struct jpeg_error_mgr *jpeg_std_error(struct jpeg_error_mgr *err) {
|
||||
return jpegli_std_error(err);
|
||||
}
|
||||
|
||||
void jpeg_abort(j_common_ptr cinfo) { jpegli_abort(cinfo); }
|
||||
|
||||
void jpeg_destroy(j_common_ptr cinfo) { jpegli_destroy(cinfo); }
|
||||
|
||||
JQUANT_TBL *jpeg_alloc_quant_table(j_common_ptr cinfo) {
|
||||
return jpegli_alloc_quant_table(cinfo);
|
||||
}
|
||||
|
||||
JHUFF_TBL *jpeg_alloc_huff_table(j_common_ptr cinfo) {
|
||||
return jpegli_alloc_huff_table(cinfo);
|
||||
}
|
||||
|
||||
void jpeg_CreateDecompress(j_decompress_ptr cinfo, int version,
|
||||
size_t structsize) {
|
||||
jpegli_CreateDecompress(cinfo, version, structsize);
|
||||
}
|
||||
|
||||
void jpeg_stdio_src(j_decompress_ptr cinfo, FILE *infile) {
|
||||
jpegli_stdio_src(cinfo, infile);
|
||||
}
|
||||
|
||||
void jpeg_mem_src(j_decompress_ptr cinfo, const unsigned char *inbuffer,
|
||||
unsigned long insize /* NOLINT */) {
|
||||
jpegli_mem_src(cinfo, inbuffer, insize);
|
||||
}
|
||||
|
||||
int jpeg_read_header(j_decompress_ptr cinfo, boolean require_image) {
|
||||
return jpegli_read_header(cinfo, require_image);
|
||||
}
|
||||
|
||||
boolean jpeg_start_decompress(j_decompress_ptr cinfo) {
|
||||
return jpegli_start_decompress(cinfo);
|
||||
}
|
||||
|
||||
JDIMENSION jpeg_read_scanlines(j_decompress_ptr cinfo, JSAMPARRAY scanlines,
|
||||
JDIMENSION max_lines) {
|
||||
return jpegli_read_scanlines(cinfo, scanlines, max_lines);
|
||||
}
|
||||
|
||||
JDIMENSION jpeg_skip_scanlines(j_decompress_ptr cinfo, JDIMENSION num_lines) {
|
||||
return jpegli_skip_scanlines(cinfo, num_lines);
|
||||
}
|
||||
|
||||
void jpeg_crop_scanline(j_decompress_ptr cinfo, JDIMENSION *xoffset,
|
||||
JDIMENSION *width) {
|
||||
jpegli_crop_scanline(cinfo, xoffset, width);
|
||||
}
|
||||
|
||||
boolean jpeg_finish_decompress(j_decompress_ptr cinfo) {
|
||||
return jpegli_finish_decompress(cinfo);
|
||||
}
|
||||
|
||||
JDIMENSION jpeg_read_raw_data(j_decompress_ptr cinfo, JSAMPIMAGE data,
|
||||
JDIMENSION max_lines) {
|
||||
return jpegli_read_raw_data(cinfo, data, max_lines);
|
||||
}
|
||||
|
||||
jvirt_barray_ptr *jpeg_read_coefficients(j_decompress_ptr cinfo) {
|
||||
return jpegli_read_coefficients(cinfo);
|
||||
}
|
||||
|
||||
boolean jpeg_has_multiple_scans(j_decompress_ptr cinfo) {
|
||||
return jpegli_has_multiple_scans(cinfo);
|
||||
}
|
||||
|
||||
boolean jpeg_start_output(j_decompress_ptr cinfo, int scan_number) {
|
||||
return jpegli_start_output(cinfo, scan_number);
|
||||
}
|
||||
|
||||
boolean jpeg_finish_output(j_decompress_ptr cinfo) {
|
||||
return jpegli_finish_output(cinfo);
|
||||
}
|
||||
|
||||
boolean jpeg_input_complete(j_decompress_ptr cinfo) {
|
||||
return jpegli_input_complete(cinfo);
|
||||
}
|
||||
|
||||
int jpeg_consume_input(j_decompress_ptr cinfo) {
|
||||
return jpegli_consume_input(cinfo);
|
||||
}
|
||||
|
||||
#if JPEG_LIB_VERSION >= 80
|
||||
void jpeg_core_output_dimensions(j_decompress_ptr cinfo) {
|
||||
jpegli_core_output_dimensions(cinfo);
|
||||
}
|
||||
#endif
|
||||
void jpeg_calc_output_dimensions(j_decompress_ptr cinfo) {
|
||||
jpegli_calc_output_dimensions(cinfo);
|
||||
}
|
||||
|
||||
void jpeg_save_markers(j_decompress_ptr cinfo, int marker_code,
|
||||
unsigned int length_limit) {
|
||||
jpegli_save_markers(cinfo, marker_code, length_limit);
|
||||
}
|
||||
|
||||
void jpeg_set_marker_processor(j_decompress_ptr cinfo, int marker_code,
|
||||
jpeg_marker_parser_method routine) {
|
||||
jpegli_set_marker_processor(cinfo, marker_code, routine);
|
||||
}
|
||||
|
||||
boolean jpeg_read_icc_profile(j_decompress_ptr cinfo, JOCTET **icc_data_ptr,
|
||||
unsigned int *icc_data_len) {
|
||||
return jpegli_read_icc_profile(cinfo, icc_data_ptr, icc_data_len);
|
||||
}
|
||||
|
||||
void jpeg_abort_decompress(j_decompress_ptr cinfo) {
|
||||
jpegli_abort_decompress(cinfo);
|
||||
}
|
||||
|
||||
void jpeg_destroy_decompress(j_decompress_ptr cinfo) {
|
||||
jpegli_destroy_decompress(cinfo);
|
||||
}
|
||||
|
||||
void jpeg_CreateCompress(j_compress_ptr cinfo, int version, size_t structsize) {
|
||||
jpegli_CreateCompress(cinfo, version, structsize);
|
||||
}
|
||||
|
||||
void jpeg_stdio_dest(j_compress_ptr cinfo, FILE *outfile) {
|
||||
jpegli_stdio_dest(cinfo, outfile);
|
||||
}
|
||||
|
||||
void jpeg_mem_dest(j_compress_ptr cinfo, unsigned char **outbuffer,
|
||||
unsigned long *outsize /* NOLINT */) {
|
||||
jpegli_mem_dest(cinfo, outbuffer, outsize);
|
||||
}
|
||||
|
||||
void jpeg_set_defaults(j_compress_ptr cinfo) { jpegli_set_defaults(cinfo); }
|
||||
|
||||
void jpeg_default_colorspace(j_compress_ptr cinfo) {
|
||||
jpegli_default_colorspace(cinfo);
|
||||
}
|
||||
|
||||
void jpeg_set_colorspace(j_compress_ptr cinfo, J_COLOR_SPACE colorspace) {
|
||||
jpegli_set_colorspace(cinfo, colorspace);
|
||||
}
|
||||
|
||||
void jpeg_set_quality(j_compress_ptr cinfo, int quality,
|
||||
boolean force_baseline) {
|
||||
jpegli_set_quality(cinfo, quality, force_baseline);
|
||||
}
|
||||
|
||||
void jpeg_set_linear_quality(j_compress_ptr cinfo, int scale_factor,
|
||||
boolean force_baseline) {
|
||||
jpegli_set_linear_quality(cinfo, scale_factor, force_baseline);
|
||||
}
|
||||
|
||||
#if JPEG_LIB_VERSION >= 70
|
||||
void jpeg_default_qtables(j_compress_ptr cinfo, boolean force_baseline) {
|
||||
jpegli_default_qtables(cinfo, force_baseline);
|
||||
}
|
||||
#endif
|
||||
|
||||
int jpeg_quality_scaling(int quality) {
|
||||
return jpegli_quality_scaling(quality);
|
||||
}
|
||||
|
||||
void jpeg_add_quant_table(j_compress_ptr cinfo, int which_tbl,
|
||||
const unsigned int *basic_table, int scale_factor,
|
||||
boolean force_baseline) {
|
||||
jpegli_add_quant_table(cinfo, which_tbl, basic_table, scale_factor,
|
||||
force_baseline);
|
||||
}
|
||||
|
||||
void jpeg_simple_progression(j_compress_ptr cinfo) {
|
||||
jpegli_simple_progression(cinfo);
|
||||
}
|
||||
|
||||
void jpeg_suppress_tables(j_compress_ptr cinfo, boolean suppress) {
|
||||
jpegli_suppress_tables(cinfo, suppress);
|
||||
}
|
||||
|
||||
#if JPEG_LIB_VERSION >= 70
|
||||
void jpeg_calc_jpeg_dimensions(j_compress_ptr cinfo) {
|
||||
jpegli_calc_jpeg_dimensions(cinfo);
|
||||
}
|
||||
#endif
|
||||
|
||||
void jpeg_copy_critical_parameters(j_decompress_ptr srcinfo,
|
||||
j_compress_ptr dstinfo) {
|
||||
jpegli_copy_critical_parameters(srcinfo, dstinfo);
|
||||
}
|
||||
|
||||
void jpeg_write_m_header(j_compress_ptr cinfo, int marker,
|
||||
unsigned int datalen) {
|
||||
jpegli_write_m_header(cinfo, marker, datalen);
|
||||
}
|
||||
|
||||
void jpeg_write_m_byte(j_compress_ptr cinfo, int val) {
|
||||
jpegli_write_m_byte(cinfo, val);
|
||||
}
|
||||
|
||||
void jpeg_write_marker(j_compress_ptr cinfo, int marker, const JOCTET *dataptr,
|
||||
unsigned int datalen) {
|
||||
jpegli_write_marker(cinfo, marker, dataptr, datalen);
|
||||
}
|
||||
|
||||
void jpeg_write_icc_profile(j_compress_ptr cinfo, const JOCTET *icc_data_ptr,
|
||||
unsigned int icc_data_len) {
|
||||
jpegli_write_icc_profile(cinfo, icc_data_ptr, icc_data_len);
|
||||
}
|
||||
|
||||
void jpeg_start_compress(j_compress_ptr cinfo, boolean write_all_tables) {
|
||||
jpegli_start_compress(cinfo, write_all_tables);
|
||||
}
|
||||
|
||||
void jpeg_write_tables(j_compress_ptr cinfo) { jpegli_write_tables(cinfo); }
|
||||
|
||||
JDIMENSION jpeg_write_scanlines(j_compress_ptr cinfo, JSAMPARRAY scanlines,
|
||||
JDIMENSION num_lines) {
|
||||
return jpegli_write_scanlines(cinfo, scanlines, num_lines);
|
||||
}
|
||||
|
||||
JDIMENSION jpeg_write_raw_data(j_compress_ptr cinfo, JSAMPIMAGE data,
|
||||
JDIMENSION num_lines) {
|
||||
return jpegli_write_raw_data(cinfo, data, num_lines);
|
||||
}
|
||||
|
||||
void jpeg_write_coefficients(j_compress_ptr cinfo,
|
||||
jvirt_barray_ptr *coef_arrays) {
|
||||
jpegli_write_coefficients(cinfo, coef_arrays);
|
||||
}
|
||||
|
||||
void jpeg_finish_compress(j_compress_ptr cinfo) {
|
||||
jpegli_finish_compress(cinfo);
|
||||
}
|
||||
|
||||
void jpeg_abort_compress(j_compress_ptr cinfo) { jpegli_abort_compress(cinfo); }
|
||||
|
||||
void jpeg_destroy_compress(j_compress_ptr cinfo) {
|
||||
jpegli_destroy_compress(cinfo);
|
||||
}
|
||||
|
||||
boolean jpeg_resync_to_restart(j_decompress_ptr cinfo, int desired) {
|
||||
return jpegli_resync_to_restart(cinfo, desired);
|
||||
}
|
||||
|
||||
void jpeg_new_colormap(j_decompress_ptr cinfo) { jpegli_new_colormap(cinfo); }
|
||||
@@ -1,189 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/memory_manager.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <hwy/aligned_allocator.h>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
|
||||
struct jvirt_sarray_control {
|
||||
JSAMPARRAY full_buffer;
|
||||
size_t numrows;
|
||||
JDIMENSION maxaccess;
|
||||
};
|
||||
|
||||
struct jvirt_barray_control {
|
||||
JBLOCKARRAY full_buffer;
|
||||
size_t numrows;
|
||||
JDIMENSION maxaccess;
|
||||
};
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
namespace {
|
||||
|
||||
struct MemoryManager {
|
||||
struct jpeg_memory_mgr pub;
|
||||
std::vector<void*> owned_ptrs[2 * JPOOL_NUMPOOLS];
|
||||
uint64_t pool_memory_usage[2 * JPOOL_NUMPOOLS];
|
||||
uint64_t total_memory_usage;
|
||||
uint64_t peak_memory_usage;
|
||||
};
|
||||
|
||||
void* Alloc(j_common_ptr cinfo, int pool_id, size_t sizeofobject) {
|
||||
MemoryManager* mem = reinterpret_cast<MemoryManager*>(cinfo->mem);
|
||||
if (pool_id < 0 || pool_id >= 2 * JPOOL_NUMPOOLS) {
|
||||
JPEGLI_ERROR("Invalid pool id %d", pool_id);
|
||||
}
|
||||
if (mem->pub.max_memory_to_use > 0 &&
|
||||
mem->total_memory_usage + static_cast<uint64_t>(sizeofobject) >
|
||||
static_cast<uint64_t>(mem->pub.max_memory_to_use)) {
|
||||
JPEGLI_ERROR("Total memory usage exceeding %ld",
|
||||
mem->pub.max_memory_to_use);
|
||||
}
|
||||
void* p;
|
||||
if (pool_id < JPOOL_NUMPOOLS) {
|
||||
p = malloc(sizeofobject);
|
||||
} else {
|
||||
p = hwy::AllocateAlignedBytes(sizeofobject, nullptr, nullptr);
|
||||
}
|
||||
if (p == nullptr) {
|
||||
JPEGLI_ERROR("Out of memory");
|
||||
}
|
||||
mem->owned_ptrs[pool_id].push_back(p);
|
||||
mem->pool_memory_usage[pool_id] += sizeofobject;
|
||||
mem->total_memory_usage += sizeofobject;
|
||||
mem->peak_memory_usage =
|
||||
std::max(mem->peak_memory_usage, mem->total_memory_usage);
|
||||
return p;
|
||||
}
|
||||
|
||||
constexpr size_t gcd(size_t a, size_t b) { return b == 0 ? a : gcd(b, a % b); }
|
||||
constexpr size_t lcm(size_t a, size_t b) { return (a * b) / gcd(a, b); }
|
||||
|
||||
template <typename T>
|
||||
T** Alloc2dArray(j_common_ptr cinfo, int pool_id, JDIMENSION samplesperrow,
|
||||
JDIMENSION numrows) {
|
||||
T** array = Allocate<T*>(cinfo, numrows, pool_id);
|
||||
// Always use aligned allocator for large 2d arrays.
|
||||
if (pool_id < JPOOL_NUMPOOLS) {
|
||||
pool_id += JPOOL_NUMPOOLS;
|
||||
}
|
||||
size_t alignment = lcm(sizeof(T), HWY_ALIGNMENT);
|
||||
size_t memstride = RoundUpTo(samplesperrow * sizeof(T), alignment);
|
||||
size_t stride = memstride / sizeof(T);
|
||||
T* buffer = Allocate<T>(cinfo, numrows * stride, pool_id);
|
||||
for (size_t i = 0; i < numrows; ++i) {
|
||||
array[i] = &buffer[i * stride];
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
template <typename Control, typename T>
|
||||
Control* RequestVirtualArray(j_common_ptr cinfo, int pool_id, boolean pre_zero,
|
||||
JDIMENSION samplesperrow, JDIMENSION numrows,
|
||||
JDIMENSION maxaccess) {
|
||||
if (pool_id != JPOOL_IMAGE) {
|
||||
JPEGLI_ERROR("Only image lifetime virtual arrays are supported.");
|
||||
}
|
||||
Control* p = Allocate<Control>(cinfo, 1, pool_id);
|
||||
p->full_buffer = Alloc2dArray<T>(cinfo, pool_id, samplesperrow, numrows);
|
||||
p->numrows = numrows;
|
||||
p->maxaccess = maxaccess;
|
||||
if (pre_zero) {
|
||||
for (size_t i = 0; i < numrows; ++i) {
|
||||
memset(p->full_buffer[i], 0, samplesperrow * sizeof(T));
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void RealizeVirtualArrays(j_common_ptr cinfo) {
|
||||
// Nothing to do, the full arrays were realized at request time already.
|
||||
}
|
||||
|
||||
template <typename Control, typename T>
|
||||
T** AccessVirtualArray(j_common_ptr cinfo, Control* ptr, JDIMENSION start_row,
|
||||
JDIMENSION num_rows, boolean writable) {
|
||||
if (num_rows > ptr->maxaccess) {
|
||||
JPEGLI_ERROR("Invalid virtual array access, num rows %u vs max rows %u",
|
||||
num_rows, ptr->maxaccess);
|
||||
}
|
||||
if (start_row + num_rows > ptr->numrows) {
|
||||
JPEGLI_ERROR("Invalid virtual array access, %u vs %u total rows",
|
||||
start_row + num_rows, ptr->numrows);
|
||||
}
|
||||
if (ptr->full_buffer == nullptr) {
|
||||
JPEGLI_ERROR("Invalid virtual array access, array not realized.");
|
||||
}
|
||||
return ptr->full_buffer + start_row;
|
||||
}
|
||||
|
||||
void ClearPool(j_common_ptr cinfo, int pool_id) {
|
||||
MemoryManager* mem = reinterpret_cast<MemoryManager*>(cinfo->mem);
|
||||
mem->owned_ptrs[pool_id].clear();
|
||||
mem->total_memory_usage -= mem->pool_memory_usage[pool_id];
|
||||
mem->pool_memory_usage[pool_id] = 0;
|
||||
}
|
||||
|
||||
void FreePool(j_common_ptr cinfo, int pool_id) {
|
||||
MemoryManager* mem = reinterpret_cast<MemoryManager*>(cinfo->mem);
|
||||
if (pool_id < 0 || pool_id >= JPOOL_NUMPOOLS) {
|
||||
JPEGLI_ERROR("Invalid pool id %d", pool_id);
|
||||
}
|
||||
for (void* ptr : mem->owned_ptrs[pool_id]) {
|
||||
free(ptr);
|
||||
}
|
||||
ClearPool(cinfo, pool_id);
|
||||
for (void* ptr : mem->owned_ptrs[JPOOL_NUMPOOLS + pool_id]) {
|
||||
hwy::FreeAlignedBytes(ptr, nullptr, nullptr);
|
||||
}
|
||||
ClearPool(cinfo, JPOOL_NUMPOOLS + pool_id);
|
||||
}
|
||||
|
||||
void SelfDestruct(j_common_ptr cinfo) {
|
||||
MemoryManager* mem = reinterpret_cast<MemoryManager*>(cinfo->mem);
|
||||
for (int pool_id = 0; pool_id < JPOOL_NUMPOOLS; ++pool_id) {
|
||||
FreePool(cinfo, pool_id);
|
||||
}
|
||||
delete mem;
|
||||
cinfo->mem = nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitMemoryManager(j_common_ptr cinfo) {
|
||||
MemoryManager* mem = new MemoryManager;
|
||||
mem->pub.alloc_small = jpegli::Alloc;
|
||||
mem->pub.alloc_large = jpegli::Alloc;
|
||||
mem->pub.alloc_sarray = jpegli::Alloc2dArray<JSAMPLE>;
|
||||
mem->pub.alloc_barray = jpegli::Alloc2dArray<JBLOCK>;
|
||||
mem->pub.request_virt_sarray =
|
||||
jpegli::RequestVirtualArray<jvirt_sarray_control, JSAMPLE>;
|
||||
mem->pub.request_virt_barray =
|
||||
jpegli::RequestVirtualArray<jvirt_barray_control, JBLOCK>;
|
||||
mem->pub.realize_virt_arrays = jpegli::RealizeVirtualArrays;
|
||||
mem->pub.access_virt_sarray =
|
||||
jpegli::AccessVirtualArray<jvirt_sarray_control, JSAMPLE>;
|
||||
mem->pub.access_virt_barray =
|
||||
jpegli::AccessVirtualArray<jvirt_barray_control, JBLOCK>;
|
||||
mem->pub.free_pool = jpegli::FreePool;
|
||||
mem->pub.self_destruct = jpegli::SelfDestruct;
|
||||
mem->pub.max_memory_to_use = 0;
|
||||
mem->total_memory_usage = 0;
|
||||
mem->peak_memory_usage = 0;
|
||||
memset(mem->pool_memory_usage, 0, sizeof(mem->pool_memory_usage));
|
||||
cinfo->mem = reinterpret_cast<struct jpeg_memory_mgr*>(mem);
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
@@ -1,46 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_MEMORY_MANAGER_H_
|
||||
#define LIB_JPEGLI_MEMORY_MANAGER_H_
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
#define JPOOL_PERMANENT_ALIGNED (JPOOL_NUMPOOLS + JPOOL_PERMANENT)
|
||||
#define JPOOL_IMAGE_ALIGNED (JPOOL_NUMPOOLS + JPOOL_IMAGE)
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void InitMemoryManager(j_common_ptr cinfo);
|
||||
|
||||
template <typename T>
|
||||
T* Allocate(j_common_ptr cinfo, size_t len, int pool_id = JPOOL_PERMANENT) {
|
||||
const size_t size = len * sizeof(T); // NOLINT
|
||||
void* p = (*cinfo->mem->alloc_small)(cinfo, pool_id, size);
|
||||
return reinterpret_cast<T*>(p);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* Allocate(j_decompress_ptr cinfo, size_t len, int pool_id = JPOOL_PERMANENT) {
|
||||
return Allocate<T>(reinterpret_cast<j_common_ptr>(cinfo), len, pool_id);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* Allocate(j_compress_ptr cinfo, size_t len, int pool_id = JPOOL_PERMANENT) {
|
||||
return Allocate<T>(reinterpret_cast<j_common_ptr>(cinfo), len, pool_id);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
JBLOCKARRAY GetBlockRow(T cinfo, int c, JDIMENSION by) {
|
||||
return (*cinfo->mem->access_virt_barray)(
|
||||
reinterpret_cast<j_common_ptr>(cinfo), cinfo->master->coeff_buffers[c],
|
||||
by, 1, true);
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_MEMORY_MANAGER_H_
|
||||
@@ -1,232 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/encode.h"
|
||||
#include "lib/jpegli/libjpeg_test_util.h"
|
||||
#include "lib/jpegli/test_params.h"
|
||||
#include "lib/jpegli/test_utils.h"
|
||||
#include "lib/jpegli/testing.h"
|
||||
|
||||
namespace jpegli {
|
||||
namespace {
|
||||
|
||||
constexpr size_t kInitialBufferSize = 1024;
|
||||
constexpr size_t kFinalBufferSize = 18;
|
||||
|
||||
struct DestinationManager {
|
||||
jpeg_destination_mgr pub;
|
||||
std::vector<uint8_t> buffer;
|
||||
|
||||
DestinationManager() {
|
||||
pub.init_destination = init_destination;
|
||||
pub.empty_output_buffer = empty_output_buffer;
|
||||
pub.term_destination = term_destination;
|
||||
}
|
||||
|
||||
void Rewind() {
|
||||
pub.next_output_byte = buffer.data();
|
||||
pub.free_in_buffer = buffer.size();
|
||||
}
|
||||
|
||||
void EmptyTo(std::vector<uint8_t>* output, size_t new_size = 0) {
|
||||
output->insert(output->end(), buffer.data(), pub.next_output_byte);
|
||||
if (new_size > 0) {
|
||||
buffer.resize(new_size);
|
||||
}
|
||||
Rewind();
|
||||
}
|
||||
|
||||
static void init_destination(j_compress_ptr cinfo) {
|
||||
auto* us = reinterpret_cast<DestinationManager*>(cinfo->dest);
|
||||
us->buffer.resize(kInitialBufferSize);
|
||||
us->Rewind();
|
||||
}
|
||||
|
||||
static boolean empty_output_buffer(j_compress_ptr cinfo) { return FALSE; }
|
||||
|
||||
static void term_destination(j_compress_ptr cinfo) {}
|
||||
};
|
||||
|
||||
struct TestConfig {
|
||||
TestImage input;
|
||||
CompressParams jparams;
|
||||
size_t buffer_size;
|
||||
size_t lines_batch_size;
|
||||
};
|
||||
|
||||
class OutputSuspensionTestParam : public ::testing::TestWithParam<TestConfig> {
|
||||
};
|
||||
|
||||
TEST_P(OutputSuspensionTestParam, PixelData) {
|
||||
jpeg_compress_struct cinfo = {};
|
||||
TestConfig config = GetParam();
|
||||
TestImage& input = config.input;
|
||||
GeneratePixels(&input);
|
||||
DestinationManager dest;
|
||||
std::vector<uint8_t> compressed;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_compress(&cinfo);
|
||||
cinfo.dest = reinterpret_cast<jpeg_destination_mgr*>(&dest);
|
||||
|
||||
cinfo.image_width = input.xsize;
|
||||
cinfo.image_height = input.ysize;
|
||||
cinfo.input_components = input.components;
|
||||
cinfo.in_color_space = JCS_RGB;
|
||||
jpegli_set_defaults(&cinfo);
|
||||
cinfo.comp_info[0].v_samp_factor = config.jparams.v_sampling[0];
|
||||
jpegli_set_progressive_level(&cinfo, 0);
|
||||
cinfo.optimize_coding = FALSE;
|
||||
jpegli_start_compress(&cinfo, TRUE);
|
||||
|
||||
size_t stride = cinfo.image_width * cinfo.input_components;
|
||||
std::vector<uint8_t> row_bytes(config.lines_batch_size * stride);
|
||||
while (cinfo.next_scanline < cinfo.image_height) {
|
||||
size_t lines_left = cinfo.image_height - cinfo.next_scanline;
|
||||
size_t num_lines = std::min(config.lines_batch_size, lines_left);
|
||||
memcpy(row_bytes.data(), &input.pixels[cinfo.next_scanline * stride],
|
||||
num_lines * stride);
|
||||
std::vector<JSAMPROW> rows(num_lines);
|
||||
for (size_t i = 0; i < num_lines; ++i) {
|
||||
rows[i] = &row_bytes[i * stride];
|
||||
}
|
||||
size_t lines_done = 0;
|
||||
while (lines_done < num_lines) {
|
||||
lines_done += jpegli_write_scanlines(&cinfo, &rows[lines_done],
|
||||
num_lines - lines_done);
|
||||
if (lines_done < num_lines) {
|
||||
dest.EmptyTo(&compressed, config.buffer_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
dest.EmptyTo(&compressed, kFinalBufferSize);
|
||||
jpegli_finish_compress(&cinfo);
|
||||
dest.EmptyTo(&compressed);
|
||||
return true;
|
||||
};
|
||||
ASSERT_TRUE(try_catch_block());
|
||||
jpegli_destroy_compress(&cinfo);
|
||||
TestImage output;
|
||||
DecodeWithLibjpeg(CompressParams(), DecompressParams(), compressed, &output);
|
||||
VerifyOutputImage(input, output, 2.5);
|
||||
}
|
||||
|
||||
TEST_P(OutputSuspensionTestParam, RawData) {
|
||||
jpeg_compress_struct cinfo = {};
|
||||
TestConfig config = GetParam();
|
||||
if (config.lines_batch_size != 1) return;
|
||||
TestImage& input = config.input;
|
||||
input.color_space = JCS_YCbCr;
|
||||
GeneratePixels(&input);
|
||||
GenerateRawData(config.jparams, &input);
|
||||
DestinationManager dest;
|
||||
std::vector<uint8_t> compressed;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_compress(&cinfo);
|
||||
cinfo.dest = reinterpret_cast<jpeg_destination_mgr*>(&dest);
|
||||
cinfo.image_width = input.xsize;
|
||||
cinfo.image_height = input.ysize;
|
||||
cinfo.input_components = input.components;
|
||||
cinfo.in_color_space = JCS_YCbCr;
|
||||
jpegli_set_defaults(&cinfo);
|
||||
cinfo.comp_info[0].h_samp_factor = config.jparams.h_sampling[0];
|
||||
cinfo.comp_info[0].v_samp_factor = config.jparams.v_sampling[0];
|
||||
jpegli_set_progressive_level(&cinfo, 0);
|
||||
cinfo.optimize_coding = FALSE;
|
||||
cinfo.raw_data_in = TRUE;
|
||||
jpegli_start_compress(&cinfo, TRUE);
|
||||
|
||||
std::vector<std::vector<uint8_t>> raw_data = input.raw_data;
|
||||
size_t max_lines = config.jparams.max_v_sample() * DCTSIZE;
|
||||
std::vector<std::vector<JSAMPROW>> rowdata(cinfo.num_components);
|
||||
std::vector<JSAMPARRAY> data(cinfo.num_components);
|
||||
for (int c = 0; c < cinfo.num_components; ++c) {
|
||||
rowdata[c].resize(config.jparams.v_samp(c) * DCTSIZE);
|
||||
data[c] = rowdata[c].data();
|
||||
}
|
||||
while (cinfo.next_scanline < cinfo.image_height) {
|
||||
for (int c = 0; c < cinfo.num_components; ++c) {
|
||||
size_t cwidth = cinfo.comp_info[c].width_in_blocks * DCTSIZE;
|
||||
size_t cheight = cinfo.comp_info[c].height_in_blocks * DCTSIZE;
|
||||
size_t num_lines = config.jparams.v_samp(c) * DCTSIZE;
|
||||
size_t y0 = (cinfo.next_scanline / max_lines) * num_lines;
|
||||
for (size_t i = 0; i < num_lines; ++i) {
|
||||
rowdata[c][i] =
|
||||
(y0 + i < cheight ? &raw_data[c][(y0 + i) * cwidth] : nullptr);
|
||||
}
|
||||
}
|
||||
while (jpegli_write_raw_data(&cinfo, data.data(), max_lines) == 0) {
|
||||
dest.EmptyTo(&compressed, config.buffer_size);
|
||||
}
|
||||
}
|
||||
dest.EmptyTo(&compressed, kFinalBufferSize);
|
||||
jpegli_finish_compress(&cinfo);
|
||||
dest.EmptyTo(&compressed);
|
||||
return true;
|
||||
};
|
||||
try_catch_block();
|
||||
jpegli_destroy_compress(&cinfo);
|
||||
DecompressParams dparams;
|
||||
dparams.output_mode = RAW_DATA;
|
||||
TestImage output;
|
||||
DecodeWithLibjpeg(CompressParams(), dparams, compressed, &output);
|
||||
VerifyOutputImage(input, output, 3.5);
|
||||
}
|
||||
|
||||
std::vector<TestConfig> GenerateTests() {
|
||||
std::vector<TestConfig> all_tests;
|
||||
const size_t xsize0 = 1920;
|
||||
const size_t ysize0 = 1080;
|
||||
for (int dysize : {0, 1, 8, 9}) {
|
||||
for (int v_sampling : {1, 2}) {
|
||||
for (int nlines : {1, 8, 117}) {
|
||||
for (int bufsize : {1, 16, 16 << 10}) {
|
||||
TestConfig config;
|
||||
config.lines_batch_size = nlines;
|
||||
config.buffer_size = bufsize;
|
||||
config.input.xsize = xsize0;
|
||||
config.input.ysize = ysize0 + dysize;
|
||||
config.jparams.h_sampling = {1, 1, 1};
|
||||
config.jparams.v_sampling = {v_sampling, 1, 1};
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return all_tests;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const TestConfig& c) {
|
||||
os << c.input;
|
||||
os << c.jparams;
|
||||
os << "Lines" << c.lines_batch_size;
|
||||
os << "BufSize" << c.buffer_size;
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string TestDescription(
|
||||
const testing::TestParamInfo<OutputSuspensionTestParam::ParamType>& info) {
|
||||
std::stringstream name;
|
||||
name << info.param;
|
||||
return name.str();
|
||||
}
|
||||
|
||||
JPEGLI_INSTANTIATE_TEST_SUITE_P(OutputSuspensionTest, OutputSuspensionTestParam,
|
||||
testing::ValuesIn(GenerateTests()),
|
||||
TestDescription);
|
||||
|
||||
} // namespace
|
||||
} // namespace jpegli
|
||||
@@ -1,766 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/quant.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/encode_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
namespace {
|
||||
|
||||
// Global scale is chosen in a way that butteraugli 3-norm matches libjpeg
|
||||
// with the same quality setting. Fitted for quality 90 on jyrki31 corpus.
|
||||
constexpr float kGlobalScaleXYB = 1.43951668f;
|
||||
constexpr float kGlobalScaleYCbCr = 1.73966010f;
|
||||
|
||||
constexpr float kBaseQuantMatrixXYB[] = {
|
||||
// c = 0
|
||||
7.5629935265f,
|
||||
19.8247814178f,
|
||||
22.5724945068f,
|
||||
20.6706695557f,
|
||||
22.6864585876f,
|
||||
23.5696277618f,
|
||||
25.8129081726f,
|
||||
36.3307571411f,
|
||||
19.8247814178f,
|
||||
21.5503177643f,
|
||||
19.9372234344f,
|
||||
20.5424213409f,
|
||||
21.8645496368f,
|
||||
23.9041385651f,
|
||||
28.2844066620f,
|
||||
32.6609764099f,
|
||||
22.5724945068f,
|
||||
19.9372234344f,
|
||||
21.9017257690f,
|
||||
19.1223449707f,
|
||||
21.7515811920f,
|
||||
24.6724700928f,
|
||||
25.4249649048f,
|
||||
32.6653823853f,
|
||||
20.6706695557f,
|
||||
20.5424213409f,
|
||||
19.1223449707f,
|
||||
20.1610221863f,
|
||||
25.3719692230f,
|
||||
25.9668903351f,
|
||||
30.9804954529f,
|
||||
31.3406009674f,
|
||||
22.6864585876f,
|
||||
21.8645496368f,
|
||||
21.7515811920f,
|
||||
25.3719692230f,
|
||||
26.2431850433f,
|
||||
40.5992202759f,
|
||||
43.2624626160f,
|
||||
63.3010940552f,
|
||||
23.5696277618f,
|
||||
23.9041385651f,
|
||||
24.6724700928f,
|
||||
25.9668903351f,
|
||||
40.5992202759f,
|
||||
48.3026771545f,
|
||||
34.0964355469f,
|
||||
61.9852142334f,
|
||||
25.8129081726f,
|
||||
28.2844066620f,
|
||||
25.4249649048f,
|
||||
30.9804954529f,
|
||||
43.2624626160f,
|
||||
34.0964355469f,
|
||||
34.4937438965f,
|
||||
66.9702758789f,
|
||||
36.3307571411f,
|
||||
32.6609764099f,
|
||||
32.6653823853f,
|
||||
31.3406009674f,
|
||||
63.3010940552f,
|
||||
61.9852142334f,
|
||||
66.9702758789f,
|
||||
39.9652709961f,
|
||||
// c = 1
|
||||
1.6262000799f,
|
||||
3.2199242115f,
|
||||
3.4903779030f,
|
||||
3.9148359299f,
|
||||
4.8337211609f,
|
||||
4.9108843803f,
|
||||
5.3137121201f,
|
||||
6.1676793098f,
|
||||
3.2199242115f,
|
||||
3.4547898769f,
|
||||
3.6036829948f,
|
||||
4.2652835846f,
|
||||
4.8368387222f,
|
||||
4.8226222992f,
|
||||
5.6120514870f,
|
||||
6.3431472778f,
|
||||
3.4903779030f,
|
||||
3.6036829948f,
|
||||
3.9044559002f,
|
||||
4.3374395370f,
|
||||
4.8435096741f,
|
||||
5.4057979584f,
|
||||
5.6066360474f,
|
||||
6.1075134277f,
|
||||
3.9148359299f,
|
||||
4.2652835846f,
|
||||
4.3374395370f,
|
||||
4.6064834595f,
|
||||
5.1751475334f,
|
||||
5.4013924599f,
|
||||
6.0399808884f,
|
||||
6.7825231552f,
|
||||
4.8337211609f,
|
||||
4.8368387222f,
|
||||
4.8435096741f,
|
||||
5.1751475334f,
|
||||
5.3748049736f,
|
||||
6.1410837173f,
|
||||
7.6529307365f,
|
||||
7.5235214233f,
|
||||
4.9108843803f,
|
||||
4.8226222992f,
|
||||
5.4057979584f,
|
||||
5.4013924599f,
|
||||
6.1410837173f,
|
||||
6.3431472778f,
|
||||
7.1083049774f,
|
||||
7.6008300781f,
|
||||
5.3137121201f,
|
||||
5.6120514870f,
|
||||
5.6066360474f,
|
||||
6.0399808884f,
|
||||
7.6529307365f,
|
||||
7.1083049774f,
|
||||
7.0943155289f,
|
||||
7.0478363037f,
|
||||
6.1676793098f,
|
||||
6.3431472778f,
|
||||
6.1075134277f,
|
||||
6.7825231552f,
|
||||
7.5235214233f,
|
||||
7.6008300781f,
|
||||
7.0478363037f,
|
||||
6.9186143875f,
|
||||
// c = 2
|
||||
3.3038473129f,
|
||||
10.0689258575f,
|
||||
12.2785224915f,
|
||||
14.6041173935f,
|
||||
16.2107315063f,
|
||||
19.2314529419f,
|
||||
28.0129547119f,
|
||||
55.6682891846f,
|
||||
10.0689258575f,
|
||||
11.4085016251f,
|
||||
11.3871345520f,
|
||||
15.4934167862f,
|
||||
16.5364933014f,
|
||||
14.9153423309f,
|
||||
26.3748722076f,
|
||||
40.8614425659f,
|
||||
12.2785224915f,
|
||||
11.3871345520f,
|
||||
17.0886878967f,
|
||||
13.9500350952f,
|
||||
16.0003223419f,
|
||||
28.5660629272f,
|
||||
26.2124195099f,
|
||||
30.1260128021f,
|
||||
14.6041173935f,
|
||||
15.4934167862f,
|
||||
13.9500350952f,
|
||||
21.1235027313f,
|
||||
26.1579780579f,
|
||||
25.5579223633f,
|
||||
40.6859359741f,
|
||||
33.8056335449f,
|
||||
16.2107315063f,
|
||||
16.5364933014f,
|
||||
16.0003223419f,
|
||||
26.1579780579f,
|
||||
26.8042831421f,
|
||||
26.1587715149f,
|
||||
35.7343978882f,
|
||||
43.6857032776f,
|
||||
19.2314529419f,
|
||||
14.9153423309f,
|
||||
28.5660629272f,
|
||||
25.5579223633f,
|
||||
26.1587715149f,
|
||||
34.5418128967f,
|
||||
41.3197937012f,
|
||||
48.7867660522f,
|
||||
28.0129547119f,
|
||||
26.3748722076f,
|
||||
26.2124195099f,
|
||||
40.6859359741f,
|
||||
35.7343978882f,
|
||||
41.3197937012f,
|
||||
47.6329460144f,
|
||||
55.3498458862f,
|
||||
55.6682891846f,
|
||||
40.8614425659f,
|
||||
30.1260128021f,
|
||||
33.8056335449f,
|
||||
43.6857032776f,
|
||||
48.7867660522f,
|
||||
55.3498458862f,
|
||||
63.6065597534f,
|
||||
};
|
||||
|
||||
const float kBaseQuantMatrixYCbCr[] = {
|
||||
// c = 0
|
||||
1.2397409345866273f, //
|
||||
1.7227115097630963f, //
|
||||
2.9212167156636855f, //
|
||||
2.812737435286529f, //
|
||||
3.339819711906184f, //
|
||||
3.463603762596166f, //
|
||||
3.840915217993518f, //
|
||||
3.86956f, //
|
||||
1.7227115097630963f, //
|
||||
2.0928894413636874f, //
|
||||
2.8456760904429297f, //
|
||||
2.704506820909662f, //
|
||||
3.4407673520905337f, //
|
||||
3.166232352090534f, //
|
||||
4.025208741558432f, //
|
||||
4.035324490952577f, //
|
||||
2.9212167156636855f, //
|
||||
2.8456760904429297f, //
|
||||
2.9587403520905338f, //
|
||||
3.3862948970669273f, //
|
||||
3.619523781336757f, //
|
||||
3.9046279999999998f, //
|
||||
3.757835838431854f, //
|
||||
4.237447515714274f, //
|
||||
2.812737435286529f, //
|
||||
2.704506820909662f, //
|
||||
3.3862948970669273f, //
|
||||
3.380058821812233f, //
|
||||
4.1679867415584315f, //
|
||||
4.805510627261856f, //
|
||||
4.784259f, //
|
||||
4.605934f, //
|
||||
3.339819711906184f, //
|
||||
3.4407673520905337f, //
|
||||
3.619523781336757f, //
|
||||
4.1679867415584315f, //
|
||||
4.579851258441568f, //
|
||||
4.923237f, //
|
||||
5.574107f, //
|
||||
5.48533336146308f, //
|
||||
3.463603762596166f, //
|
||||
3.166232352090534f, //
|
||||
3.9046279999999998f, //
|
||||
4.805510627261856f, //
|
||||
4.923237f, //
|
||||
5.43936f, //
|
||||
5.093895741558431f, //
|
||||
6.0872254423617225f, //
|
||||
3.840915217993518f, //
|
||||
4.025208741558432f, //
|
||||
3.757835838431854f, //
|
||||
4.784259f, //
|
||||
5.574107f, //
|
||||
5.093895741558431f, //
|
||||
5.438461f, //
|
||||
5.4037359493250845f, //
|
||||
3.86956f, //
|
||||
4.035324490952577f, //
|
||||
4.237447515714274f, //
|
||||
4.605934f, //
|
||||
5.48533336146308f, //
|
||||
6.0872254423617225f, //
|
||||
5.4037359493250845f, //
|
||||
4.37787101190424f,
|
||||
// c = 1
|
||||
2.8236197786377537f, //
|
||||
6.495639358561486f, //
|
||||
9.310489207538302f, //
|
||||
10.64747864717083f, //
|
||||
11.07419143098738f, //
|
||||
17.146390223910462f, //
|
||||
18.463982229408998f, //
|
||||
29.087001644203088f, //
|
||||
6.495639358561486f, //
|
||||
8.890103846667353f, //
|
||||
8.976895794294748f, //
|
||||
13.666270550318826f, //
|
||||
16.547071905624193f, //
|
||||
16.63871382827686f, //
|
||||
26.778396930893695f, //
|
||||
21.33034294694781f, //
|
||||
9.310489207538302f, //
|
||||
8.976895794294748f, //
|
||||
11.08737706005991f, //
|
||||
18.20548239870446f, //
|
||||
19.752481654011646f, //
|
||||
23.985660533114896f, //
|
||||
102.6457378402362f, //
|
||||
24.450989f, //
|
||||
10.64747864717083f, //
|
||||
13.666270550318826f, //
|
||||
18.20548239870446f, //
|
||||
18.628012327860365f, //
|
||||
16.042509519487183f, //
|
||||
25.04918273242625f, //
|
||||
25.017140189353015f, //
|
||||
35.79788782635831f, //
|
||||
11.07419143098738f, //
|
||||
16.547071905624193f, //
|
||||
19.752481654011646f, //
|
||||
16.042509519487183f, //
|
||||
19.373482748612577f, //
|
||||
14.677529999999999f, //
|
||||
19.94695960400931f, //
|
||||
51.094112f, //
|
||||
17.146390223910462f, //
|
||||
16.63871382827686f, //
|
||||
23.985660533114896f, //
|
||||
25.04918273242625f, //
|
||||
14.677529999999999f, //
|
||||
31.320412426835304f, //
|
||||
46.357234000000005f, //
|
||||
67.48111451705412f, //
|
||||
18.463982229408998f, //
|
||||
26.778396930893695f, //
|
||||
102.6457378402362f, //
|
||||
25.017140189353015f, //
|
||||
19.94695960400931f, //
|
||||
46.357234000000005f, //
|
||||
61.315764694388044f, //
|
||||
88.34665293823721f, //
|
||||
29.087001644203088f, //
|
||||
21.33034294694781f, //
|
||||
24.450989f, //
|
||||
35.79788782635831f, //
|
||||
51.094112f, //
|
||||
67.48111451705412f, //
|
||||
88.34665293823721f, //
|
||||
112.16099098350989f,
|
||||
// c = 2
|
||||
2.9217254961255255f, //
|
||||
4.497681013199305f, //
|
||||
7.356344520940414f, //
|
||||
6.583891506504051f, //
|
||||
8.535608740100237f, //
|
||||
8.799434353234647f, //
|
||||
9.188341534163023f, //
|
||||
9.482700481227672f, //
|
||||
4.497681013199305f, //
|
||||
6.309548851989123f, //
|
||||
7.024608962670982f, //
|
||||
7.156445324163424f, //
|
||||
8.049059218663244f, //
|
||||
7.0124290657218555f, //
|
||||
6.711923184393611f, //
|
||||
8.380307846134853f, //
|
||||
7.356344520940414f, //
|
||||
7.024608962670982f, //
|
||||
6.892101177327445f, //
|
||||
6.882819916277163f, //
|
||||
8.782226090078568f, //
|
||||
6.8774750000000004f, //
|
||||
7.8858175969577955f, //
|
||||
8.67909f, //
|
||||
6.583891506504051f, //
|
||||
7.156445324163424f, //
|
||||
6.882819916277163f, //
|
||||
7.003072944847055f, //
|
||||
7.7223464701024875f, //
|
||||
7.955425720217421f, //
|
||||
7.4734110000000005f, //
|
||||
8.362933242943903f, //
|
||||
8.535608740100237f, //
|
||||
8.049059218663244f, //
|
||||
8.782226090078568f, //
|
||||
7.7223464701024875f, //
|
||||
6.778005927001542f, //
|
||||
9.484922741558432f, //
|
||||
9.043702663686046f, //
|
||||
8.053178199770173f, //
|
||||
8.799434353234647f, //
|
||||
7.0124290657218555f, //
|
||||
6.8774750000000004f, //
|
||||
7.955425720217421f, //
|
||||
9.484922741558432f, //
|
||||
8.607606527385098f, //
|
||||
9.922697394370815f, //
|
||||
64.25135180237939f, //
|
||||
9.188341534163023f, //
|
||||
6.711923184393611f, //
|
||||
7.8858175969577955f, //
|
||||
7.4734110000000005f, //
|
||||
9.043702663686046f, //
|
||||
9.922697394370815f, //
|
||||
63.184936549738225f, //
|
||||
83.35294340273799f, //
|
||||
9.482700481227672f, //
|
||||
8.380307846134853f, //
|
||||
8.67909f, //
|
||||
8.362933242943903f, //
|
||||
8.053178199770173f, //
|
||||
64.25135180237939f, //
|
||||
83.35294340273799f, //
|
||||
114.89202448569779f, //
|
||||
};
|
||||
|
||||
const float k420GlobalScale = 1.22;
|
||||
const float k420Rescale[64] = {
|
||||
0.4093, 0.3209, 0.3477, 0.3333, 0.3144, 0.2823, 0.3214, 0.3354, //
|
||||
0.3209, 0.3111, 0.3489, 0.2801, 0.3059, 0.3119, 0.4135, 0.3445, //
|
||||
0.3477, 0.3489, 0.3586, 0.3257, 0.2727, 0.3754, 0.3369, 0.3484, //
|
||||
0.3333, 0.2801, 0.3257, 0.3020, 0.3515, 0.3410, 0.3971, 0.3839, //
|
||||
0.3144, 0.3059, 0.2727, 0.3515, 0.3105, 0.3397, 0.2716, 0.3836, //
|
||||
0.2823, 0.3119, 0.3754, 0.3410, 0.3397, 0.3212, 0.3203, 0.0726, //
|
||||
0.3214, 0.4135, 0.3369, 0.3971, 0.2716, 0.3203, 0.0798, 0.0553, //
|
||||
0.3354, 0.3445, 0.3484, 0.3839, 0.3836, 0.0726, 0.0553, 0.3368, //
|
||||
};
|
||||
|
||||
const float kBaseQuantMatrixStd[] = {
|
||||
// c = 0
|
||||
16.0f, 11.0f, 10.0f, 16.0f, 24.0f, 40.0f, 51.0f, 61.0f, //
|
||||
12.0f, 12.0f, 14.0f, 19.0f, 26.0f, 58.0f, 60.0f, 55.0f, //
|
||||
14.0f, 13.0f, 16.0f, 24.0f, 40.0f, 57.0f, 69.0f, 56.0f, //
|
||||
14.0f, 17.0f, 22.0f, 29.0f, 51.0f, 87.0f, 80.0f, 62.0f, //
|
||||
18.0f, 22.0f, 37.0f, 56.0f, 68.0f, 109.0f, 103.0f, 77.0f, //
|
||||
24.0f, 35.0f, 55.0f, 64.0f, 81.0f, 104.0f, 113.0f, 92.0f, //
|
||||
49.0f, 64.0f, 78.0f, 87.0f, 103.0f, 121.0f, 120.0f, 101.0f, //
|
||||
72.0f, 92.0f, 95.0f, 98.0f, 112.0f, 100.0f, 103.0f, 99.0f, //
|
||||
// c = 1
|
||||
17.0f, 18.0f, 24.0f, 47.0f, 99.0f, 99.0f, 99.0f, 99.0f, //
|
||||
18.0f, 21.0f, 26.0f, 66.0f, 99.0f, 99.0f, 99.0f, 99.0f, //
|
||||
24.0f, 26.0f, 56.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, //
|
||||
47.0f, 66.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, //
|
||||
99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, //
|
||||
99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, //
|
||||
99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, //
|
||||
99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, 99.0f, //
|
||||
};
|
||||
|
||||
const float kZeroBiasMulYCbCrLQ[] = {
|
||||
// c = 0
|
||||
0.0000f, 0.0568f, 0.3880f, 0.6190f, 0.6190f, 0.4490f, 0.4490f, 0.6187f, //
|
||||
0.0568f, 0.5829f, 0.6189f, 0.6190f, 0.6190f, 0.7190f, 0.6190f, 0.6189f, //
|
||||
0.3880f, 0.6189f, 0.6190f, 0.6190f, 0.6190f, 0.6190f, 0.6187f, 0.6100f, //
|
||||
0.6190f, 0.6190f, 0.6190f, 0.6190f, 0.5890f, 0.3839f, 0.7160f, 0.6190f, //
|
||||
0.6190f, 0.6190f, 0.6190f, 0.5890f, 0.6190f, 0.3880f, 0.5860f, 0.4790f, //
|
||||
0.4490f, 0.7190f, 0.6190f, 0.3839f, 0.3880f, 0.6190f, 0.6190f, 0.6190f, //
|
||||
0.4490f, 0.6190f, 0.6187f, 0.7160f, 0.5860f, 0.6190f, 0.6204f, 0.6190f, //
|
||||
0.6187f, 0.6189f, 0.6100f, 0.6190f, 0.4790f, 0.6190f, 0.6190f, 0.3480f, //
|
||||
// c = 1
|
||||
0.0000f, 1.1640f, 0.9373f, 1.1319f, 0.8016f, 0.9136f, 1.1530f, 0.9430f, //
|
||||
1.1640f, 0.9188f, 0.9160f, 1.1980f, 1.1830f, 0.9758f, 0.9430f, 0.9430f, //
|
||||
0.9373f, 0.9160f, 0.8430f, 1.1720f, 0.7083f, 0.9430f, 0.9430f, 0.9430f, //
|
||||
1.1319f, 1.1980f, 1.1720f, 1.1490f, 0.8547f, 0.9430f, 0.9430f, 0.9430f, //
|
||||
0.8016f, 1.1830f, 0.7083f, 0.8547f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, //
|
||||
0.9136f, 0.9758f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, //
|
||||
1.1530f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9480f, //
|
||||
0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9430f, 0.9480f, 0.9430f, //
|
||||
// c = 2
|
||||
0.0000f, 1.3190f, 0.4308f, 0.4460f, 0.0661f, 0.0660f, 0.2660f, 0.2960f, //
|
||||
1.3190f, 0.3280f, 0.3093f, 0.0750f, 0.0505f, 0.1594f, 0.3060f, 0.2113f, //
|
||||
0.4308f, 0.3093f, 0.3060f, 0.1182f, 0.0500f, 0.3060f, 0.3915f, 0.2426f, //
|
||||
0.4460f, 0.0750f, 0.1182f, 0.0512f, 0.0500f, 0.2130f, 0.3930f, 0.1590f, //
|
||||
0.0661f, 0.0505f, 0.0500f, 0.0500f, 0.3055f, 0.3360f, 0.5148f, 0.5403f, //
|
||||
0.0660f, 0.1594f, 0.3060f, 0.2130f, 0.3360f, 0.5060f, 0.5874f, 0.3060f, //
|
||||
0.2660f, 0.3060f, 0.3915f, 0.3930f, 0.5148f, 0.5874f, 0.3060f, 0.3060f, //
|
||||
0.2960f, 0.2113f, 0.2426f, 0.1590f, 0.5403f, 0.3060f, 0.3060f, 0.3060f, //
|
||||
};
|
||||
|
||||
const float kZeroBiasMulYCbCrHQ[] = {
|
||||
// c = 0
|
||||
0.0000f, 0.0044f, 0.2521f, 0.6547f, 0.8161f, 0.6130f, 0.8841f, 0.8155f, //
|
||||
0.0044f, 0.6831f, 0.6553f, 0.6295f, 0.7848f, 0.7843f, 0.8474f, 0.7836f, //
|
||||
0.2521f, 0.6553f, 0.7834f, 0.7829f, 0.8161f, 0.8072f, 0.7743f, 0.9242f, //
|
||||
0.6547f, 0.6295f, 0.7829f, 0.8654f, 0.7829f, 0.6986f, 0.7818f, 0.7726f, //
|
||||
0.8161f, 0.7848f, 0.8161f, 0.7829f, 0.7471f, 0.7827f, 0.7843f, 0.7653f, //
|
||||
0.6130f, 0.7843f, 0.8072f, 0.6986f, 0.7827f, 0.7848f, 0.9508f, 0.7653f, //
|
||||
0.8841f, 0.8474f, 0.7743f, 0.7818f, 0.7843f, 0.9508f, 0.7839f, 0.8437f, //
|
||||
0.8155f, 0.7836f, 0.9242f, 0.7726f, 0.7653f, 0.7653f, 0.8437f, 0.7819f, //
|
||||
// c = 1
|
||||
0.0000f, 1.0816f, 1.0556f, 1.2876f, 1.1554f, 1.1567f, 1.8851f, 0.5488f, //
|
||||
1.0816f, 1.1537f, 1.1850f, 1.0712f, 1.1671f, 2.0719f, 1.0544f, 1.4764f, //
|
||||
1.0556f, 1.1850f, 1.2870f, 1.1981f, 1.8181f, 1.2618f, 1.0564f, 1.1191f, //
|
||||
1.2876f, 1.0712f, 1.1981f, 1.4753f, 2.0609f, 1.0564f, 1.2645f, 1.0564f, //
|
||||
1.1554f, 1.1671f, 1.8181f, 2.0609f, 0.7324f, 1.1163f, 0.8464f, 1.0564f, //
|
||||
1.1567f, 2.0719f, 1.2618f, 1.0564f, 1.1163f, 1.0040f, 1.0564f, 1.0564f, //
|
||||
1.8851f, 1.0544f, 1.0564f, 1.2645f, 0.8464f, 1.0564f, 1.0564f, 1.0564f, //
|
||||
0.5488f, 1.4764f, 1.1191f, 1.0564f, 1.0564f, 1.0564f, 1.0564f, 1.0564f, //
|
||||
// c = 2
|
||||
0.0000f, 0.5392f, 0.6659f, 0.8968f, 0.6829f, 0.6328f, 0.5802f, 0.4836f, //
|
||||
0.5392f, 0.6746f, 0.6760f, 0.6102f, 0.6015f, 0.6958f, 0.7327f, 0.4897f, //
|
||||
0.6659f, 0.6760f, 0.6957f, 0.6543f, 0.4396f, 0.6330f, 0.7081f, 0.2583f, //
|
||||
0.8968f, 0.6102f, 0.6543f, 0.5913f, 0.6457f, 0.5828f, 0.5139f, 0.3565f, //
|
||||
0.6829f, 0.6015f, 0.4396f, 0.6457f, 0.5633f, 0.4263f, 0.6371f, 0.5949f, //
|
||||
0.6328f, 0.6958f, 0.6330f, 0.5828f, 0.4263f, 0.2847f, 0.2909f, 0.6629f, //
|
||||
0.5802f, 0.7327f, 0.7081f, 0.5139f, 0.6371f, 0.2909f, 0.6644f, 0.6644f, //
|
||||
0.4836f, 0.4897f, 0.2583f, 0.3565f, 0.5949f, 0.6629f, 0.6644f, 0.6644f, //
|
||||
};
|
||||
|
||||
const float kZeroBiasOffsetYCbCrDC[] = {0.0f, 0.0f, 0.0f};
|
||||
|
||||
const float kZeroBiasOffsetYCbCrAC[] = {
|
||||
0.59082f,
|
||||
0.58146f,
|
||||
0.57988f,
|
||||
};
|
||||
|
||||
constexpr uint8_t kTransferFunctionPQ = 16;
|
||||
constexpr uint8_t kTransferFunctionHLG = 18;
|
||||
|
||||
float DistanceToLinearQuality(float distance) {
|
||||
if (distance <= 0.1f) {
|
||||
return 1.0f;
|
||||
} else if (distance <= 4.6f) {
|
||||
return (200.0f / 9.0f) * (distance - 0.1f);
|
||||
} else if (distance <= 6.4f) {
|
||||
return 5000.0f / (100.0f - (distance - 0.1f) / 0.09f);
|
||||
} else if (distance < 25.0f) {
|
||||
return 530000.0f /
|
||||
(3450.0f -
|
||||
300.0f * std::sqrt((848.0f * distance - 5330.0f) / 120.0f));
|
||||
} else {
|
||||
return 5000.0f;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr float kExponent[DCTSIZE2] = {
|
||||
1.00f, 0.51f, 0.67f, 0.74f, 1.00f, 1.00f, 1.00f, 1.00f, //
|
||||
0.51f, 0.66f, 0.69f, 0.87f, 1.00f, 1.00f, 1.00f, 1.00f, //
|
||||
0.67f, 0.69f, 0.84f, 0.83f, 0.96f, 1.00f, 1.00f, 1.00f, //
|
||||
0.74f, 0.87f, 0.83f, 1.00f, 1.00f, 0.91f, 0.91f, 1.00f, //
|
||||
1.00f, 1.00f, 0.96f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, //
|
||||
1.00f, 1.00f, 1.00f, 0.91f, 1.00f, 1.00f, 1.00f, 1.00f, //
|
||||
1.00f, 1.00f, 1.00f, 0.91f, 1.00f, 1.00f, 1.00f, 1.00f, //
|
||||
1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, //
|
||||
};
|
||||
constexpr float kDist0 = 1.5f; // distance where non-linearity kicks in.
|
||||
|
||||
float DistanceToScale(float distance, int k) {
|
||||
if (distance < kDist0) {
|
||||
return distance;
|
||||
}
|
||||
const float exp = kExponent[k];
|
||||
const float mul = std::pow(kDist0, 1.0 - exp);
|
||||
return std::max<float>(0.5f * distance, mul * std::pow(distance, exp));
|
||||
}
|
||||
|
||||
float ScaleToDistance(float scale, int k) {
|
||||
if (scale < kDist0) {
|
||||
return scale;
|
||||
}
|
||||
const float exp = 1.0 / kExponent[k];
|
||||
const float mul = std::pow(kDist0, 1.0 - exp);
|
||||
return std::min<float>(2.0f * scale, mul * std::pow(scale, exp));
|
||||
}
|
||||
|
||||
float QuantValsToDistance(j_compress_ptr cinfo) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
float global_scale = kGlobalScaleYCbCr;
|
||||
if (m->cicp_transfer_function == kTransferFunctionPQ) {
|
||||
global_scale *= .4f;
|
||||
} else if (m->cicp_transfer_function == kTransferFunctionHLG) {
|
||||
global_scale *= .5f;
|
||||
}
|
||||
int quant_max = m->force_baseline ? 255 : 32767U;
|
||||
static const float kDistMax = 10000.0f;
|
||||
float dist_min = 0.0f;
|
||||
float dist_max = kDistMax;
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
int quant_idx = cinfo->comp_info[c].quant_tbl_no;
|
||||
uint16_t* quantval = cinfo->quant_tbl_ptrs[quant_idx]->quantval;
|
||||
const float* base_qm = &kBaseQuantMatrixYCbCr[quant_idx * DCTSIZE2];
|
||||
for (int k = 0; k < DCTSIZE2; ++k) {
|
||||
float dmin = 0.0;
|
||||
float dmax = kDistMax;
|
||||
float invq = 1.0f / base_qm[k] / global_scale;
|
||||
int qval = quantval[k];
|
||||
if (qval > 1) {
|
||||
float scale_min = (qval - 0.5f) * invq;
|
||||
dmin = ScaleToDistance(scale_min, k);
|
||||
}
|
||||
if (qval < quant_max) {
|
||||
float scale_max = (qval + 0.5f) * invq;
|
||||
dmax = ScaleToDistance(scale_max, k);
|
||||
}
|
||||
if (dmin <= dist_max) {
|
||||
dist_min = std::max(dmin, dist_min);
|
||||
}
|
||||
if (dmax >= dist_min) {
|
||||
dist_max = std::min(dist_max, dmax);
|
||||
}
|
||||
}
|
||||
}
|
||||
float distance;
|
||||
if (dist_min == 0) {
|
||||
distance = dist_max;
|
||||
} else if (dist_max == kDistMax) {
|
||||
distance = dist_min;
|
||||
} else {
|
||||
distance = 0.5f * (dist_min + dist_max);
|
||||
}
|
||||
return distance;
|
||||
}
|
||||
|
||||
bool IsYUV420(j_compress_ptr cinfo) {
|
||||
return (cinfo->jpeg_color_space == JCS_YCbCr &&
|
||||
cinfo->comp_info[0].h_samp_factor == 2 &&
|
||||
cinfo->comp_info[0].v_samp_factor == 2 &&
|
||||
cinfo->comp_info[1].h_samp_factor == 1 &&
|
||||
cinfo->comp_info[1].v_samp_factor == 1 &&
|
||||
cinfo->comp_info[2].h_samp_factor == 1 &&
|
||||
cinfo->comp_info[2].v_samp_factor == 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void SetQuantMatrices(j_compress_ptr cinfo, float distances[NUM_QUANT_TBLS],
|
||||
bool add_two_chroma_tables) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
const bool xyb = m->xyb_mode && cinfo->jpeg_color_space == JCS_RGB;
|
||||
const bool is_yuv420 = IsYUV420(cinfo);
|
||||
|
||||
float global_scale;
|
||||
bool non_linear_scaling = true;
|
||||
const float* base_quant_matrix[NUM_QUANT_TBLS];
|
||||
int num_base_tables;
|
||||
|
||||
if (xyb) {
|
||||
global_scale = kGlobalScaleXYB;
|
||||
num_base_tables = 3;
|
||||
base_quant_matrix[0] = kBaseQuantMatrixXYB;
|
||||
base_quant_matrix[1] = kBaseQuantMatrixXYB + DCTSIZE2;
|
||||
base_quant_matrix[2] = kBaseQuantMatrixXYB + 2 * DCTSIZE2;
|
||||
} else if (cinfo->jpeg_color_space == JCS_YCbCr && !m->use_std_tables) {
|
||||
global_scale = kGlobalScaleYCbCr;
|
||||
if (m->cicp_transfer_function == kTransferFunctionPQ) {
|
||||
global_scale *= .4f;
|
||||
} else if (m->cicp_transfer_function == kTransferFunctionHLG) {
|
||||
global_scale *= .5f;
|
||||
}
|
||||
if (is_yuv420) {
|
||||
global_scale *= k420GlobalScale;
|
||||
}
|
||||
if (add_two_chroma_tables) {
|
||||
cinfo->comp_info[2].quant_tbl_no = 2;
|
||||
num_base_tables = 3;
|
||||
base_quant_matrix[0] = kBaseQuantMatrixYCbCr;
|
||||
base_quant_matrix[1] = kBaseQuantMatrixYCbCr + DCTSIZE2;
|
||||
base_quant_matrix[2] = kBaseQuantMatrixYCbCr + 2 * DCTSIZE2;
|
||||
} else {
|
||||
num_base_tables = 2;
|
||||
base_quant_matrix[0] = kBaseQuantMatrixYCbCr;
|
||||
// Use the Cr table for both Cb and Cr.
|
||||
base_quant_matrix[1] = kBaseQuantMatrixYCbCr + 2 * DCTSIZE2;
|
||||
}
|
||||
} else {
|
||||
global_scale = 0.01f;
|
||||
non_linear_scaling = false;
|
||||
num_base_tables = 2;
|
||||
base_quant_matrix[0] = kBaseQuantMatrixStd;
|
||||
base_quant_matrix[1] = kBaseQuantMatrixStd + DCTSIZE2;
|
||||
}
|
||||
|
||||
int quant_max = m->force_baseline ? 255 : 32767U;
|
||||
for (int quant_idx = 0; quant_idx < num_base_tables; ++quant_idx) {
|
||||
const float* base_qm = base_quant_matrix[quant_idx];
|
||||
JQUANT_TBL** qtable = &cinfo->quant_tbl_ptrs[quant_idx];
|
||||
if (*qtable == nullptr) {
|
||||
*qtable = jpegli_alloc_quant_table(reinterpret_cast<j_common_ptr>(cinfo));
|
||||
}
|
||||
for (int k = 0; k < DCTSIZE2; ++k) {
|
||||
float scale = global_scale;
|
||||
if (non_linear_scaling) {
|
||||
scale *= DistanceToScale(distances[quant_idx], k);
|
||||
if (is_yuv420 && quant_idx > 0) {
|
||||
scale *= k420Rescale[k];
|
||||
}
|
||||
} else {
|
||||
scale *= DistanceToLinearQuality(distances[quant_idx]);
|
||||
}
|
||||
int qval = std::round(scale * base_qm[k]);
|
||||
(*qtable)->quantval[k] = std::max(1, std::min(qval, quant_max));
|
||||
}
|
||||
(*qtable)->sent_table = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
void InitQuantizer(j_compress_ptr cinfo, QuantPass pass) {
|
||||
jpeg_comp_master* m = cinfo->master;
|
||||
// Compute quantization multipliers from the quant table values.
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
int quant_idx = cinfo->comp_info[c].quant_tbl_no;
|
||||
JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[quant_idx];
|
||||
if (!quant_table) {
|
||||
JPEGLI_ERROR("Missing quantization table %d for component %d", quant_idx,
|
||||
c);
|
||||
}
|
||||
for (size_t k = 0; k < DCTSIZE2; k++) {
|
||||
int val = quant_table->quantval[k];
|
||||
if (val == 0) {
|
||||
JPEGLI_ERROR("Invalid quantval 0.");
|
||||
}
|
||||
switch (pass) {
|
||||
case QuantPass::NO_SEARCH:
|
||||
m->quant_mul[c][k] = 8.0f / val;
|
||||
break;
|
||||
case QuantPass::SEARCH_FIRST_PASS:
|
||||
m->quant_mul[c][k] = 128.0f;
|
||||
break;
|
||||
case QuantPass::SEARCH_SECOND_PASS:
|
||||
m->quant_mul[c][kJPEGZigZagOrder[k]] = 1.0f / (16 * val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m->use_adaptive_quantization) {
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
for (int k = 0; k < DCTSIZE2; ++k) {
|
||||
m->zero_bias_mul[c][k] = k == 0 ? 0.0f : 0.5f;
|
||||
m->zero_bias_offset[c][k] = k == 0 ? 0.0f : 0.5f;
|
||||
}
|
||||
}
|
||||
if (cinfo->jpeg_color_space == JCS_YCbCr) {
|
||||
float distance = QuantValsToDistance(cinfo);
|
||||
static const float kDistHQ = 1.0f;
|
||||
static const float kDistLQ = 3.0f;
|
||||
float mix0 = (distance - kDistHQ) / (kDistLQ - kDistHQ);
|
||||
mix0 = std::max(0.0f, std::min(1.0f, mix0));
|
||||
float mix1 = 1.0f - mix0;
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
for (int k = 0; k < DCTSIZE2; ++k) {
|
||||
float mul0 = kZeroBiasMulYCbCrLQ[c * DCTSIZE2 + k];
|
||||
float mul1 = kZeroBiasMulYCbCrHQ[c * DCTSIZE2 + k];
|
||||
m->zero_bias_mul[c][k] = mix0 * mul0 + mix1 * mul1;
|
||||
m->zero_bias_offset[c][k] =
|
||||
k == 0 ? kZeroBiasOffsetYCbCrDC[c] : kZeroBiasOffsetYCbCrAC[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (cinfo->jpeg_color_space == JCS_YCbCr) {
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
for (int k = 0; k < DCTSIZE2; ++k) {
|
||||
m->zero_bias_offset[c][k] =
|
||||
k == 0 ? kZeroBiasOffsetYCbCrDC[c] : kZeroBiasOffsetYCbCrAC[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_QUANT_H_
|
||||
#define LIB_JPEGLI_QUANT_H_
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void SetQuantMatrices(j_compress_ptr cinfo, float distances[NUM_QUANT_TBLS],
|
||||
bool add_two_chroma_tables);
|
||||
|
||||
enum QuantPass {
|
||||
NO_SEARCH,
|
||||
SEARCH_FIRST_PASS,
|
||||
SEARCH_SECOND_PASS,
|
||||
};
|
||||
|
||||
void InitQuantizer(j_compress_ptr cinfo, QuantPass pass);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_QUANT_H_
|
||||
@@ -1,769 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/render.h"
|
||||
|
||||
#include <jxl/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/color_quantize.h"
|
||||
#include "lib/jpegli/color_transform.h"
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/common_internal.h"
|
||||
#include "lib/jpegli/decode_internal.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jpegli/idct.h"
|
||||
#include "lib/jpegli/types.h"
|
||||
#include "lib/jpegli/upsample.h"
|
||||
#include "lib/jxl/base/byte_order.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
#ifdef MEMORY_SANITIZER
|
||||
#define JXL_MEMORY_SANITIZER 1
|
||||
#elif defined(__has_feature)
|
||||
#if __has_feature(memory_sanitizer)
|
||||
#define JXL_MEMORY_SANITIZER 1
|
||||
#else
|
||||
#define JXL_MEMORY_SANITIZER 0
|
||||
#endif
|
||||
#else
|
||||
#define JXL_MEMORY_SANITIZER 0
|
||||
#endif
|
||||
|
||||
#if JXL_MEMORY_SANITIZER
|
||||
#include "sanitizer/msan_interface.h"
|
||||
#endif
|
||||
|
||||
#undef HWY_TARGET_INCLUDE
|
||||
#define HWY_TARGET_INCLUDE "lib/jpegli/render.cc"
|
||||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jpegli {
|
||||
namespace HWY_NAMESPACE {
|
||||
|
||||
// These templates are not found via ADL.
|
||||
using hwy::HWY_NAMESPACE::Abs;
|
||||
using hwy::HWY_NAMESPACE::Add;
|
||||
using hwy::HWY_NAMESPACE::Clamp;
|
||||
using hwy::HWY_NAMESPACE::Gt;
|
||||
using hwy::HWY_NAMESPACE::IfThenElseZero;
|
||||
using hwy::HWY_NAMESPACE::Mul;
|
||||
using hwy::HWY_NAMESPACE::NearestInt;
|
||||
using hwy::HWY_NAMESPACE::Or;
|
||||
using hwy::HWY_NAMESPACE::Rebind;
|
||||
using hwy::HWY_NAMESPACE::ShiftLeftSame;
|
||||
using hwy::HWY_NAMESPACE::ShiftRightSame;
|
||||
using hwy::HWY_NAMESPACE::Vec;
|
||||
using D = HWY_FULL(float);
|
||||
using DI = HWY_FULL(int32_t);
|
||||
constexpr D d;
|
||||
constexpr DI di;
|
||||
|
||||
void GatherBlockStats(const int16_t* JXL_RESTRICT coeffs,
|
||||
const size_t coeffs_size, int32_t* JXL_RESTRICT nonzeros,
|
||||
int32_t* JXL_RESTRICT sumabs) {
|
||||
for (size_t i = 0; i < coeffs_size; i += Lanes(d)) {
|
||||
size_t k = i % DCTSIZE2;
|
||||
const Rebind<int16_t, DI> di16;
|
||||
const Vec<DI> coeff = PromoteTo(di, Load(di16, coeffs + i));
|
||||
const auto abs_coeff = Abs(coeff);
|
||||
const auto not_0 = Gt(abs_coeff, Zero(di));
|
||||
const auto nzero = IfThenElseZero(not_0, Set(di, 1));
|
||||
Store(Add(nzero, Load(di, nonzeros + k)), di, nonzeros + k);
|
||||
Store(Add(abs_coeff, Load(di, sumabs + k)), di, sumabs + k);
|
||||
}
|
||||
}
|
||||
|
||||
void DecenterRow(float* row, size_t xsize) {
|
||||
const HWY_CAPPED(float, 8) df;
|
||||
const auto c128 = Set(df, 128.0f / 255);
|
||||
for (size_t x = 0; x < xsize; x += Lanes(df)) {
|
||||
Store(Add(Load(df, row + x), c128), df, row + x);
|
||||
}
|
||||
}
|
||||
|
||||
void DitherRow(j_decompress_ptr cinfo, float* row, int c, size_t y,
|
||||
size_t xsize) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
if (!m->dither_[c]) return;
|
||||
const float* dither_row =
|
||||
&m->dither_[c][(y & m->dither_mask_) * m->dither_size_];
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
row[x] += dither_row[x & m->dither_mask_];
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void StoreUnsignedRow(float* JXL_RESTRICT input[], size_t x0, size_t len,
|
||||
size_t num_channels, float multiplier, T* output) {
|
||||
const HWY_CAPPED(float, 8) cd;
|
||||
auto zero = Zero(cd);
|
||||
auto mul = Set(cd, multiplier);
|
||||
const Rebind<T, decltype(cd)> cdu;
|
||||
#if JXL_MEMORY_SANITIZER
|
||||
const size_t padding = hwy::RoundUpTo(len, Lanes(cd)) - len;
|
||||
for (size_t c = 0; c < num_channels; ++c) {
|
||||
__msan_unpoison(input[c] + x0 + len, sizeof(input[c][0]) * padding);
|
||||
}
|
||||
#endif
|
||||
if (num_channels == 1) {
|
||||
for (size_t i = 0; i < len; i += Lanes(cd)) {
|
||||
auto v0 = Clamp(zero, Mul(LoadU(cd, &input[0][x0 + i]), mul), mul);
|
||||
StoreU(DemoteTo(cdu, NearestInt(v0)), cdu, &output[i]);
|
||||
}
|
||||
} else if (num_channels == 2) {
|
||||
for (size_t i = 0; i < len; i += Lanes(cd)) {
|
||||
auto v0 = Clamp(zero, Mul(LoadU(cd, &input[0][x0 + i]), mul), mul);
|
||||
auto v1 = Clamp(zero, Mul(LoadU(cd, &input[1][x0 + i]), mul), mul);
|
||||
StoreInterleaved2(DemoteTo(cdu, NearestInt(v0)),
|
||||
DemoteTo(cdu, NearestInt(v1)), cdu, &output[2 * i]);
|
||||
}
|
||||
} else if (num_channels == 3) {
|
||||
for (size_t i = 0; i < len; i += Lanes(cd)) {
|
||||
auto v0 = Clamp(zero, Mul(LoadU(cd, &input[0][x0 + i]), mul), mul);
|
||||
auto v1 = Clamp(zero, Mul(LoadU(cd, &input[1][x0 + i]), mul), mul);
|
||||
auto v2 = Clamp(zero, Mul(LoadU(cd, &input[2][x0 + i]), mul), mul);
|
||||
StoreInterleaved3(DemoteTo(cdu, NearestInt(v0)),
|
||||
DemoteTo(cdu, NearestInt(v1)),
|
||||
DemoteTo(cdu, NearestInt(v2)), cdu, &output[3 * i]);
|
||||
}
|
||||
} else if (num_channels == 4) {
|
||||
for (size_t i = 0; i < len; i += Lanes(cd)) {
|
||||
auto v0 = Clamp(zero, Mul(LoadU(cd, &input[0][x0 + i]), mul), mul);
|
||||
auto v1 = Clamp(zero, Mul(LoadU(cd, &input[1][x0 + i]), mul), mul);
|
||||
auto v2 = Clamp(zero, Mul(LoadU(cd, &input[2][x0 + i]), mul), mul);
|
||||
auto v3 = Clamp(zero, Mul(LoadU(cd, &input[3][x0 + i]), mul), mul);
|
||||
StoreInterleaved4(DemoteTo(cdu, NearestInt(v0)),
|
||||
DemoteTo(cdu, NearestInt(v1)),
|
||||
DemoteTo(cdu, NearestInt(v2)),
|
||||
DemoteTo(cdu, NearestInt(v3)), cdu, &output[4 * i]);
|
||||
}
|
||||
}
|
||||
#if JXL_MEMORY_SANITIZER
|
||||
__msan_poison(output + num_channels * len,
|
||||
sizeof(output[0]) * num_channels * padding);
|
||||
#endif
|
||||
}
|
||||
|
||||
void StoreFloatRow(float* JXL_RESTRICT input[3], size_t x0, size_t len,
|
||||
size_t num_channels, float* output) {
|
||||
const HWY_CAPPED(float, 8) cd;
|
||||
if (num_channels == 1) {
|
||||
memcpy(output, input[0] + x0, len * sizeof(output[0]));
|
||||
} else if (num_channels == 2) {
|
||||
for (size_t i = 0; i < len; i += Lanes(cd)) {
|
||||
StoreInterleaved2(LoadU(cd, &input[0][x0 + i]),
|
||||
LoadU(cd, &input[1][x0 + i]), cd, &output[2 * i]);
|
||||
}
|
||||
} else if (num_channels == 3) {
|
||||
for (size_t i = 0; i < len; i += Lanes(cd)) {
|
||||
StoreInterleaved3(LoadU(cd, &input[0][x0 + i]),
|
||||
LoadU(cd, &input[1][x0 + i]),
|
||||
LoadU(cd, &input[2][x0 + i]), cd, &output[3 * i]);
|
||||
}
|
||||
} else if (num_channels == 4) {
|
||||
for (size_t i = 0; i < len; i += Lanes(cd)) {
|
||||
StoreInterleaved4(LoadU(cd, &input[0][x0 + i]),
|
||||
LoadU(cd, &input[1][x0 + i]),
|
||||
LoadU(cd, &input[2][x0 + i]),
|
||||
LoadU(cd, &input[3][x0 + i]), cd, &output[4 * i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr float kFSWeightMR = 7.0f / 16.0f;
|
||||
static constexpr float kFSWeightBL = 3.0f / 16.0f;
|
||||
static constexpr float kFSWeightBM = 5.0f / 16.0f;
|
||||
static constexpr float kFSWeightBR = 1.0f / 16.0f;
|
||||
|
||||
float LimitError(float error) {
|
||||
float abserror = std::abs(error);
|
||||
if (abserror > 48.0f) {
|
||||
abserror = 32.0f;
|
||||
} else if (abserror > 16.0f) {
|
||||
abserror = 0.5f * abserror + 8.0f;
|
||||
}
|
||||
return error > 0.0f ? abserror : -abserror;
|
||||
}
|
||||
|
||||
void WriteToOutput(j_decompress_ptr cinfo, float* JXL_RESTRICT rows[],
|
||||
size_t xoffset, size_t len, size_t num_channels,
|
||||
uint8_t* JXL_RESTRICT output) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
uint8_t* JXL_RESTRICT scratch_space = m->output_scratch_;
|
||||
if (cinfo->quantize_colors && m->quant_pass_ == 1) {
|
||||
float* error_row[kMaxComponents];
|
||||
float* next_error_row[kMaxComponents];
|
||||
J_DITHER_MODE dither_mode = cinfo->dither_mode;
|
||||
if (dither_mode == JDITHER_ORDERED) {
|
||||
for (size_t c = 0; c < num_channels; ++c) {
|
||||
DitherRow(cinfo, &rows[c][xoffset], c, cinfo->output_scanline,
|
||||
cinfo->output_width);
|
||||
}
|
||||
} else if (dither_mode == JDITHER_FS) {
|
||||
for (size_t c = 0; c < num_channels; ++c) {
|
||||
if (cinfo->output_scanline % 2 == 0) {
|
||||
error_row[c] = m->error_row_[c];
|
||||
next_error_row[c] = m->error_row_[c + kMaxComponents];
|
||||
} else {
|
||||
error_row[c] = m->error_row_[c + kMaxComponents];
|
||||
next_error_row[c] = m->error_row_[c];
|
||||
}
|
||||
memset(next_error_row[c], 0.0, cinfo->output_width * sizeof(float));
|
||||
}
|
||||
}
|
||||
const float mul = 255.0f;
|
||||
if (dither_mode != JDITHER_FS) {
|
||||
StoreUnsignedRow(rows, xoffset, len, num_channels, mul, scratch_space);
|
||||
}
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
uint8_t* pixel = &scratch_space[num_channels * i];
|
||||
if (dither_mode == JDITHER_FS) {
|
||||
for (size_t c = 0; c < num_channels; ++c) {
|
||||
float val = rows[c][i] * mul + LimitError(error_row[c][i]);
|
||||
pixel[c] = std::round(std::min(255.0f, std::max(0.0f, val)));
|
||||
}
|
||||
}
|
||||
int index = LookupColorIndex(cinfo, pixel);
|
||||
output[i] = index;
|
||||
if (dither_mode == JDITHER_FS) {
|
||||
size_t prev_i = i > 0 ? i - 1 : 0;
|
||||
size_t next_i = i + 1 < len ? i + 1 : len - 1;
|
||||
for (size_t c = 0; c < num_channels; ++c) {
|
||||
float error = pixel[c] - cinfo->colormap[c][index];
|
||||
error_row[c][next_i] += kFSWeightMR * error;
|
||||
next_error_row[c][prev_i] += kFSWeightBL * error;
|
||||
next_error_row[c][i] += kFSWeightBM * error;
|
||||
next_error_row[c][next_i] += kFSWeightBR * error;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (m->output_data_type_ == JPEGLI_TYPE_UINT8) {
|
||||
const float mul = 255.0;
|
||||
StoreUnsignedRow(rows, xoffset, len, num_channels, mul, scratch_space);
|
||||
memcpy(output, scratch_space, len * num_channels);
|
||||
} else if (m->output_data_type_ == JPEGLI_TYPE_UINT16) {
|
||||
const float mul = 65535.0;
|
||||
uint16_t* tmp = reinterpret_cast<uint16_t*>(scratch_space);
|
||||
StoreUnsignedRow(rows, xoffset, len, num_channels, mul, tmp);
|
||||
if (m->swap_endianness_) {
|
||||
const HWY_CAPPED(uint16_t, 8) du;
|
||||
size_t output_len = len * num_channels;
|
||||
for (size_t j = 0; j < output_len; j += Lanes(du)) {
|
||||
auto v = LoadU(du, tmp + j);
|
||||
auto vswap = Or(ShiftRightSame(v, 8), ShiftLeftSame(v, 8));
|
||||
StoreU(vswap, du, tmp + j);
|
||||
}
|
||||
}
|
||||
memcpy(output, tmp, len * num_channels * 2);
|
||||
} else if (m->output_data_type_ == JPEGLI_TYPE_FLOAT) {
|
||||
float* tmp = reinterpret_cast<float*>(scratch_space);
|
||||
StoreFloatRow(rows, xoffset, len, num_channels, tmp);
|
||||
if (m->swap_endianness_) {
|
||||
size_t output_len = len * num_channels;
|
||||
for (size_t j = 0; j < output_len; ++j) {
|
||||
tmp[j] = BSwapFloat(tmp[j]);
|
||||
}
|
||||
}
|
||||
memcpy(output, tmp, len * num_channels * 4);
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
|
||||
#if HWY_ONCE
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
HWY_EXPORT(GatherBlockStats);
|
||||
HWY_EXPORT(WriteToOutput);
|
||||
HWY_EXPORT(DecenterRow);
|
||||
|
||||
void GatherBlockStats(const int16_t* JXL_RESTRICT coeffs,
|
||||
const size_t coeffs_size, int32_t* JXL_RESTRICT nonzeros,
|
||||
int32_t* JXL_RESTRICT sumabs) {
|
||||
HWY_DYNAMIC_DISPATCH(GatherBlockStats)(coeffs, coeffs_size, nonzeros, sumabs);
|
||||
}
|
||||
|
||||
void WriteToOutput(j_decompress_ptr cinfo, float* JXL_RESTRICT rows[],
|
||||
size_t xoffset, size_t len, size_t num_channels,
|
||||
uint8_t* JXL_RESTRICT output) {
|
||||
HWY_DYNAMIC_DISPATCH(WriteToOutput)
|
||||
(cinfo, rows, xoffset, len, num_channels, output);
|
||||
}
|
||||
|
||||
void DecenterRow(float* row, size_t xsize) {
|
||||
HWY_DYNAMIC_DISPATCH(DecenterRow)(row, xsize);
|
||||
}
|
||||
|
||||
bool ShouldApplyDequantBiases(j_decompress_ptr cinfo, int ci) {
|
||||
const auto& compinfo = cinfo->comp_info[ci];
|
||||
return (compinfo.h_samp_factor == cinfo->max_h_samp_factor &&
|
||||
compinfo.v_samp_factor == cinfo->max_v_samp_factor);
|
||||
}
|
||||
|
||||
// See the following article for the details:
|
||||
// J. R. Price and M. Rabbani, "Dequantization bias for JPEG decompression"
|
||||
// Proceedings International Conference on Information Technology: Coding and
|
||||
// Computing (Cat. No.PR00540), 2000, pp. 30-35, doi: 10.1109/ITCC.2000.844179.
|
||||
void ComputeOptimalLaplacianBiases(const int num_blocks, const int* nonzeros,
|
||||
const int* sumabs, float* biases) {
|
||||
for (size_t k = 1; k < DCTSIZE2; ++k) {
|
||||
if (nonzeros[k] == 0) {
|
||||
biases[k] = 0.5f;
|
||||
continue;
|
||||
}
|
||||
// Notation adapted from the article
|
||||
float N = num_blocks;
|
||||
float N1 = nonzeros[k];
|
||||
float N0 = num_blocks - N1;
|
||||
float S = sumabs[k];
|
||||
// Compute gamma from N0, N1, N, S (eq. 11), with A and B being just
|
||||
// temporary grouping of terms.
|
||||
float A = 4.0 * S + 2.0 * N;
|
||||
float B = 4.0 * S - 2.0 * N1;
|
||||
float gamma = (-1.0 * N0 + std::sqrt(N0 * N0 * 1.0 + A * B)) / A;
|
||||
float gamma2 = gamma * gamma;
|
||||
// The bias is computed from gamma with (eq. 5), where the quantization
|
||||
// multiplier Q can be factored out and thus the bias can be applied
|
||||
// directly on the quantized coefficient.
|
||||
biases[k] =
|
||||
0.5 * (((1.0 + gamma2) / (1.0 - gamma2)) + 1.0 / std::log(gamma));
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::array<int, SAVED_COEFS> Q_POS = {0, 1, 8, 16, 9,
|
||||
2, 3, 10, 17, 24};
|
||||
|
||||
bool is_nonzero_quantizers(const JQUANT_TBL* qtable) {
|
||||
return std::all_of(Q_POS.begin(), Q_POS.end(),
|
||||
[&](int pos) { return qtable->quantval[pos] != 0; });
|
||||
}
|
||||
|
||||
// Determine whether smoothing should be applied during decompression
|
||||
bool do_smoothing(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
bool smoothing_useful = false;
|
||||
|
||||
if (!cinfo->progressive_mode || cinfo->coef_bits == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto* coef_bits_latch = m->coef_bits_latch;
|
||||
auto* prev_coef_bits_latch = m->prev_coef_bits_latch;
|
||||
|
||||
for (int ci = 0; ci < cinfo->num_components; ci++) {
|
||||
jpeg_component_info* compptr = &cinfo->comp_info[ci];
|
||||
JQUANT_TBL* qtable = compptr->quant_table;
|
||||
int* coef_bits = cinfo->coef_bits[ci];
|
||||
int* prev_coef_bits = cinfo->coef_bits[ci + cinfo->num_components];
|
||||
|
||||
// Return early if conditions for smoothing are not met
|
||||
if (qtable == nullptr || !is_nonzero_quantizers(qtable) ||
|
||||
coef_bits[0] < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
coef_bits_latch[ci][0] = coef_bits[0];
|
||||
|
||||
for (int coefi = 1; coefi < SAVED_COEFS; coefi++) {
|
||||
prev_coef_bits_latch[ci][coefi] =
|
||||
cinfo->input_scan_number > 1 ? prev_coef_bits[coefi] : -1;
|
||||
if (coef_bits[coefi] != 0) {
|
||||
smoothing_useful = true;
|
||||
}
|
||||
coef_bits_latch[ci][coefi] = coef_bits[coefi];
|
||||
}
|
||||
}
|
||||
|
||||
return smoothing_useful;
|
||||
}
|
||||
|
||||
void PredictSmooth(j_decompress_ptr cinfo, JBLOCKARRAY blocks, int component,
|
||||
size_t bx, int iy) {
|
||||
const size_t imcu_row = cinfo->output_iMCU_row;
|
||||
int16_t* scratch = cinfo->master->smoothing_scratch_;
|
||||
std::vector<int> Q_VAL(SAVED_COEFS);
|
||||
int* coef_bits;
|
||||
|
||||
std::array<std::array<int, 5>, 5> dc_values;
|
||||
auto& compinfo = cinfo->comp_info[component];
|
||||
const size_t by0 = imcu_row * compinfo.v_samp_factor;
|
||||
const size_t by = by0 + iy;
|
||||
|
||||
int prev_iy = by > 0 ? iy - 1 : 0;
|
||||
int prev_prev_iy = by > 1 ? iy - 2 : prev_iy;
|
||||
int next_iy = by + 1 < compinfo.height_in_blocks ? iy + 1 : iy;
|
||||
int next_next_iy = by + 2 < compinfo.height_in_blocks ? iy + 2 : next_iy;
|
||||
|
||||
const int16_t* cur_row = blocks[iy][bx];
|
||||
const int16_t* prev_row = blocks[prev_iy][bx];
|
||||
const int16_t* prev_prev_row = blocks[prev_prev_iy][bx];
|
||||
const int16_t* next_row = blocks[next_iy][bx];
|
||||
const int16_t* next_next_row = blocks[next_next_iy][bx];
|
||||
|
||||
int prev_block_ind = bx ? -DCTSIZE2 : 0;
|
||||
int prev_prev_block_ind = bx > 1 ? -2 * DCTSIZE2 : prev_block_ind;
|
||||
int next_block_ind = bx + 1 < compinfo.width_in_blocks ? DCTSIZE2 : 0;
|
||||
int next_next_block_ind =
|
||||
bx + 2 < compinfo.width_in_blocks ? DCTSIZE2 * 2 : next_block_ind;
|
||||
|
||||
std::array<const int16_t*, 5> row_ptrs = {prev_prev_row, prev_row, cur_row,
|
||||
next_row, next_next_row};
|
||||
std::array<int, 5> block_inds = {prev_prev_block_ind, prev_block_ind, 0,
|
||||
next_block_ind, next_next_block_ind};
|
||||
|
||||
memcpy(scratch, cur_row, DCTSIZE2 * sizeof(cur_row[0]));
|
||||
|
||||
for (int r = 0; r < 5; ++r) {
|
||||
for (int c = 0; c < 5; ++c) {
|
||||
dc_values[r][c] = row_ptrs[r][block_inds[c]];
|
||||
}
|
||||
}
|
||||
// Get the correct coef_bits: In case of an incomplete scan, we use the
|
||||
// prev coefficients.
|
||||
if (cinfo->output_iMCU_row + 1 > cinfo->input_iMCU_row) {
|
||||
coef_bits = cinfo->master->prev_coef_bits_latch[component];
|
||||
} else {
|
||||
coef_bits = cinfo->master->coef_bits_latch[component];
|
||||
}
|
||||
|
||||
bool change_dc = true;
|
||||
for (int i = 1; i < SAVED_COEFS; i++) {
|
||||
if (coef_bits[i] != -1) {
|
||||
change_dc = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
JQUANT_TBL* quanttbl = cinfo->quant_tbl_ptrs[compinfo.quant_tbl_no];
|
||||
for (size_t i = 0; i < 6; ++i) {
|
||||
Q_VAL[i] = quanttbl->quantval[Q_POS[i]];
|
||||
}
|
||||
if (change_dc) {
|
||||
for (size_t i = 6; i < SAVED_COEFS; ++i) {
|
||||
Q_VAL[i] = quanttbl->quantval[Q_POS[i]];
|
||||
}
|
||||
}
|
||||
auto calculate_dct_value = [&](int coef_index) {
|
||||
int64_t num = 0;
|
||||
int pred;
|
||||
int Al;
|
||||
// we use the symmetry of the smoothing matrices by transposing the 5x5 dc
|
||||
// matrix in that case.
|
||||
bool swap_indices = coef_index == 2 || coef_index == 5 || coef_index == 8 ||
|
||||
coef_index == 9;
|
||||
auto dc = [&](int i, int j) {
|
||||
return swap_indices ? dc_values[j][i] : dc_values[i][j];
|
||||
};
|
||||
JPEGLI_CHECK(coef_index >= 0 && coef_index < 10);
|
||||
Al = coef_bits[coef_index];
|
||||
switch (coef_index) {
|
||||
case 0:
|
||||
// set the DC
|
||||
num = (-2 * dc(0, 0) - 6 * dc(0, 1) - 8 * dc(0, 2) - 6 * dc(0, 3) -
|
||||
2 * dc(0, 4) - 6 * dc(1, 0) + 6 * dc(1, 1) + 42 * dc(1, 2) +
|
||||
6 * dc(1, 3) - 6 * dc(1, 4) - 8 * dc(2, 0) + 42 * dc(2, 1) +
|
||||
152 * dc(2, 2) + 42 * dc(2, 3) - 8 * dc(2, 4) - 6 * dc(3, 0) +
|
||||
6 * dc(3, 1) + 42 * dc(3, 2) + 6 * dc(3, 3) - 6 * dc(3, 4) -
|
||||
2 * dc(4, 0) - 6 * dc(4, 1) - 8 * dc(4, 2) - 6 * dc(4, 3) -
|
||||
2 * dc(4, 4));
|
||||
// special case: for the DC the dequantization is different
|
||||
Al = 0;
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
// set Q01 or Q10
|
||||
num = (change_dc ? (-dc(0, 0) - dc(0, 1) + dc(0, 3) + dc(0, 4) -
|
||||
3 * dc(1, 0) + 13 * dc(1, 1) - 13 * dc(1, 3) +
|
||||
3 * dc(1, 4) - 3 * dc(2, 0) + 38 * dc(2, 1) -
|
||||
38 * dc(2, 3) + 3 * dc(2, 4) - 3 * dc(3, 0) +
|
||||
13 * dc(3, 1) - 13 * dc(3, 3) + 3 * dc(3, 4) -
|
||||
dc(4, 0) - dc(4, 1) + dc(4, 3) + dc(4, 4))
|
||||
: (-7 * dc(2, 0) + 50 * dc(2, 1) - 50 * dc(2, 3) +
|
||||
7 * dc(2, 4)));
|
||||
break;
|
||||
case 3:
|
||||
case 5:
|
||||
// set Q02 or Q20
|
||||
num = (change_dc
|
||||
? dc(0, 2) + 2 * dc(1, 1) + 7 * dc(1, 2) + 2 * dc(1, 3) -
|
||||
5 * dc(2, 1) - 14 * dc(2, 2) - 5 * dc(2, 3) +
|
||||
2 * dc(3, 1) + 7 * dc(3, 2) + 2 * dc(3, 3) + dc(4, 2)
|
||||
: (-dc(0, 2) + 13 * dc(1, 2) - 24 * dc(2, 2) +
|
||||
13 * dc(3, 2) - dc(4, 2)));
|
||||
break;
|
||||
case 4:
|
||||
// set Q11
|
||||
num =
|
||||
(change_dc ? -dc(0, 0) + dc(0, 4) + 9 * dc(1, 1) - 9 * dc(1, 3) -
|
||||
9 * dc(3, 1) + 9 * dc(3, 3) + dc(4, 0) - dc(4, 4)
|
||||
: (dc(1, 4) + dc(3, 0) - 10 * dc(3, 1) + 10 * dc(3, 3) -
|
||||
dc(0, 1) - dc(3, 4) + dc(4, 1) - dc(4, 3) + dc(0, 3) -
|
||||
dc(1, 0) + 10 * dc(1, 1) - 10 * dc(1, 3)));
|
||||
break;
|
||||
case 6:
|
||||
case 9:
|
||||
// set Q03 or Q30
|
||||
num = (dc(1, 1) - dc(1, 3) + 2 * dc(2, 1) - 2 * dc(2, 3) + dc(3, 1) -
|
||||
dc(3, 3));
|
||||
break;
|
||||
case 7:
|
||||
case 8:
|
||||
default:
|
||||
// set Q12 and Q21
|
||||
num = (dc(1, 1) - 3 * dc(1, 2) + dc(1, 3) - dc(3, 1) + 3 * dc(3, 2) -
|
||||
dc(3, 3));
|
||||
break;
|
||||
}
|
||||
num = Q_VAL[0] * num;
|
||||
if (num >= 0) {
|
||||
pred = ((Q_VAL[coef_index] << 7) + num) / (Q_VAL[coef_index] << 8);
|
||||
if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1;
|
||||
} else {
|
||||
pred = ((Q_VAL[coef_index] << 7) - num) / (Q_VAL[coef_index] << 8);
|
||||
if (Al > 0 && pred >= (1 << Al)) pred = (1 << Al) - 1;
|
||||
pred = -pred;
|
||||
}
|
||||
return static_cast<int16_t>(pred);
|
||||
};
|
||||
|
||||
int loop_end = change_dc ? SAVED_COEFS : 6;
|
||||
for (int i = 1; i < loop_end; ++i) {
|
||||
if (coef_bits[i] != 0 && scratch[Q_POS[i]] == 0) {
|
||||
scratch[Q_POS[i]] = calculate_dct_value(i);
|
||||
}
|
||||
}
|
||||
if (change_dc) {
|
||||
scratch[0] = calculate_dct_value(0);
|
||||
}
|
||||
}
|
||||
|
||||
void PrepareForOutput(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
bool smoothing = do_smoothing(cinfo);
|
||||
m->apply_smoothing = smoothing && FROM_JXL_BOOL(cinfo->do_block_smoothing);
|
||||
size_t coeffs_per_block = cinfo->num_components * DCTSIZE2;
|
||||
memset(m->nonzeros_, 0, coeffs_per_block * sizeof(m->nonzeros_[0]));
|
||||
memset(m->sumabs_, 0, coeffs_per_block * sizeof(m->sumabs_[0]));
|
||||
memset(m->num_processed_blocks_, 0, sizeof(m->num_processed_blocks_));
|
||||
memset(m->biases_, 0, coeffs_per_block * sizeof(m->biases_[0]));
|
||||
cinfo->output_iMCU_row = 0;
|
||||
cinfo->output_scanline = 0;
|
||||
const float kDequantScale = 1.0f / (8 * 255);
|
||||
for (int c = 0; c < cinfo->num_components; c++) {
|
||||
const auto& comp = cinfo->comp_info[c];
|
||||
JQUANT_TBL* table = comp.quant_table;
|
||||
if (table == nullptr) continue;
|
||||
for (size_t k = 0; k < DCTSIZE2; ++k) {
|
||||
m->dequant_[c * DCTSIZE2 + k] = table->quantval[k] * kDequantScale;
|
||||
}
|
||||
}
|
||||
JPEGLI_CHECK(ChooseInverseTransform(cinfo));
|
||||
ChooseColorTransform(cinfo);
|
||||
}
|
||||
|
||||
void DecodeCurrentiMCURow(j_decompress_ptr cinfo) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
const size_t imcu_row = cinfo->output_iMCU_row;
|
||||
JBLOCKARRAY blocks[kMaxComponents];
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
const jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
int by0 = 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;
|
||||
blocks[c] = (*cinfo->mem->access_virt_barray)(
|
||||
reinterpret_cast<j_common_ptr>(cinfo), m->coef_arrays[c], offset,
|
||||
max_block_rows, FALSE);
|
||||
}
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
size_t k0 = c * DCTSIZE2;
|
||||
auto& compinfo = cinfo->comp_info[c];
|
||||
size_t block_row = imcu_row * compinfo.v_samp_factor;
|
||||
if (ShouldApplyDequantBiases(cinfo, c)) {
|
||||
// Update statistics for this iMCU row.
|
||||
for (int iy = 0; iy < compinfo.v_samp_factor; ++iy) {
|
||||
size_t by = block_row + iy;
|
||||
if (by >= compinfo.height_in_blocks) {
|
||||
continue;
|
||||
}
|
||||
int16_t* JXL_RESTRICT coeffs = &blocks[c][iy][0][0];
|
||||
size_t num = compinfo.width_in_blocks * DCTSIZE2;
|
||||
GatherBlockStats(coeffs, num, &m->nonzeros_[k0], &m->sumabs_[k0]);
|
||||
m->num_processed_blocks_[c] += compinfo.width_in_blocks;
|
||||
}
|
||||
if (imcu_row % 4 == 3) {
|
||||
// Re-compute optimal biases every few iMCU-rows.
|
||||
ComputeOptimalLaplacianBiases(m->num_processed_blocks_[c],
|
||||
&m->nonzeros_[k0], &m->sumabs_[k0],
|
||||
&m->biases_[k0]);
|
||||
}
|
||||
}
|
||||
RowBuffer<float>* raw_out = &m->raw_output_[c];
|
||||
for (int iy = 0; iy < compinfo.v_samp_factor; ++iy) {
|
||||
size_t by = block_row + iy;
|
||||
if (by >= compinfo.height_in_blocks) {
|
||||
continue;
|
||||
}
|
||||
size_t dctsize = m->scaled_dct_size[c];
|
||||
int16_t* JXL_RESTRICT row_in = &blocks[c][iy][0][0];
|
||||
float* JXL_RESTRICT row_out = raw_out->Row(by * dctsize);
|
||||
for (size_t bx = 0; bx < compinfo.width_in_blocks; ++bx) {
|
||||
if (m->apply_smoothing) {
|
||||
PredictSmooth(cinfo, blocks[c], c, bx, iy);
|
||||
(*m->inverse_transform[c])(m->smoothing_scratch_, &m->dequant_[k0],
|
||||
&m->biases_[k0], m->idct_scratch_,
|
||||
&row_out[bx * dctsize], raw_out->stride(),
|
||||
dctsize);
|
||||
} else {
|
||||
(*m->inverse_transform[c])(&row_in[bx * DCTSIZE2], &m->dequant_[k0],
|
||||
&m->biases_[k0], m->idct_scratch_,
|
||||
&row_out[bx * dctsize], raw_out->stride(),
|
||||
dctsize);
|
||||
}
|
||||
}
|
||||
if (m->streaming_mode_) {
|
||||
memset(row_in, 0, compinfo.width_in_blocks * sizeof(JBLOCK));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessRawOutput(j_decompress_ptr cinfo, JSAMPIMAGE data) {
|
||||
jpegli::DecodeCurrentiMCURow(cinfo);
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
const auto& compinfo = cinfo->comp_info[c];
|
||||
size_t comp_width = compinfo.width_in_blocks * DCTSIZE;
|
||||
size_t comp_height = compinfo.height_in_blocks * DCTSIZE;
|
||||
size_t comp_nrows = compinfo.v_samp_factor * DCTSIZE;
|
||||
size_t y0 = static_cast<size_t>(cinfo->output_iMCU_row) *
|
||||
compinfo.v_samp_factor * DCTSIZE;
|
||||
size_t y1 = std::min(y0 + comp_nrows, comp_height);
|
||||
for (size_t y = y0; y < y1; ++y) {
|
||||
float* rows[1] = {m->raw_output_[c].Row(y)};
|
||||
uint8_t* output = data[c][y - y0];
|
||||
DecenterRow(rows[0], comp_width);
|
||||
WriteToOutput(cinfo, rows, 0, comp_width, 1, output);
|
||||
}
|
||||
}
|
||||
++cinfo->output_iMCU_row;
|
||||
cinfo->output_scanline += cinfo->max_v_samp_factor * DCTSIZE;
|
||||
if (cinfo->output_scanline >= cinfo->output_height) {
|
||||
++m->output_passes_done_;
|
||||
}
|
||||
}
|
||||
|
||||
void ProcessOutput(j_decompress_ptr cinfo, size_t* num_output_rows,
|
||||
JSAMPARRAY scanlines, size_t max_output_rows) {
|
||||
jpeg_decomp_master* m = cinfo->master;
|
||||
const size_t vfactor = cinfo->max_v_samp_factor;
|
||||
const size_t hfactor = cinfo->max_h_samp_factor;
|
||||
const size_t context = m->need_context_rows_ ? 1 : 0;
|
||||
const size_t imcu_row = cinfo->output_iMCU_row;
|
||||
const size_t imcu_height = vfactor * m->min_scaled_dct_size;
|
||||
const size_t imcu_width = hfactor * m->min_scaled_dct_size;
|
||||
const size_t output_width = m->iMCU_cols_ * imcu_width;
|
||||
if (imcu_row == cinfo->total_iMCU_rows ||
|
||||
(imcu_row > context &&
|
||||
cinfo->output_scanline < (imcu_row - context) * imcu_height)) {
|
||||
// We are ready to output some scanlines.
|
||||
size_t ybegin = cinfo->output_scanline;
|
||||
size_t yend = (imcu_row == cinfo->total_iMCU_rows
|
||||
? cinfo->output_height
|
||||
: (imcu_row - context) * imcu_height);
|
||||
yend = std::min<size_t>(yend, ybegin + max_output_rows - *num_output_rows);
|
||||
size_t yb = (ybegin / vfactor) * vfactor;
|
||||
size_t ye = DivCeil(yend, vfactor) * vfactor;
|
||||
for (size_t y = yb; y < ye; y += vfactor) {
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
RowBuffer<float>* raw_out = &m->raw_output_[c];
|
||||
RowBuffer<float>* render_out = &m->render_output_[c];
|
||||
int line_groups = vfactor / m->v_factor[c];
|
||||
int downsampled_width = output_width / m->h_factor[c];
|
||||
size_t yc = y / m->v_factor[c];
|
||||
for (int dy = 0; dy < line_groups; ++dy) {
|
||||
size_t ymid = yc + dy;
|
||||
const float* JXL_RESTRICT row_mid = raw_out->Row(ymid);
|
||||
if (cinfo->do_fancy_upsampling && m->v_factor[c] == 2) {
|
||||
const float* JXL_RESTRICT row_top =
|
||||
ymid == 0 ? row_mid : raw_out->Row(ymid - 1);
|
||||
const float* JXL_RESTRICT row_bot = ymid + 1 == m->raw_height_[c]
|
||||
? row_mid
|
||||
: raw_out->Row(ymid + 1);
|
||||
Upsample2Vertical(row_top, row_mid, row_bot,
|
||||
render_out->Row(2 * dy),
|
||||
render_out->Row(2 * dy + 1), downsampled_width);
|
||||
} else {
|
||||
for (int yix = 0; yix < m->v_factor[c]; ++yix) {
|
||||
memcpy(render_out->Row(m->v_factor[c] * dy + yix), row_mid,
|
||||
downsampled_width * sizeof(float));
|
||||
}
|
||||
}
|
||||
if (m->h_factor[c] > 1) {
|
||||
for (int yix = 0; yix < m->v_factor[c]; ++yix) {
|
||||
int row_ix = m->v_factor[c] * dy + yix;
|
||||
float* JXL_RESTRICT row = render_out->Row(row_ix);
|
||||
float* JXL_RESTRICT tmp =
|
||||
m->upsample_scratch_ + HWY_ALIGNMENT / sizeof(float);
|
||||
if (cinfo->do_fancy_upsampling && m->h_factor[c] == 2) {
|
||||
Upsample2Horizontal(row, tmp, output_width);
|
||||
} else {
|
||||
// TODO(szabadka) SIMDify this.
|
||||
for (size_t x = 0; x < output_width; ++x) {
|
||||
tmp[x] = row[x / m->h_factor[c]];
|
||||
}
|
||||
memcpy(row, tmp, output_width * sizeof(tmp[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (size_t yix = 0; yix < vfactor; ++yix) {
|
||||
if (y + yix < ybegin || y + yix >= yend) continue;
|
||||
float* rows[kMaxComponents];
|
||||
int num_all_components =
|
||||
std::max(cinfo->out_color_components, cinfo->num_components);
|
||||
for (int c = 0; c < num_all_components; ++c) {
|
||||
rows[c] = m->render_output_[c].Row(yix);
|
||||
}
|
||||
(*m->color_transform)(rows, output_width);
|
||||
for (int c = 0; c < cinfo->out_color_components; ++c) {
|
||||
// Undo the centering of the sample values around zero.
|
||||
DecenterRow(rows[c], output_width);
|
||||
}
|
||||
if (scanlines) {
|
||||
uint8_t* output = scanlines[*num_output_rows];
|
||||
WriteToOutput(cinfo, rows, m->xoffset_, cinfo->output_width,
|
||||
cinfo->out_color_components, output);
|
||||
}
|
||||
JPEGLI_CHECK(cinfo->output_scanline == y + yix);
|
||||
++cinfo->output_scanline;
|
||||
++(*num_output_rows);
|
||||
if (cinfo->output_scanline == cinfo->output_height) {
|
||||
++m->output_passes_done_;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DecodeCurrentiMCURow(cinfo);
|
||||
++cinfo->output_iMCU_row;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
#endif // HWY_ONCE
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_RENDER_H_
|
||||
#define LIB_JPEGLI_RENDER_H_
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void PrepareForOutput(j_decompress_ptr cinfo);
|
||||
|
||||
void ProcessOutput(j_decompress_ptr cinfo, size_t* num_output_rows,
|
||||
JSAMPARRAY scanlines, size_t max_output_rows);
|
||||
|
||||
void ProcessRawOutput(j_decompress_ptr cinfo, JSAMPIMAGE data);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_RENDER_H_
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/simd.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#undef HWY_TARGET_INCLUDE
|
||||
#define HWY_TARGET_INCLUDE "lib/jpegli/simd.cc"
|
||||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jpegli {
|
||||
namespace HWY_NAMESPACE {
|
||||
|
||||
size_t GetVectorSize() { return HWY_LANES(uint8_t); }
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
|
||||
#if HWY_ONCE
|
||||
namespace jpegli {
|
||||
namespace {
|
||||
|
||||
HWY_EXPORT(GetVectorSize); // Local function.
|
||||
|
||||
} // namespace
|
||||
|
||||
size_t VectorSize() {
|
||||
static size_t bytes = HWY_DYNAMIC_DISPATCH(GetVectorSize)();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
#endif // HWY_ONCE
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_SIMD_H_
|
||||
#define LIB_JPEGLI_SIMD_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
// Returns SIMD vector size in bytes.
|
||||
size_t VectorSize();
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_SIMD_H_
|
||||
@@ -1,96 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/decode.h"
|
||||
#include "lib/jpegli/error.h"
|
||||
#include "lib/jpegli/memory_manager.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void init_mem_source(j_decompress_ptr cinfo) {}
|
||||
void init_stdio_source(j_decompress_ptr cinfo) {}
|
||||
|
||||
void skip_input_data(j_decompress_ptr cinfo, long num_bytes /* NOLINT */) {
|
||||
if (num_bytes <= 0) return;
|
||||
while (num_bytes >
|
||||
static_cast<long>(cinfo->src->bytes_in_buffer)) { // NOLINT
|
||||
num_bytes -= cinfo->src->bytes_in_buffer;
|
||||
(*cinfo->src->fill_input_buffer)(cinfo);
|
||||
}
|
||||
cinfo->src->next_input_byte += num_bytes;
|
||||
cinfo->src->bytes_in_buffer -= num_bytes;
|
||||
}
|
||||
|
||||
void term_source(j_decompress_ptr cinfo) {}
|
||||
|
||||
boolean EmitFakeEoiMarker(j_decompress_ptr cinfo) {
|
||||
static constexpr uint8_t kFakeEoiMarker[2] = {0xff, 0xd9};
|
||||
cinfo->src->next_input_byte = kFakeEoiMarker;
|
||||
cinfo->src->bytes_in_buffer = 2;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
constexpr size_t kStdioBufferSize = 64 << 10;
|
||||
|
||||
struct StdioSourceManager {
|
||||
jpeg_source_mgr pub;
|
||||
FILE* f;
|
||||
uint8_t* buffer;
|
||||
|
||||
static boolean fill_input_buffer(j_decompress_ptr cinfo) {
|
||||
auto* src = reinterpret_cast<StdioSourceManager*>(cinfo->src);
|
||||
size_t num_bytes_read = fread(src->buffer, 1, kStdioBufferSize, src->f);
|
||||
if (num_bytes_read == 0) {
|
||||
return EmitFakeEoiMarker(cinfo);
|
||||
}
|
||||
src->pub.next_input_byte = src->buffer;
|
||||
src->pub.bytes_in_buffer = num_bytes_read;
|
||||
return TRUE;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
void jpegli_mem_src(j_decompress_ptr cinfo, const unsigned char* inbuffer,
|
||||
unsigned long insize /* NOLINT */) {
|
||||
if (cinfo->src && cinfo->src->init_source != jpegli::init_mem_source) {
|
||||
JPEGLI_ERROR("jpegli_mem_src: a different source manager was already set");
|
||||
}
|
||||
if (!cinfo->src) {
|
||||
cinfo->src = jpegli::Allocate<jpeg_source_mgr>(cinfo, 1);
|
||||
}
|
||||
cinfo->src->next_input_byte = inbuffer;
|
||||
cinfo->src->bytes_in_buffer = insize;
|
||||
cinfo->src->init_source = jpegli::init_mem_source;
|
||||
cinfo->src->fill_input_buffer = jpegli::EmitFakeEoiMarker;
|
||||
cinfo->src->skip_input_data = jpegli::skip_input_data;
|
||||
cinfo->src->resync_to_restart = jpegli_resync_to_restart;
|
||||
cinfo->src->term_source = jpegli::term_source;
|
||||
}
|
||||
|
||||
void jpegli_stdio_src(j_decompress_ptr cinfo, FILE* infile) {
|
||||
if (cinfo->src && cinfo->src->init_source != jpegli::init_stdio_source) {
|
||||
JPEGLI_ERROR("jpeg_stdio_src: a different source manager was already set");
|
||||
}
|
||||
if (!cinfo->src) {
|
||||
cinfo->src = reinterpret_cast<jpeg_source_mgr*>(
|
||||
jpegli::Allocate<jpegli::StdioSourceManager>(cinfo, 1));
|
||||
}
|
||||
auto* src = reinterpret_cast<jpegli::StdioSourceManager*>(cinfo->src);
|
||||
src->f = infile;
|
||||
src->buffer = jpegli::Allocate<uint8_t>(cinfo, jpegli::kStdioBufferSize);
|
||||
src->pub.next_input_byte = src->buffer;
|
||||
src->pub.bytes_in_buffer = 0;
|
||||
src->pub.init_source = jpegli::init_stdio_source;
|
||||
src->pub.fill_input_buffer = jpegli::StdioSourceManager::fill_input_buffer;
|
||||
src->pub.skip_input_data = jpegli::skip_input_data;
|
||||
src->pub.resync_to_restart = jpegli_resync_to_restart;
|
||||
src->pub.term_source = jpegli::term_source;
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/decode.h"
|
||||
#include "lib/jpegli/libjpeg_test_util.h"
|
||||
#include "lib/jpegli/test_params.h"
|
||||
#include "lib/jpegli/test_utils.h"
|
||||
#include "lib/jpegli/testing.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jpegli {
|
||||
namespace {
|
||||
|
||||
void ReadOutputImage(j_decompress_ptr cinfo, TestImage* output) {
|
||||
jpegli_read_header(cinfo, /*require_image=*/TRUE);
|
||||
jpegli_start_decompress(cinfo);
|
||||
output->ysize = cinfo->output_height;
|
||||
output->xsize = cinfo->output_width;
|
||||
output->components = cinfo->num_components;
|
||||
output->AllocatePixels();
|
||||
size_t stride = cinfo->output_width * cinfo->num_components;
|
||||
while (cinfo->output_scanline < cinfo->output_height) {
|
||||
JSAMPROW scanline = &output->pixels[cinfo->output_scanline * stride];
|
||||
jpegli_read_scanlines(cinfo, &scanline, 1);
|
||||
}
|
||||
jpegli_finish_decompress(cinfo);
|
||||
}
|
||||
|
||||
struct TestConfig {
|
||||
std::string fn;
|
||||
std::string fn_desc;
|
||||
DecompressParams dparams;
|
||||
};
|
||||
|
||||
class SourceManagerTestParam : public ::testing::TestWithParam<TestConfig> {};
|
||||
|
||||
namespace {
|
||||
FILE* MemOpen(const std::vector<uint8_t>& data) {
|
||||
FILE* src = tmpfile();
|
||||
if (!src) return nullptr;
|
||||
fwrite(data.data(), 1, data.size(), src);
|
||||
fseek(src, 0, SEEK_SET);
|
||||
return src;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_P(SourceManagerTestParam, TestStdioSourceManager) {
|
||||
TestConfig config = GetParam();
|
||||
JXL_ASSIGN_OR_QUIT(std::vector<uint8_t> compressed, ReadTestData(config.fn),
|
||||
"Failed to read test data.");
|
||||
if (config.dparams.size_factor < 1.0) {
|
||||
compressed.resize(compressed.size() * config.dparams.size_factor);
|
||||
}
|
||||
FILE* src = MemOpen(compressed);
|
||||
ASSERT_TRUE(src);
|
||||
TestImage output0;
|
||||
jpeg_decompress_struct cinfo;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_decompress(&cinfo);
|
||||
jpegli_stdio_src(&cinfo, src);
|
||||
ReadOutputImage(&cinfo, &output0);
|
||||
return true;
|
||||
};
|
||||
bool ok = try_catch_block();
|
||||
fclose(src);
|
||||
ASSERT_TRUE(ok);
|
||||
jpegli_destroy_decompress(&cinfo);
|
||||
|
||||
TestImage output1;
|
||||
DecodeWithLibjpeg(CompressParams(), DecompressParams(), compressed, &output1);
|
||||
VerifyOutputImage(output1, output0, 1.0f);
|
||||
}
|
||||
|
||||
TEST_P(SourceManagerTestParam, TestMemSourceManager) {
|
||||
TestConfig config = GetParam();
|
||||
JXL_ASSIGN_OR_QUIT(std::vector<uint8_t> compressed, ReadTestData(config.fn),
|
||||
"Failed to read test data.");
|
||||
if (config.dparams.size_factor < 1.0f) {
|
||||
compressed.resize(compressed.size() * config.dparams.size_factor);
|
||||
}
|
||||
TestImage output0;
|
||||
jpeg_decompress_struct cinfo;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_decompress(&cinfo);
|
||||
jpegli_mem_src(&cinfo, compressed.data(), compressed.size());
|
||||
ReadOutputImage(&cinfo, &output0);
|
||||
return true;
|
||||
};
|
||||
ASSERT_TRUE(try_catch_block());
|
||||
jpegli_destroy_decompress(&cinfo);
|
||||
|
||||
TestImage output1;
|
||||
DecodeWithLibjpeg(CompressParams(), DecompressParams(), compressed, &output1);
|
||||
VerifyOutputImage(output1, output0, 1.0f);
|
||||
}
|
||||
|
||||
std::vector<TestConfig> GenerateTests() {
|
||||
std::vector<TestConfig> all_tests;
|
||||
{
|
||||
std::vector<std::pair<std::string, std::string>> testfiles({
|
||||
{"jxl/flower/flower.png.im_q85_444.jpg", "Q85YUV444"},
|
||||
{"jxl/flower/flower.png.im_q85_420.jpg", "Q85YUV420"},
|
||||
{"jxl/flower/flower.png.im_q85_420_R13B.jpg", "Q85YUV420R13B"},
|
||||
});
|
||||
for (const auto& it : testfiles) {
|
||||
for (float size_factor : {0.1f, 0.33f, 0.5f, 0.75f}) {
|
||||
TestConfig config;
|
||||
config.fn = it.first;
|
||||
config.fn_desc = it.second;
|
||||
config.dparams.size_factor = size_factor;
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
return all_tests;
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const TestConfig& c) {
|
||||
os << c.fn_desc;
|
||||
if (c.dparams.size_factor < 1.0f) {
|
||||
os << "Partial" << static_cast<int>(c.dparams.size_factor * 100) << "p";
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string TestDescription(
|
||||
const testing::TestParamInfo<SourceManagerTestParam::ParamType>& info) {
|
||||
std::stringstream name;
|
||||
name << info.param;
|
||||
return name.str();
|
||||
}
|
||||
|
||||
JPEGLI_INSTANTIATE_TEST_SUITE_P(SourceManagerTest, SourceManagerTestParam,
|
||||
testing::ValuesIn(GenerateTests()),
|
||||
TestDescription);
|
||||
|
||||
} // namespace
|
||||
} // namespace jpegli
|
||||
@@ -1,246 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/decode.h"
|
||||
#include "lib/jpegli/encode.h"
|
||||
#include "lib/jpegli/test_params.h"
|
||||
#include "lib/jpegli/test_utils.h"
|
||||
#include "lib/jpegli/testing.h"
|
||||
|
||||
namespace jpegli {
|
||||
namespace {
|
||||
|
||||
// A simple suspending source manager with an input buffer.
|
||||
struct SourceManager {
|
||||
jpeg_source_mgr pub;
|
||||
std::vector<uint8_t> buffer;
|
||||
|
||||
SourceManager() {
|
||||
pub.next_input_byte = nullptr;
|
||||
pub.bytes_in_buffer = 0;
|
||||
pub.init_source = init_source;
|
||||
pub.fill_input_buffer = fill_input_buffer;
|
||||
pub.skip_input_data = skip_input_data;
|
||||
pub.resync_to_restart = jpegli_resync_to_restart;
|
||||
pub.term_source = term_source;
|
||||
}
|
||||
|
||||
static void init_source(j_decompress_ptr cinfo) {}
|
||||
static boolean fill_input_buffer(j_decompress_ptr cinfo) { return FALSE; }
|
||||
static void skip_input_data(j_decompress_ptr cinfo,
|
||||
long num_bytes /* NOLINT */) {}
|
||||
static void term_source(j_decompress_ptr cinfo) {}
|
||||
};
|
||||
|
||||
// A destination manager that empties its output buffer into a SourceManager's
|
||||
// input buffer. The buffer size is kept short because empty_output_buffer() is
|
||||
// called only when the output buffer is full, and we want to update the decoder
|
||||
// input frequently to demonstrate that streaming works.
|
||||
constexpr size_t kOutputBufferSize = 1024;
|
||||
struct DestinationManager {
|
||||
jpeg_destination_mgr pub;
|
||||
std::vector<uint8_t> buffer;
|
||||
SourceManager* dest;
|
||||
|
||||
explicit DestinationManager(SourceManager* src)
|
||||
: buffer(kOutputBufferSize), dest(src) {
|
||||
pub.next_output_byte = buffer.data();
|
||||
pub.free_in_buffer = buffer.size();
|
||||
pub.init_destination = init_destination;
|
||||
pub.empty_output_buffer = empty_output_buffer;
|
||||
pub.term_destination = term_destination;
|
||||
}
|
||||
|
||||
static void init_destination(j_compress_ptr cinfo) {}
|
||||
|
||||
static boolean empty_output_buffer(j_compress_ptr cinfo) {
|
||||
auto* us = reinterpret_cast<DestinationManager*>(cinfo->dest);
|
||||
jpeg_destination_mgr* src = &us->pub;
|
||||
jpeg_source_mgr* dst = &us->dest->pub;
|
||||
std::vector<uint8_t>& src_buf = us->buffer;
|
||||
std::vector<uint8_t>& dst_buf = us->dest->buffer;
|
||||
if (dst->bytes_in_buffer > 0 && dst->bytes_in_buffer < dst_buf.size()) {
|
||||
memmove(dst_buf.data(), dst->next_input_byte, dst->bytes_in_buffer);
|
||||
}
|
||||
size_t src_len = src_buf.size() - src->free_in_buffer;
|
||||
dst_buf.resize(dst->bytes_in_buffer + src_len);
|
||||
memcpy(&dst_buf[dst->bytes_in_buffer], src_buf.data(), src_len);
|
||||
dst->next_input_byte = dst_buf.data();
|
||||
dst->bytes_in_buffer = dst_buf.size();
|
||||
src->next_output_byte = src_buf.data();
|
||||
src->free_in_buffer = src_buf.size();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void term_destination(j_compress_ptr cinfo) {
|
||||
empty_output_buffer(cinfo);
|
||||
}
|
||||
};
|
||||
|
||||
struct TestConfig {
|
||||
TestImage input;
|
||||
CompressParams jparams;
|
||||
};
|
||||
|
||||
class StreamingTestParam : public ::testing::TestWithParam<TestConfig> {};
|
||||
|
||||
TEST_P(StreamingTestParam, TestStreaming) {
|
||||
jpeg_decompress_struct dinfo = {};
|
||||
jpeg_compress_struct cinfo = {};
|
||||
SourceManager src;
|
||||
TestConfig config = GetParam();
|
||||
TestImage& input = config.input;
|
||||
TestImage output;
|
||||
GeneratePixels(&input);
|
||||
const auto try_catch_block = [&]() {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
dinfo.err = cinfo.err;
|
||||
dinfo.client_data = cinfo.client_data;
|
||||
// Create a pair of compressor and decompressor objects, where the
|
||||
// compressor's output is connected to the decompressor's input.
|
||||
jpegli_create_decompress(&dinfo);
|
||||
jpegli_create_compress(&cinfo);
|
||||
dinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src);
|
||||
DestinationManager dest(&src);
|
||||
cinfo.dest = reinterpret_cast<jpeg_destination_mgr*>(&dest);
|
||||
|
||||
cinfo.image_width = input.xsize;
|
||||
cinfo.image_height = input.ysize;
|
||||
cinfo.input_components = input.components;
|
||||
cinfo.in_color_space = static_cast<J_COLOR_SPACE>(input.color_space);
|
||||
jpegli_set_defaults(&cinfo);
|
||||
cinfo.comp_info[0].v_samp_factor = config.jparams.v_sampling[0];
|
||||
jpegli_set_progressive_level(&cinfo, 0);
|
||||
cinfo.optimize_coding = FALSE;
|
||||
jpegli_start_compress(&cinfo, TRUE);
|
||||
|
||||
size_t stride = cinfo.image_width * cinfo.input_components;
|
||||
size_t iMCU_height = 8 * cinfo.max_v_samp_factor;
|
||||
std::vector<uint8_t> row_bytes(iMCU_height * stride);
|
||||
size_t y_in = 0;
|
||||
size_t y_out = 0;
|
||||
while (y_in < cinfo.image_height) {
|
||||
// Feed one iMCU row at a time to the compressor.
|
||||
size_t lines_in = std::min(iMCU_height, cinfo.image_height - y_in);
|
||||
memcpy(row_bytes.data(), &input.pixels[y_in * stride], lines_in * stride);
|
||||
std::vector<JSAMPROW> rows_in(lines_in);
|
||||
for (size_t i = 0; i < lines_in; ++i) {
|
||||
rows_in[i] = &row_bytes[i * stride];
|
||||
}
|
||||
EXPECT_EQ(lines_in,
|
||||
jpegli_write_scanlines(&cinfo, rows_in.data(), lines_in));
|
||||
y_in += lines_in;
|
||||
if (y_in == cinfo.image_height) {
|
||||
jpegli_finish_compress(&cinfo);
|
||||
}
|
||||
|
||||
// After the first iMCU row, we don't yet expect any output because the
|
||||
// compressor delays processing to have context rows after the iMCU row.
|
||||
if (y_in < std::min<size_t>(2 * iMCU_height, cinfo.image_height)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// After two iMCU rows, the compressor has started emitting compressed
|
||||
// data. We check here that at least the scan header was output, because
|
||||
// we expect that the compressor's output buffer was filled at least once
|
||||
// while emitting the first compressed iMCU row.
|
||||
if (y_in == std::min<size_t>(2 * iMCU_height, cinfo.image_height)) {
|
||||
EXPECT_EQ(JPEG_REACHED_SOS,
|
||||
jpegli_read_header(&dinfo, /*require_image=*/TRUE));
|
||||
output.xsize = dinfo.image_width;
|
||||
output.ysize = dinfo.image_height;
|
||||
output.components = dinfo.num_components;
|
||||
EXPECT_EQ(output.xsize, input.xsize);
|
||||
EXPECT_EQ(output.ysize, input.ysize);
|
||||
EXPECT_EQ(output.components, input.components);
|
||||
EXPECT_TRUE(jpegli_start_decompress(&dinfo));
|
||||
output.pixels.resize(output.ysize * stride);
|
||||
if (y_in < cinfo.image_height) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// After six iMCU rows, the compressor has emitted five iMCU rows of
|
||||
// compressed data, of which we expect four full iMCU row of compressed
|
||||
// data to be in the decoder's input buffer, but since the decoder also
|
||||
// needs context rows for upsampling and smoothing, we don't expect any
|
||||
// output to be ready yet.
|
||||
if (y_in < 7 * iMCU_height && y_in < cinfo.image_height) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// After five iMCU rows, we expect the decoder to have rendered the output
|
||||
// with four iMCU rows of delay.
|
||||
// TODO(szabadka) Reduce the processing delay in the decoder if possible.
|
||||
size_t lines_out =
|
||||
(y_in == cinfo.image_height ? cinfo.image_height - y_out
|
||||
: iMCU_height);
|
||||
std::vector<JSAMPROW> rows_out(lines_out);
|
||||
for (size_t i = 0; i < lines_out; ++i) {
|
||||
rows_out[i] =
|
||||
reinterpret_cast<JSAMPLE*>(&output.pixels[(y_out + i) * stride]);
|
||||
}
|
||||
EXPECT_EQ(lines_out,
|
||||
jpegli_read_scanlines(&dinfo, rows_out.data(), lines_out));
|
||||
VerifyOutputImage(input, output, y_out, lines_out, 3.8f);
|
||||
y_out += lines_out;
|
||||
|
||||
if (y_out == cinfo.image_height) {
|
||||
EXPECT_TRUE(jpegli_finish_decompress(&dinfo));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
EXPECT_TRUE(try_catch_block());
|
||||
jpegli_destroy_decompress(&dinfo);
|
||||
jpegli_destroy_compress(&cinfo);
|
||||
}
|
||||
|
||||
std::vector<TestConfig> GenerateTests() {
|
||||
std::vector<TestConfig> all_tests;
|
||||
const size_t xsize0 = 1920;
|
||||
const size_t ysize0 = 1080;
|
||||
for (int dysize : {0, 1, 8, 9}) {
|
||||
for (int v_sampling : {1, 2}) {
|
||||
TestConfig config;
|
||||
config.input.xsize = xsize0;
|
||||
config.input.ysize = ysize0 + dysize;
|
||||
config.jparams.h_sampling = {1, 1, 1};
|
||||
config.jparams.v_sampling = {v_sampling, 1, 1};
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
return all_tests;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const TestConfig& c) {
|
||||
os << c.input;
|
||||
os << c.jparams;
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string TestDescription(
|
||||
const testing::TestParamInfo<StreamingTestParam::ParamType>& info) {
|
||||
std::stringstream name;
|
||||
name << info.param;
|
||||
return name.str();
|
||||
}
|
||||
|
||||
JPEGLI_INSTANTIATE_TEST_SUITE_P(StreamingTest, StreamingTestParam,
|
||||
testing::ValuesIn(GenerateTests()),
|
||||
TestDescription);
|
||||
|
||||
} // namespace
|
||||
} // namespace jpegli
|
||||
@@ -1,163 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_TEST_PARAMS_H_
|
||||
#define LIB_JPEGLI_TEST_PARAMS_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/types.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
// We define this here as well to make sure that the *_api_test.cc tests only
|
||||
// use the public API and therefore we don't include any *_internal.h headers.
|
||||
template <typename T1, typename T2>
|
||||
constexpr inline T1 DivCeil(T1 a, T2 b) {
|
||||
return (a + b - 1) / b;
|
||||
}
|
||||
|
||||
#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
|
||||
|
||||
static constexpr int kLastScan = 0xffff;
|
||||
|
||||
static uint32_t kTestColorMap[] = {
|
||||
0x000000, 0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0x00ffff,
|
||||
0xff00ff, 0xffffff, 0x6251fc, 0x45d9c7, 0xa7f059, 0xd9a945,
|
||||
0xfa4e44, 0xceaffc, 0xbad7db, 0xc1f0b1, 0xdbca9a, 0xfacac5,
|
||||
0xf201ff, 0x0063db, 0x00f01c, 0xdbb204, 0xf12f0c, 0x7ba1dc};
|
||||
static constexpr int kTestColorMapNumColors = ARRAY_SIZE(kTestColorMap);
|
||||
|
||||
static constexpr int kSpecialMarker0 = 0xe5;
|
||||
static constexpr int kSpecialMarker1 = 0xe9;
|
||||
static constexpr uint8_t kMarkerData[] = {0, 1, 255, 0, 17};
|
||||
static constexpr uint8_t kMarkerSequence[] = {0xe6, 0xe8, 0xe7,
|
||||
0xe6, 0xe7, 0xe8};
|
||||
static constexpr size_t kMarkerSequenceLen = ARRAY_SIZE(kMarkerSequence);
|
||||
|
||||
enum JpegIOMode {
|
||||
PIXELS,
|
||||
RAW_DATA,
|
||||
COEFFICIENTS,
|
||||
};
|
||||
|
||||
struct CustomQuantTable {
|
||||
int slot_idx = 0;
|
||||
uint16_t table_type = 0;
|
||||
int scale_factor = 100;
|
||||
bool add_raw = false;
|
||||
bool force_baseline = true;
|
||||
std::vector<unsigned int> basic_table;
|
||||
std::vector<unsigned int> quantval;
|
||||
void Generate();
|
||||
};
|
||||
|
||||
struct TestImage {
|
||||
size_t xsize = 2268;
|
||||
size_t ysize = 1512;
|
||||
int color_space = 2; // JCS_RGB
|
||||
size_t components = 3;
|
||||
JpegliDataType data_type = JPEGLI_TYPE_UINT8;
|
||||
JpegliEndianness endianness = JPEGLI_NATIVE_ENDIAN;
|
||||
std::vector<uint8_t> pixels;
|
||||
std::vector<std::vector<uint8_t>> raw_data;
|
||||
std::vector<std::vector<int16_t>> coeffs;
|
||||
void AllocatePixels() {
|
||||
pixels.resize(ysize * xsize * components *
|
||||
jpegli_bytes_per_sample(data_type));
|
||||
}
|
||||
void Clear() {
|
||||
pixels.clear();
|
||||
raw_data.clear();
|
||||
coeffs.clear();
|
||||
}
|
||||
};
|
||||
|
||||
struct CompressParams {
|
||||
int quality = 90;
|
||||
bool set_jpeg_colorspace = false;
|
||||
int jpeg_color_space = 0; // JCS_UNKNOWN
|
||||
std::vector<int> quant_indexes;
|
||||
std::vector<CustomQuantTable> quant_tables;
|
||||
std::vector<int> h_sampling;
|
||||
std::vector<int> v_sampling;
|
||||
std::vector<int> comp_ids;
|
||||
int override_JFIF = -1;
|
||||
int override_Adobe = -1;
|
||||
bool add_marker = false;
|
||||
bool simple_progression = false;
|
||||
// -1 is library default
|
||||
// 0, 1, 2 is set through jpegli_set_progressive_level()
|
||||
// 2 + N is kScriptN
|
||||
int progressive_mode = -1;
|
||||
unsigned int restart_interval = 0;
|
||||
int restart_in_rows = 0;
|
||||
int smoothing_factor = 0;
|
||||
int optimize_coding = -1;
|
||||
bool use_flat_dc_luma_code = false;
|
||||
bool omit_standard_tables = false;
|
||||
bool xyb_mode = false;
|
||||
bool libjpeg_mode = false;
|
||||
bool use_adaptive_quantization = true;
|
||||
std::vector<uint8_t> icc;
|
||||
|
||||
int h_samp(int c) const { return h_sampling.empty() ? 1 : h_sampling[c]; }
|
||||
int v_samp(int c) const { return v_sampling.empty() ? 1 : v_sampling[c]; }
|
||||
int max_h_sample() const {
|
||||
auto it = std::max_element(h_sampling.begin(), h_sampling.end());
|
||||
return it == h_sampling.end() ? 1 : *it;
|
||||
}
|
||||
int max_v_sample() const {
|
||||
auto it = std::max_element(v_sampling.begin(), v_sampling.end());
|
||||
return it == v_sampling.end() ? 1 : *it;
|
||||
}
|
||||
int comp_width(const TestImage& input, int c) const {
|
||||
return DivCeil(input.xsize * h_samp(c), max_h_sample() * 8) * 8;
|
||||
}
|
||||
int comp_height(const TestImage& input, int c) const {
|
||||
return DivCeil(input.ysize * v_samp(c), max_v_sample() * 8) * 8;
|
||||
}
|
||||
};
|
||||
|
||||
enum ColorQuantMode {
|
||||
CQUANT_1PASS,
|
||||
CQUANT_2PASS,
|
||||
CQUANT_EXTERNAL,
|
||||
CQUANT_REUSE,
|
||||
};
|
||||
|
||||
struct ScanDecompressParams {
|
||||
int max_scan_number;
|
||||
int dither_mode;
|
||||
ColorQuantMode color_quant_mode;
|
||||
};
|
||||
|
||||
struct DecompressParams {
|
||||
float size_factor = 1.0f;
|
||||
size_t chunk_size = 65536;
|
||||
size_t max_output_lines = 16;
|
||||
JpegIOMode output_mode = PIXELS;
|
||||
JpegliDataType data_type = JPEGLI_TYPE_UINT8;
|
||||
JpegliEndianness endianness = JPEGLI_NATIVE_ENDIAN;
|
||||
bool set_out_color_space = false;
|
||||
int out_color_space = 0; // JCS_UNKNOWN
|
||||
bool crop_output = false;
|
||||
bool do_block_smoothing = false;
|
||||
bool do_fancy_upsampling = true;
|
||||
bool skip_scans = false;
|
||||
int scale_num = 1;
|
||||
int scale_denom = 1;
|
||||
bool quantize_colors = false;
|
||||
int desired_number_of_colors = 256;
|
||||
std::vector<ScanDecompressParams> scan_params;
|
||||
};
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_TEST_PARAMS_H_
|
||||
@@ -1,486 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This template file is included in both the libjpeg_test_util.cc and the
|
||||
// test_utils.cc files with different JPEG_API_FN macros and possibly different
|
||||
// include paths for the jpeg headers.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/test_params.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/sanitizers.h"
|
||||
|
||||
#if defined(JPEG_API_FLAVOUR_JPEGLI)
|
||||
#include "lib/jpegli/decode.h"
|
||||
#define JPEG_API_FN(name) jpegli_##name
|
||||
#else
|
||||
#define JPEG_API_FN(name) jpeg_##name
|
||||
#endif // JPEG_API_FLAVOUR_JPEGLI
|
||||
|
||||
namespace J_TEST_UTILS {
|
||||
|
||||
using ::jpegli::CompressParams;
|
||||
using ::jpegli::CQUANT_1PASS;
|
||||
using ::jpegli::CQUANT_2PASS;
|
||||
using ::jpegli::CQUANT_EXTERNAL;
|
||||
using ::jpegli::CQUANT_REUSE;
|
||||
using ::jpegli::DecompressParams;
|
||||
using ::jpegli::DivCeil;
|
||||
using ::jpegli::kLastScan;
|
||||
using ::jpegli::kMarkerData;
|
||||
using ::jpegli::kSpecialMarker0;
|
||||
using ::jpegli::kSpecialMarker1;
|
||||
using ::jpegli::kTestColorMap;
|
||||
using ::jpegli::kTestColorMapNumColors;
|
||||
using ::jpegli::RAW_DATA;
|
||||
using ::jpegli::ScanDecompressParams;
|
||||
using ::jpegli::TestImage;
|
||||
|
||||
#if defined(JPEG_API_FLAVOUR_JPEGLI)
|
||||
using ::jpegli::Check;
|
||||
#else
|
||||
namespace {
|
||||
void Check(bool ok) {
|
||||
if (!ok) {
|
||||
JXL_CRASH();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
// Sequential non-interleaved.
|
||||
constexpr jpeg_scan_info kScript1[] = {
|
||||
{1, {0}, 0, 63, 0, 0},
|
||||
{1, {1}, 0, 63, 0, 0},
|
||||
{1, {2}, 0, 63, 0, 0},
|
||||
};
|
||||
// Sequential partially interleaved, chroma first.
|
||||
constexpr jpeg_scan_info kScript2[] = {
|
||||
{2, {1, 2}, 0, 63, 0, 0},
|
||||
{1, {0}, 0, 63, 0, 0},
|
||||
};
|
||||
|
||||
// Rest of the scan scripts are progressive.
|
||||
|
||||
constexpr jpeg_scan_info kScript3[] = {
|
||||
// Interleaved full DC.
|
||||
{3, {0, 1, 2}, 0, 0, 0, 0},
|
||||
// Full AC scans.
|
||||
{1, {0}, 1, 63, 0, 0},
|
||||
{1, {1}, 1, 63, 0, 0},
|
||||
{1, {2}, 1, 63, 0, 0},
|
||||
};
|
||||
constexpr jpeg_scan_info kScript4[] = {
|
||||
// Non-interleaved full DC.
|
||||
{1, {0}, 0, 0, 0, 0},
|
||||
{1, {1}, 0, 0, 0, 0},
|
||||
{1, {2}, 0, 0, 0, 0},
|
||||
// Full AC scans.
|
||||
{1, {0}, 1, 63, 0, 0},
|
||||
{1, {1}, 1, 63, 0, 0},
|
||||
{1, {2}, 1, 63, 0, 0},
|
||||
};
|
||||
constexpr jpeg_scan_info kScript5[] = {
|
||||
// Partially interleaved full DC, chroma first.
|
||||
{2, {1, 2}, 0, 0, 0, 0},
|
||||
{1, {0}, 0, 0, 0, 0},
|
||||
// AC shifted by 1 bit.
|
||||
{1, {0}, 1, 63, 0, 1},
|
||||
{1, {1}, 1, 63, 0, 1},
|
||||
{1, {2}, 1, 63, 0, 1},
|
||||
// AC refinement scan.
|
||||
{1, {0}, 1, 63, 1, 0},
|
||||
{1, {1}, 1, 63, 1, 0},
|
||||
{1, {2}, 1, 63, 1, 0},
|
||||
};
|
||||
constexpr jpeg_scan_info kScript6[] = {
|
||||
// Interleaved DC shifted by 2 bits.
|
||||
{3, {0, 1, 2}, 0, 0, 0, 2},
|
||||
// Interleaved DC refinement scans.
|
||||
{3, {0, 1, 2}, 0, 0, 2, 1},
|
||||
{3, {0, 1, 2}, 0, 0, 1, 0},
|
||||
// Full AC scans.
|
||||
{1, {0}, 1, 63, 0, 0},
|
||||
{1, {1}, 1, 63, 0, 0},
|
||||
{1, {2}, 1, 63, 0, 0},
|
||||
};
|
||||
|
||||
constexpr jpeg_scan_info kScript7[] = {
|
||||
// Non-interleaved DC shifted by 2 bits.
|
||||
{1, {0}, 0, 0, 0, 2},
|
||||
{1, {1}, 0, 0, 0, 2},
|
||||
{1, {2}, 0, 0, 0, 2},
|
||||
// Non-interleaved DC first refinement scans.
|
||||
{1, {0}, 0, 0, 2, 1},
|
||||
{1, {1}, 0, 0, 2, 1},
|
||||
{1, {2}, 0, 0, 2, 1},
|
||||
// Non-interleaved DC second refinement scans.
|
||||
{1, {0}, 0, 0, 1, 0},
|
||||
{1, {1}, 0, 0, 1, 0},
|
||||
{1, {2}, 0, 0, 1, 0},
|
||||
// Full AC scans.
|
||||
{1, {0}, 1, 63, 0, 0},
|
||||
{1, {1}, 1, 63, 0, 0},
|
||||
{1, {2}, 1, 63, 0, 0},
|
||||
};
|
||||
|
||||
constexpr jpeg_scan_info kScript8[] = {
|
||||
// Partially interleaved DC shifted by 2 bits, chroma first
|
||||
{2, {1, 2}, 0, 0, 0, 2},
|
||||
{1, {0}, 0, 0, 0, 2},
|
||||
// Partially interleaved DC first refinement scans.
|
||||
{2, {0, 2}, 0, 0, 2, 1},
|
||||
{1, {1}, 0, 0, 2, 1},
|
||||
// Partially interleaved DC first refinement scans, chroma first.
|
||||
{2, {1, 2}, 0, 0, 1, 0},
|
||||
{1, {0}, 0, 0, 1, 0},
|
||||
// Full AC scans.
|
||||
{1, {0}, 1, 63, 0, 0},
|
||||
{1, {1}, 1, 63, 0, 0},
|
||||
{1, {2}, 1, 63, 0, 0},
|
||||
};
|
||||
|
||||
constexpr jpeg_scan_info kScript9[] = {
|
||||
// Interleaved full DC.
|
||||
{3, {0, 1, 2}, 0, 0, 0, 0},
|
||||
// AC scans for component 0
|
||||
// shifted by 1 bit, two spectral ranges
|
||||
{1, {0}, 1, 6, 0, 1},
|
||||
{1, {0}, 7, 63, 0, 1},
|
||||
// refinement scan, full
|
||||
{1, {0}, 1, 63, 1, 0},
|
||||
// AC scans for component 1
|
||||
// shifted by 1 bit, full
|
||||
{1, {1}, 1, 63, 0, 1},
|
||||
// refinement scan, two spectral ranges
|
||||
{1, {1}, 1, 6, 1, 0},
|
||||
{1, {1}, 7, 63, 1, 0},
|
||||
// AC scans for component 2
|
||||
// shifted by 1 bit, two spectral ranges
|
||||
{1, {2}, 1, 6, 0, 1},
|
||||
{1, {2}, 7, 63, 0, 1},
|
||||
// refinement scan, two spectral ranges (but different from above)
|
||||
{1, {2}, 1, 16, 1, 0},
|
||||
{1, {2}, 17, 63, 1, 0},
|
||||
};
|
||||
|
||||
constexpr jpeg_scan_info kScript10[] = {
|
||||
// Interleaved full DC.
|
||||
{3, {0, 1, 2}, 0, 0, 0, 0},
|
||||
// AC scans for spectral range 1..16
|
||||
// shifted by 1
|
||||
{1, {0}, 1, 16, 0, 1},
|
||||
{1, {1}, 1, 16, 0, 1},
|
||||
{1, {2}, 1, 16, 0, 1},
|
||||
// refinement scans, two sub-ranges
|
||||
{1, {0}, 1, 8, 1, 0},
|
||||
{1, {0}, 9, 16, 1, 0},
|
||||
{1, {1}, 1, 8, 1, 0},
|
||||
{1, {1}, 9, 16, 1, 0},
|
||||
{1, {2}, 1, 8, 1, 0},
|
||||
{1, {2}, 9, 16, 1, 0},
|
||||
// AC scans for spectral range 17..63
|
||||
{1, {0}, 17, 63, 0, 1},
|
||||
{1, {1}, 17, 63, 0, 1},
|
||||
{1, {2}, 17, 63, 0, 1},
|
||||
// refinement scans, two sub-ranges
|
||||
{1, {0}, 17, 28, 1, 0},
|
||||
{1, {0}, 29, 63, 1, 0},
|
||||
{1, {1}, 17, 28, 1, 0},
|
||||
{1, {1}, 29, 63, 1, 0},
|
||||
{1, {2}, 17, 28, 1, 0},
|
||||
{1, {2}, 29, 63, 1, 0},
|
||||
};
|
||||
|
||||
struct ScanScript {
|
||||
int num_scans;
|
||||
const jpeg_scan_info* scans;
|
||||
};
|
||||
|
||||
constexpr ScanScript kTestScript[] = {
|
||||
{ARRAY_SIZE(kScript1), kScript1}, {ARRAY_SIZE(kScript2), kScript2},
|
||||
{ARRAY_SIZE(kScript3), kScript3}, {ARRAY_SIZE(kScript4), kScript4},
|
||||
{ARRAY_SIZE(kScript5), kScript5}, {ARRAY_SIZE(kScript6), kScript6},
|
||||
{ARRAY_SIZE(kScript7), kScript7}, {ARRAY_SIZE(kScript8), kScript8},
|
||||
{ARRAY_SIZE(kScript9), kScript9}, {ARRAY_SIZE(kScript10), kScript10},
|
||||
};
|
||||
constexpr int kNumTestScripts = ARRAY_SIZE(kTestScript);
|
||||
|
||||
void SetScanDecompressParams(const DecompressParams& dparams,
|
||||
j_decompress_ptr cinfo, int scan_number) {
|
||||
const ScanDecompressParams* sparams = nullptr;
|
||||
for (const auto& sp : dparams.scan_params) {
|
||||
if (scan_number <= sp.max_scan_number) {
|
||||
sparams = &sp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sparams == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (dparams.quantize_colors) {
|
||||
cinfo->dither_mode = static_cast<J_DITHER_MODE>(sparams->dither_mode);
|
||||
if (sparams->color_quant_mode == CQUANT_1PASS) {
|
||||
cinfo->two_pass_quantize = FALSE;
|
||||
cinfo->colormap = nullptr;
|
||||
} else if (sparams->color_quant_mode == CQUANT_2PASS) {
|
||||
Check(cinfo->out_color_space == JCS_RGB);
|
||||
cinfo->two_pass_quantize = TRUE;
|
||||
cinfo->colormap = nullptr;
|
||||
} else if (sparams->color_quant_mode == CQUANT_EXTERNAL) {
|
||||
Check(cinfo->out_color_space == JCS_RGB);
|
||||
cinfo->two_pass_quantize = FALSE;
|
||||
bool have_colormap = cinfo->colormap != nullptr;
|
||||
cinfo->actual_number_of_colors = kTestColorMapNumColors;
|
||||
cinfo->colormap = (*cinfo->mem->alloc_sarray)(
|
||||
reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE,
|
||||
cinfo->actual_number_of_colors, 3);
|
||||
jxl::msan::UnpoisonMemory(reinterpret_cast<void*>(cinfo->colormap),
|
||||
3 * sizeof(JSAMPLE*));
|
||||
for (int i = 0; i < kTestColorMapNumColors; ++i) {
|
||||
cinfo->colormap[0][i] = (kTestColorMap[i] >> 16) & 0xff;
|
||||
cinfo->colormap[1][i] = (kTestColorMap[i] >> 8) & 0xff;
|
||||
cinfo->colormap[2][i] = (kTestColorMap[i] >> 0) & 0xff;
|
||||
}
|
||||
if (have_colormap) {
|
||||
JPEG_API_FN(new_colormap)(cinfo);
|
||||
}
|
||||
} else if (sparams->color_quant_mode == CQUANT_REUSE) {
|
||||
Check(cinfo->out_color_space == JCS_RGB);
|
||||
Check(cinfo->colormap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetDecompressParams(const DecompressParams& dparams,
|
||||
j_decompress_ptr cinfo) {
|
||||
cinfo->do_block_smoothing = dparams.do_block_smoothing ? 1 : 0;
|
||||
cinfo->do_fancy_upsampling = dparams.do_fancy_upsampling ? 1 : 0;
|
||||
if (dparams.output_mode == RAW_DATA) {
|
||||
cinfo->raw_data_out = TRUE;
|
||||
}
|
||||
if (dparams.set_out_color_space) {
|
||||
cinfo->out_color_space =
|
||||
static_cast<J_COLOR_SPACE>(dparams.out_color_space);
|
||||
if (dparams.out_color_space == JCS_UNKNOWN) {
|
||||
cinfo->jpeg_color_space = JCS_UNKNOWN;
|
||||
}
|
||||
}
|
||||
cinfo->scale_num = dparams.scale_num;
|
||||
cinfo->scale_denom = dparams.scale_denom;
|
||||
cinfo->quantize_colors = dparams.quantize_colors ? 1 : 0;
|
||||
cinfo->desired_number_of_colors = dparams.desired_number_of_colors;
|
||||
if (!dparams.scan_params.empty()) {
|
||||
if (cinfo->buffered_image) {
|
||||
for (const auto& sparams : dparams.scan_params) {
|
||||
if (sparams.color_quant_mode == CQUANT_1PASS) {
|
||||
cinfo->enable_1pass_quant = TRUE;
|
||||
} else if (sparams.color_quant_mode == CQUANT_2PASS) {
|
||||
cinfo->enable_2pass_quant = TRUE;
|
||||
} else if (sparams.color_quant_mode == CQUANT_EXTERNAL) {
|
||||
cinfo->enable_external_quant = TRUE;
|
||||
}
|
||||
}
|
||||
J_TEST_UTILS::SetScanDecompressParams(dparams, cinfo, 1);
|
||||
} else {
|
||||
J_TEST_UTILS::SetScanDecompressParams(dparams, cinfo, kLastScan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheckMarkerPresent(j_decompress_ptr cinfo, uint8_t marker_type) {
|
||||
bool marker_found = false;
|
||||
for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr;
|
||||
marker = marker->next) {
|
||||
jxl::msan::UnpoisonMemory(marker, sizeof(*marker));
|
||||
jxl::msan::UnpoisonMemory(marker->data, marker->data_length);
|
||||
if (marker->marker == marker_type &&
|
||||
marker->data_length == sizeof(kMarkerData) &&
|
||||
memcmp(marker->data, kMarkerData, sizeof(kMarkerData)) == 0) {
|
||||
marker_found = true;
|
||||
}
|
||||
}
|
||||
Check(marker_found);
|
||||
}
|
||||
|
||||
void VerifyHeader(const CompressParams& jparams, j_decompress_ptr cinfo) {
|
||||
if (jparams.set_jpeg_colorspace) {
|
||||
Check(cinfo->jpeg_color_space == jparams.jpeg_color_space);
|
||||
}
|
||||
if (jparams.override_JFIF >= 0) {
|
||||
Check(cinfo->saw_JFIF_marker == jparams.override_JFIF);
|
||||
}
|
||||
if (jparams.override_Adobe >= 0) {
|
||||
Check(cinfo->saw_Adobe_marker == jparams.override_Adobe);
|
||||
}
|
||||
if (jparams.add_marker) {
|
||||
CheckMarkerPresent(cinfo, kSpecialMarker0);
|
||||
CheckMarkerPresent(cinfo, kSpecialMarker1);
|
||||
}
|
||||
jxl::msan::UnpoisonMemory(
|
||||
cinfo->comp_info, cinfo->num_components * sizeof(cinfo->comp_info[0]));
|
||||
int max_h_samp_factor = 1;
|
||||
int max_v_samp_factor = 1;
|
||||
for (int i = 0; i < cinfo->num_components; ++i) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[i];
|
||||
if (!jparams.comp_ids.empty()) {
|
||||
Check(comp->component_id == jparams.comp_ids[i]);
|
||||
}
|
||||
if (!jparams.h_sampling.empty()) {
|
||||
Check(comp->h_samp_factor == jparams.h_sampling[i]);
|
||||
}
|
||||
if (!jparams.v_sampling.empty()) {
|
||||
Check(comp->v_samp_factor == jparams.v_sampling[i]);
|
||||
}
|
||||
if (!jparams.quant_indexes.empty()) {
|
||||
Check(comp->quant_tbl_no == jparams.quant_indexes[i]);
|
||||
}
|
||||
max_h_samp_factor = std::max(max_h_samp_factor, comp->h_samp_factor);
|
||||
max_v_samp_factor = std::max(max_v_samp_factor, comp->v_samp_factor);
|
||||
}
|
||||
Check(max_h_samp_factor == cinfo->max_h_samp_factor);
|
||||
Check(max_v_samp_factor == cinfo->max_v_samp_factor);
|
||||
int referenced_tables[NUM_QUANT_TBLS] = {};
|
||||
for (int i = 0; i < cinfo->num_components; ++i) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[i];
|
||||
Check(comp->width_in_blocks ==
|
||||
DivCeil(cinfo->image_width * comp->h_samp_factor,
|
||||
max_h_samp_factor * DCTSIZE));
|
||||
Check(comp->height_in_blocks ==
|
||||
DivCeil(cinfo->image_height * comp->v_samp_factor,
|
||||
max_v_samp_factor * DCTSIZE));
|
||||
referenced_tables[comp->quant_tbl_no] = 1;
|
||||
}
|
||||
for (const auto& table : jparams.quant_tables) {
|
||||
JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[table.slot_idx];
|
||||
if (!referenced_tables[table.slot_idx]) {
|
||||
Check(quant_table == nullptr);
|
||||
continue;
|
||||
}
|
||||
Check(quant_table != nullptr);
|
||||
jxl::msan::UnpoisonMemory(quant_table, sizeof(*quant_table));
|
||||
for (int k = 0; k < DCTSIZE2; ++k) {
|
||||
Check(quant_table->quantval[k] == table.quantval[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyScanHeader(const CompressParams& jparams, j_decompress_ptr cinfo) {
|
||||
Check(cinfo->input_scan_number > 0);
|
||||
if (cinfo->progressive_mode) {
|
||||
Check(cinfo->Ss != 0 || cinfo->Se != 63);
|
||||
} else {
|
||||
Check(cinfo->Ss == 0 && cinfo->Se == 63);
|
||||
}
|
||||
if (jparams.progressive_mode > 2) {
|
||||
Check(jparams.progressive_mode < 3 + kNumTestScripts);
|
||||
const ScanScript& script = kTestScript[jparams.progressive_mode - 3];
|
||||
Check(cinfo->input_scan_number <= script.num_scans);
|
||||
const jpeg_scan_info& scan = script.scans[cinfo->input_scan_number - 1];
|
||||
Check(cinfo->comps_in_scan == scan.comps_in_scan);
|
||||
for (int i = 0; i < cinfo->comps_in_scan; ++i) {
|
||||
Check(cinfo->cur_comp_info[i]->component_index ==
|
||||
scan.component_index[i]);
|
||||
}
|
||||
Check(cinfo->Ss == scan.Ss);
|
||||
Check(cinfo->Se == scan.Se);
|
||||
Check(cinfo->Ah == scan.Ah);
|
||||
Check(cinfo->Al == scan.Al);
|
||||
}
|
||||
if (jparams.restart_interval > 0) {
|
||||
Check(cinfo->restart_interval == jparams.restart_interval);
|
||||
} else if (jparams.restart_in_rows > 0) {
|
||||
Check(cinfo->restart_interval ==
|
||||
jparams.restart_in_rows * cinfo->MCUs_per_row);
|
||||
}
|
||||
if (jparams.progressive_mode == 0 && jparams.optimize_coding == 0) {
|
||||
if (cinfo->jpeg_color_space == JCS_RGB) {
|
||||
Check(cinfo->comp_info[0].dc_tbl_no == 0);
|
||||
Check(cinfo->comp_info[1].dc_tbl_no == 0);
|
||||
Check(cinfo->comp_info[2].dc_tbl_no == 0);
|
||||
Check(cinfo->comp_info[0].ac_tbl_no == 0);
|
||||
Check(cinfo->comp_info[1].ac_tbl_no == 0);
|
||||
Check(cinfo->comp_info[2].ac_tbl_no == 0);
|
||||
} else if (cinfo->jpeg_color_space == JCS_YCbCr) {
|
||||
Check(cinfo->comp_info[0].dc_tbl_no == 0);
|
||||
Check(cinfo->comp_info[1].dc_tbl_no == 1);
|
||||
Check(cinfo->comp_info[2].dc_tbl_no == 1);
|
||||
Check(cinfo->comp_info[0].ac_tbl_no == 0);
|
||||
Check(cinfo->comp_info[1].ac_tbl_no == 1);
|
||||
Check(cinfo->comp_info[2].ac_tbl_no == 1);
|
||||
} else if (cinfo->jpeg_color_space == JCS_CMYK) {
|
||||
Check(cinfo->comp_info[0].dc_tbl_no == 0);
|
||||
Check(cinfo->comp_info[1].dc_tbl_no == 0);
|
||||
Check(cinfo->comp_info[2].dc_tbl_no == 0);
|
||||
Check(cinfo->comp_info[3].dc_tbl_no == 0);
|
||||
Check(cinfo->comp_info[0].ac_tbl_no == 0);
|
||||
Check(cinfo->comp_info[1].ac_tbl_no == 0);
|
||||
Check(cinfo->comp_info[2].ac_tbl_no == 0);
|
||||
Check(cinfo->comp_info[3].ac_tbl_no == 0);
|
||||
} else if (cinfo->jpeg_color_space == JCS_YCCK) {
|
||||
Check(cinfo->comp_info[0].dc_tbl_no == 0);
|
||||
Check(cinfo->comp_info[1].dc_tbl_no == 1);
|
||||
Check(cinfo->comp_info[2].dc_tbl_no == 1);
|
||||
Check(cinfo->comp_info[3].dc_tbl_no == 0);
|
||||
Check(cinfo->comp_info[0].ac_tbl_no == 0);
|
||||
Check(cinfo->comp_info[1].ac_tbl_no == 1);
|
||||
Check(cinfo->comp_info[2].ac_tbl_no == 1);
|
||||
Check(cinfo->comp_info[3].ac_tbl_no == 0);
|
||||
}
|
||||
if (jparams.use_flat_dc_luma_code) {
|
||||
JHUFF_TBL* tbl = cinfo->dc_huff_tbl_ptrs[0];
|
||||
jxl::msan::UnpoisonMemory(tbl, sizeof(*tbl));
|
||||
for (int i = 0; i < 15; ++i) {
|
||||
Check(tbl->huffval[i] == i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UnmapColors(uint8_t* row, size_t xsize, int components,
|
||||
JSAMPARRAY colormap, size_t num_colors) {
|
||||
Check(colormap != nullptr);
|
||||
std::vector<uint8_t> tmp(xsize * components);
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
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());
|
||||
}
|
||||
|
||||
void CopyCoefficients(j_decompress_ptr cinfo, jvirt_barray_ptr* coef_arrays,
|
||||
TestImage* output) {
|
||||
output->xsize = cinfo->image_width;
|
||||
output->ysize = cinfo->image_height;
|
||||
output->components = cinfo->num_components;
|
||||
output->color_space = cinfo->out_color_space;
|
||||
j_common_ptr comptr = reinterpret_cast<j_common_ptr>(cinfo);
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
jpeg_component_info* comp = &cinfo->comp_info[c];
|
||||
std::vector<JCOEF> coeffs(comp->width_in_blocks * comp->height_in_blocks *
|
||||
DCTSIZE2);
|
||||
for (size_t by = 0; by < comp->height_in_blocks; ++by) {
|
||||
JBLOCKARRAY blocks = (*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(&coeffs[offset], blocks[0], stride);
|
||||
}
|
||||
output->coeffs.emplace_back(std::move(coeffs));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace J_TEST_UTILS
|
||||
|
||||
#undef JPEG_API_FN
|
||||
@@ -1,879 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/test_utils.h"
|
||||
|
||||
#include <jxl/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <ios>
|
||||
#include <iterator>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/encode.h"
|
||||
#include "lib/jpegli/test_params.h"
|
||||
#include "lib/jpegli/types.h"
|
||||
#include "lib/jxl/base/byte_order.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
#if !defined(TEST_DATA_PATH)
|
||||
#include "tools/cpp/runfiles/runfiles.h"
|
||||
#endif
|
||||
|
||||
#define J_TEST_UTILS jpegli
|
||||
#define JPEG_API_FLAVOUR_JPEGLI
|
||||
#include "lib/jpegli/test_utils-inl.h"
|
||||
#undef JPEG_API_FLAVOUR_JPEGLI
|
||||
#undef J_TEST_UTILS
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void Check(bool ok) {
|
||||
if (!ok) {
|
||||
JXL_CRASH();
|
||||
}
|
||||
}
|
||||
#define QUIT(M) Check(false);
|
||||
|
||||
#if defined(TEST_DATA_PATH)
|
||||
std::string GetTestDataPath(const std::string& filename) {
|
||||
return std::string(TEST_DATA_PATH "/") + filename;
|
||||
}
|
||||
#else
|
||||
using ::bazel::tools::cpp::runfiles::Runfiles;
|
||||
const std::unique_ptr<Runfiles> kRunfiles(Runfiles::Create(""));
|
||||
std::string GetTestDataPath(const std::string& filename) {
|
||||
std::string root(JPEGXL_ROOT_PACKAGE "/testdata/");
|
||||
return kRunfiles->Rlocation(root + filename);
|
||||
}
|
||||
#endif
|
||||
|
||||
::jxl::StatusOr<std::vector<uint8_t>> ReadTestData(
|
||||
const std::string& filename) {
|
||||
std::vector<uint8_t> data;
|
||||
std::string full_path = GetTestDataPath(filename);
|
||||
fprintf(stderr, "ReadTestData %s\n", full_path.c_str());
|
||||
std::ifstream file(full_path, std::ios::binary);
|
||||
std::vector<char> str((std::istreambuf_iterator<char>(file)),
|
||||
std::istreambuf_iterator<char>());
|
||||
JXL_ENSURE(file.good());
|
||||
const uint8_t* raw = reinterpret_cast<const uint8_t*>(str.data());
|
||||
data = std::vector<uint8_t>(raw, raw + str.size());
|
||||
printf("Test data %s is %d bytes long.\n", filename.c_str(),
|
||||
static_cast<int>(data.size()));
|
||||
return data;
|
||||
}
|
||||
|
||||
void CustomQuantTable::Generate() {
|
||||
basic_table.resize(DCTSIZE2);
|
||||
quantval.resize(DCTSIZE2);
|
||||
switch (table_type) {
|
||||
case 0: {
|
||||
for (int k = 0; k < DCTSIZE2; ++k) {
|
||||
basic_table[k] = k + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
for (int k = 0; k < DCTSIZE2; ++k) {
|
||||
basic_table[k] = table_type;
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < DCTSIZE2; ++k) {
|
||||
quantval[k] = (basic_table[k] * scale_factor + 50U) / 100U;
|
||||
quantval[k] = std::max(quantval[k], 1U);
|
||||
quantval[k] = std::min(quantval[k], 65535U);
|
||||
if (!add_raw) {
|
||||
quantval[k] = std::min(quantval[k], force_baseline ? 255U : 32767U);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PNMParser::ParseHeader(const uint8_t** pos, size_t* xsize, size_t* ysize,
|
||||
size_t* num_channels, size_t* bitdepth) {
|
||||
if (pos_[0] != 'P' || (pos_[1] != '5' && pos_[1] != '6')) {
|
||||
fprintf(stderr, "Invalid PNM header.");
|
||||
return false;
|
||||
}
|
||||
*num_channels = (pos_[1] == '5' ? 1 : 3);
|
||||
pos_ += 2;
|
||||
|
||||
size_t maxval;
|
||||
if (!SkipWhitespace() || !ParseUnsigned(xsize) || !SkipWhitespace() ||
|
||||
!ParseUnsigned(ysize) || !SkipWhitespace() || !ParseUnsigned(&maxval) ||
|
||||
!SkipWhitespace()) {
|
||||
return false;
|
||||
}
|
||||
if (maxval == 0 || maxval >= 65536) {
|
||||
fprintf(stderr, "Invalid maxval value.\n");
|
||||
return false;
|
||||
}
|
||||
bool found_bitdepth = false;
|
||||
for (int bits = 1; bits <= 16; ++bits) {
|
||||
if (maxval == (1u << bits) - 1) {
|
||||
*bitdepth = bits;
|
||||
found_bitdepth = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found_bitdepth) {
|
||||
fprintf(stderr, "Invalid maxval value.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
*pos = pos_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PNMParser::ParseUnsigned(size_t* number) {
|
||||
if (pos_ == end_ || *pos_ < '0' || *pos_ > '9') {
|
||||
fprintf(stderr, "Expected unsigned number.\n");
|
||||
return false;
|
||||
}
|
||||
*number = 0;
|
||||
while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
|
||||
*number *= 10;
|
||||
*number += *pos_ - '0';
|
||||
++pos_;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PNMParser::SkipWhitespace() {
|
||||
if (pos_ == end_ || !IsWhitespace(*pos_)) {
|
||||
fprintf(stderr, "Expected whitespace.\n");
|
||||
return false;
|
||||
}
|
||||
while (pos_ < end_ && IsWhitespace(*pos_)) {
|
||||
++pos_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadPNM(const std::vector<uint8_t>& data, size_t* xsize, size_t* ysize,
|
||||
size_t* num_channels, size_t* bitdepth,
|
||||
std::vector<uint8_t>* pixels) {
|
||||
if (data.size() < 2) {
|
||||
fprintf(stderr, "PNM file too small.\n");
|
||||
return false;
|
||||
}
|
||||
PNMParser parser(data.data(), data.size());
|
||||
const uint8_t* pos = nullptr;
|
||||
if (!parser.ParseHeader(&pos, xsize, ysize, num_channels, bitdepth)) {
|
||||
return false;
|
||||
}
|
||||
pixels->resize(data.data() + data.size() - pos);
|
||||
memcpy(pixels->data(), pos, pixels->size());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string ColorSpaceName(J_COLOR_SPACE colorspace) {
|
||||
switch (colorspace) {
|
||||
case JCS_UNKNOWN:
|
||||
return "UNKNOWN";
|
||||
case JCS_GRAYSCALE:
|
||||
return "GRAYSCALE";
|
||||
case JCS_RGB:
|
||||
return "RGB";
|
||||
case JCS_YCbCr:
|
||||
return "YCbCr";
|
||||
case JCS_CMYK:
|
||||
return "CMYK";
|
||||
case JCS_YCCK:
|
||||
return "YCCK";
|
||||
case JCS_EXT_RGB:
|
||||
return "EXT_RGB";
|
||||
case JCS_EXT_BGR:
|
||||
return "EXT_BGR";
|
||||
case JCS_EXT_RGBA:
|
||||
return "EXT_RGBA";
|
||||
case JCS_EXT_BGRA:
|
||||
return "EXT_BGRA";
|
||||
case JCS_EXT_ARGB:
|
||||
return "EXT_ARGB";
|
||||
case JCS_EXT_ABGR:
|
||||
return "EXT_ABGR";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::string IOMethodName(JpegliDataType data_type,
|
||||
JpegliEndianness endianness) {
|
||||
std::string retval;
|
||||
if (data_type == JPEGLI_TYPE_UINT8) {
|
||||
return "";
|
||||
} else if (data_type == JPEGLI_TYPE_UINT16) {
|
||||
retval = "UINT16";
|
||||
} else if (data_type == JPEGLI_TYPE_FLOAT) {
|
||||
retval = "FLOAT";
|
||||
}
|
||||
if (endianness == JPEGLI_LITTLE_ENDIAN) {
|
||||
retval += "LE";
|
||||
} else if (endianness == JPEGLI_BIG_ENDIAN) {
|
||||
retval += "BE";
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
std::string SamplingId(const CompressParams& jparams) {
|
||||
std::stringstream os;
|
||||
Check(jparams.h_sampling.size() == jparams.v_sampling.size());
|
||||
if (!jparams.h_sampling.empty()) {
|
||||
size_t len = jparams.h_sampling.size();
|
||||
while (len > 1 && jparams.h_sampling[len - 1] == 1 &&
|
||||
jparams.v_sampling[len - 1] == 1) {
|
||||
--len;
|
||||
}
|
||||
os << "SAMP";
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
if (i > 0) os << "_";
|
||||
os << jparams.h_sampling[i] << "x" << jparams.v_sampling[i];
|
||||
}
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const TestImage& input) {
|
||||
os << input.xsize << "x" << input.ysize;
|
||||
os << IOMethodName(input.data_type, input.endianness);
|
||||
if (input.color_space != JCS_RGB) {
|
||||
os << "InputColor"
|
||||
<< ColorSpaceName(static_cast<J_COLOR_SPACE>(input.color_space));
|
||||
}
|
||||
if (input.color_space == JCS_UNKNOWN) {
|
||||
os << input.components;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const CompressParams& jparams) {
|
||||
os << "Q" << jparams.quality;
|
||||
os << SamplingId(jparams);
|
||||
if (jparams.set_jpeg_colorspace) {
|
||||
os << "JpegColor"
|
||||
<< ColorSpaceName(static_cast<J_COLOR_SPACE>(jparams.jpeg_color_space));
|
||||
}
|
||||
if (!jparams.comp_ids.empty()) {
|
||||
os << "CID";
|
||||
for (int cid : jparams.comp_ids) {
|
||||
os << cid;
|
||||
}
|
||||
}
|
||||
if (!jparams.quant_indexes.empty()) {
|
||||
os << "QIDX";
|
||||
for (int qi : jparams.quant_indexes) {
|
||||
os << qi;
|
||||
}
|
||||
for (const auto& table : jparams.quant_tables) {
|
||||
os << "TABLE" << table.slot_idx << "T" << table.table_type << "F"
|
||||
<< table.scale_factor
|
||||
<< (table.add_raw ? "R"
|
||||
: table.force_baseline ? "B"
|
||||
: "");
|
||||
}
|
||||
}
|
||||
if (jparams.progressive_mode >= 0) {
|
||||
os << "P" << jparams.progressive_mode;
|
||||
} else if (jparams.simple_progression) {
|
||||
os << "Psimple";
|
||||
}
|
||||
if (jparams.optimize_coding == 1) {
|
||||
os << "OptimizedCode";
|
||||
} else if (jparams.optimize_coding == 0) {
|
||||
os << "FixedCode";
|
||||
if (jparams.use_flat_dc_luma_code) {
|
||||
os << "FlatDCLuma";
|
||||
} else if (jparams.omit_standard_tables) {
|
||||
os << "OmitDHT";
|
||||
}
|
||||
}
|
||||
if (!jparams.use_adaptive_quantization) {
|
||||
os << "NoAQ";
|
||||
}
|
||||
if (jparams.restart_interval > 0) {
|
||||
os << "R" << jparams.restart_interval;
|
||||
}
|
||||
if (jparams.restart_in_rows > 0) {
|
||||
os << "RR" << jparams.restart_in_rows;
|
||||
}
|
||||
if (jparams.xyb_mode) {
|
||||
os << "XYB";
|
||||
} else if (jparams.libjpeg_mode) {
|
||||
os << "Libjpeg";
|
||||
}
|
||||
if (jparams.override_JFIF >= 0) {
|
||||
os << (jparams.override_JFIF ? "AddJFIF" : "NoJFIF");
|
||||
}
|
||||
if (jparams.override_Adobe >= 0) {
|
||||
os << (jparams.override_Adobe ? "AddAdobe" : "NoAdobe");
|
||||
}
|
||||
if (jparams.add_marker) {
|
||||
os << "AddMarker";
|
||||
}
|
||||
if (!jparams.icc.empty()) {
|
||||
os << "ICCSize" << jparams.icc.size();
|
||||
}
|
||||
if (jparams.smoothing_factor != 0) {
|
||||
os << "SF" << jparams.smoothing_factor;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
::jxl::Status SetNumChannels(J_COLOR_SPACE colorspace, size_t* channels) {
|
||||
if (colorspace == JCS_GRAYSCALE) {
|
||||
*channels = 1;
|
||||
} else if (colorspace == JCS_RGB || colorspace == JCS_YCbCr ||
|
||||
colorspace == JCS_EXT_RGB || colorspace == JCS_EXT_BGR) {
|
||||
*channels = 3;
|
||||
} else if (colorspace == JCS_CMYK || colorspace == JCS_YCCK ||
|
||||
colorspace == JCS_EXT_RGBA || colorspace == JCS_EXT_BGRA ||
|
||||
colorspace == JCS_EXT_ARGB || colorspace == JCS_EXT_ABGR) {
|
||||
*channels = 4;
|
||||
} else if (colorspace == JCS_UNKNOWN) {
|
||||
JXL_ENSURE(*channels <= 4);
|
||||
} else {
|
||||
return JXL_FAILURE("Unsupported colorspace: %d",
|
||||
static_cast<int>(colorspace));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RGBToYCbCr(float r, float g, float b, float* y, float* cb, float* cr) {
|
||||
*y = 0.299f * r + 0.587f * g + 0.114f * b;
|
||||
*cb = -0.168736f * r - 0.331264f * g + 0.5f * b + 0.5f;
|
||||
*cr = 0.5f * r - 0.418688f * g - 0.081312f * b + 0.5f;
|
||||
}
|
||||
|
||||
void ConvertPixel(const uint8_t* input_rgb, uint8_t* out,
|
||||
J_COLOR_SPACE colorspace, size_t num_channels,
|
||||
JpegliDataType data_type = JPEGLI_TYPE_UINT8,
|
||||
JXL_BOOL swap_endianness = JPEGLI_NATIVE_ENDIAN) {
|
||||
const float kMul = 255.0f;
|
||||
float r = input_rgb[0] / kMul;
|
||||
float g = input_rgb[1] / kMul;
|
||||
float b = input_rgb[2] / kMul;
|
||||
uint8_t out8[MAX_COMPONENTS];
|
||||
if (colorspace == JCS_GRAYSCALE) {
|
||||
const float Y = 0.299f * r + 0.587f * g + 0.114f * b;
|
||||
out8[0] = static_cast<uint8_t>(std::round(Y * kMul));
|
||||
} else if (colorspace == JCS_RGB || colorspace == JCS_EXT_RGB ||
|
||||
colorspace == JCS_EXT_RGBA) {
|
||||
out8[0] = input_rgb[0];
|
||||
out8[1] = input_rgb[1];
|
||||
out8[2] = input_rgb[2];
|
||||
if (colorspace == JCS_EXT_RGBA) out8[3] = 255;
|
||||
} else if (colorspace == JCS_EXT_BGR || colorspace == JCS_EXT_BGRA) {
|
||||
out8[2] = input_rgb[0];
|
||||
out8[1] = input_rgb[1];
|
||||
out8[0] = input_rgb[2];
|
||||
if (colorspace == JCS_EXT_BGRA) out8[3] = 255;
|
||||
} else if (colorspace == JCS_EXT_ABGR) {
|
||||
out8[0] = 255;
|
||||
out8[3] = input_rgb[0];
|
||||
out8[2] = input_rgb[1];
|
||||
out8[1] = input_rgb[2];
|
||||
} else if (colorspace == JCS_EXT_ARGB) {
|
||||
out8[0] = 255;
|
||||
out8[1] = input_rgb[0];
|
||||
out8[2] = input_rgb[1];
|
||||
out8[3] = input_rgb[2];
|
||||
} else if (colorspace == JCS_UNKNOWN) {
|
||||
for (size_t c = 0; c < num_channels; ++c) {
|
||||
out8[c] = input_rgb[std::min<size_t>(2, c)];
|
||||
}
|
||||
} else if (colorspace == JCS_YCbCr) {
|
||||
float Y;
|
||||
float Cb;
|
||||
float Cr;
|
||||
RGBToYCbCr(r, g, b, &Y, &Cb, &Cr);
|
||||
out8[0] = static_cast<uint8_t>(std::round(Y * kMul));
|
||||
out8[1] = static_cast<uint8_t>(std::round(Cb * kMul));
|
||||
out8[2] = static_cast<uint8_t>(std::round(Cr * kMul));
|
||||
} else if (colorspace == JCS_CMYK || colorspace == JCS_YCCK) {
|
||||
float K = 1.0f - std::max(r, std::max(g, b));
|
||||
float scaleK = 1.0f / (1.0f - K);
|
||||
r *= scaleK;
|
||||
g *= scaleK;
|
||||
b *= scaleK;
|
||||
if (colorspace == JCS_CMYK) {
|
||||
out8[0] = static_cast<uint8_t>(std::round((1.0f - r) * kMul));
|
||||
out8[1] = static_cast<uint8_t>(std::round((1.0f - g) * kMul));
|
||||
out8[2] = static_cast<uint8_t>(std::round((1.0f - b) * kMul));
|
||||
} else if (colorspace == JCS_YCCK) {
|
||||
float Y;
|
||||
float Cb;
|
||||
float Cr;
|
||||
RGBToYCbCr(r, g, b, &Y, &Cb, &Cr);
|
||||
out8[0] = static_cast<uint8_t>(std::round(Y * kMul));
|
||||
out8[1] = static_cast<uint8_t>(std::round(Cb * kMul));
|
||||
out8[2] = static_cast<uint8_t>(std::round(Cr * kMul));
|
||||
}
|
||||
out8[3] = static_cast<uint8_t>(std::round(K * kMul));
|
||||
} else {
|
||||
Check(false);
|
||||
}
|
||||
if (data_type == JPEGLI_TYPE_UINT8) {
|
||||
memcpy(out, out8, num_channels);
|
||||
} else if (data_type == JPEGLI_TYPE_UINT16) {
|
||||
for (size_t c = 0; c < num_channels; ++c) {
|
||||
uint16_t val = (out8[c] << 8) + out8[c];
|
||||
val |= 0x40; // Make little-endian and big-endian asymmetric
|
||||
if (swap_endianness) {
|
||||
val = JXL_BSWAP16(val);
|
||||
}
|
||||
memcpy(&out[sizeof(val) * c], &val, sizeof(val));
|
||||
}
|
||||
} else if (data_type == JPEGLI_TYPE_FLOAT) {
|
||||
for (size_t c = 0; c < num_channels; ++c) {
|
||||
float val = out8[c] / 255.0f;
|
||||
if (swap_endianness) {
|
||||
val = BSwapFloat(val);
|
||||
}
|
||||
memcpy(&out[sizeof(val) * c], &val, sizeof(val));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConvertToGrayscale(TestImage* img) {
|
||||
if (img->color_space == JCS_GRAYSCALE) return;
|
||||
Check(img->data_type == JPEGLI_TYPE_UINT8);
|
||||
bool rgb_pre_alpha =
|
||||
img->color_space == JCS_EXT_ARGB || img->color_space == JCS_EXT_ABGR;
|
||||
bool rgb_post_alpha =
|
||||
img->color_space == JCS_EXT_RGBA || img->color_space == JCS_EXT_BGRA;
|
||||
bool rgb_alpha = rgb_pre_alpha || rgb_post_alpha;
|
||||
bool is_rgb = img->color_space == JCS_RGB ||
|
||||
img->color_space == JCS_EXT_RGB ||
|
||||
img->color_space == JCS_EXT_BGR || rgb_alpha;
|
||||
bool switch_br = img->color_space == JCS_EXT_BGR ||
|
||||
img->color_space == JCS_EXT_ABGR ||
|
||||
img->color_space == JCS_EXT_BGRA;
|
||||
size_t stride = rgb_alpha ? 4 : 3;
|
||||
size_t offset = rgb_pre_alpha ? 1 : 0;
|
||||
for (size_t i = offset; i < img->pixels.size(); i += stride) {
|
||||
if (is_rgb) {
|
||||
if (switch_br) std::swap(img->pixels[i], img->pixels[i + 2]);
|
||||
ConvertPixel(&img->pixels[i], &img->pixels[i / stride], JCS_GRAYSCALE, 1);
|
||||
} else if (img->color_space == JCS_YCbCr) {
|
||||
img->pixels[i / 3] = img->pixels[i];
|
||||
}
|
||||
}
|
||||
img->pixels.resize(img->pixels.size() / 3);
|
||||
img->color_space = JCS_GRAYSCALE;
|
||||
img->components = 1;
|
||||
}
|
||||
|
||||
void GeneratePixels(TestImage* img) {
|
||||
JXL_ASSIGN_OR_QUIT(std::vector<uint8_t> imgdata,
|
||||
ReadTestData("jxl/flower/flower.pnm"),
|
||||
"Failed to read test data");
|
||||
size_t xsize;
|
||||
size_t ysize;
|
||||
size_t channels;
|
||||
size_t bitdepth;
|
||||
std::vector<uint8_t> pixels;
|
||||
Check(ReadPNM(imgdata, &xsize, &ysize, &channels, &bitdepth, &pixels));
|
||||
if (img->xsize == 0) img->xsize = xsize;
|
||||
if (img->ysize == 0) img->ysize = ysize;
|
||||
Check(img->xsize <= xsize);
|
||||
Check(img->ysize <= ysize);
|
||||
Check(3 == channels);
|
||||
Check(8 == bitdepth);
|
||||
size_t in_bytes_per_pixel = channels;
|
||||
size_t in_stride = xsize * in_bytes_per_pixel;
|
||||
size_t x0 = (xsize - img->xsize) / 2;
|
||||
size_t y0 = (ysize - img->ysize) / 2;
|
||||
Check(SetNumChannels(static_cast<J_COLOR_SPACE>(img->color_space),
|
||||
&img->components));
|
||||
size_t out_bytes_per_pixel =
|
||||
jpegli_bytes_per_sample(img->data_type) * img->components;
|
||||
size_t out_stride = img->xsize * out_bytes_per_pixel;
|
||||
bool swap_endianness =
|
||||
(img->endianness == JPEGLI_LITTLE_ENDIAN && !IsLittleEndian()) ||
|
||||
(img->endianness == JPEGLI_BIG_ENDIAN && IsLittleEndian());
|
||||
img->pixels.resize(img->ysize * out_stride);
|
||||
for (size_t iy = 0; iy < img->ysize; ++iy) {
|
||||
size_t y = y0 + iy;
|
||||
for (size_t ix = 0; ix < img->xsize; ++ix) {
|
||||
size_t x = x0 + ix;
|
||||
size_t idx_in = y * in_stride + x * in_bytes_per_pixel;
|
||||
size_t idx_out = iy * out_stride + ix * out_bytes_per_pixel;
|
||||
ConvertPixel(&pixels[idx_in], &img->pixels[idx_out],
|
||||
static_cast<J_COLOR_SPACE>(img->color_space),
|
||||
img->components, img->data_type,
|
||||
TO_JXL_BOOL(swap_endianness));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateRawData(const CompressParams& jparams, TestImage* img) {
|
||||
for (size_t c = 0; c < img->components; ++c) {
|
||||
size_t xsize = jparams.comp_width(*img, c);
|
||||
size_t ysize = jparams.comp_height(*img, c);
|
||||
size_t factor_y = jparams.max_v_sample() / jparams.v_samp(c);
|
||||
size_t factor_x = jparams.max_h_sample() / jparams.h_samp(c);
|
||||
size_t factor = factor_x * factor_y;
|
||||
std::vector<uint8_t> plane(ysize * xsize);
|
||||
size_t bytes_per_pixel = img->components;
|
||||
for (size_t y = 0; y < ysize; ++y) {
|
||||
for (size_t x = 0; x < xsize; ++x) {
|
||||
int result = 0;
|
||||
for (size_t iy = 0; iy < factor_y; ++iy) {
|
||||
size_t yy = std::min(y * factor_y + iy, img->ysize - 1);
|
||||
for (size_t ix = 0; ix < factor_x; ++ix) {
|
||||
size_t xx = std::min(x * factor_x + ix, img->xsize - 1);
|
||||
size_t pixel_ix = (yy * img->xsize + xx) * bytes_per_pixel + c;
|
||||
result += img->pixels[pixel_ix];
|
||||
}
|
||||
}
|
||||
result = static_cast<uint8_t>((result + factor / 2) / factor);
|
||||
plane[y * xsize + x] = result;
|
||||
}
|
||||
}
|
||||
img->raw_data.emplace_back(std::move(plane));
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateCoeffs(const CompressParams& jparams, TestImage* img) {
|
||||
for (size_t c = 0; c < img->components; ++c) {
|
||||
int xsize_blocks = jparams.comp_width(*img, c) / DCTSIZE;
|
||||
int ysize_blocks = jparams.comp_height(*img, c) / DCTSIZE;
|
||||
std::vector<JCOEF> plane(ysize_blocks * xsize_blocks * DCTSIZE2);
|
||||
for (int by = 0; by < ysize_blocks; ++by) {
|
||||
for (int bx = 0; bx < xsize_blocks; ++bx) {
|
||||
JCOEF* block = &plane[(by * xsize_blocks + bx) * DCTSIZE2];
|
||||
for (int k = 0; k < DCTSIZE2; ++k) {
|
||||
block[k] = (bx - by) / (k + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
img->coeffs.emplace_back(std::move(plane));
|
||||
}
|
||||
}
|
||||
|
||||
void EncodeWithJpegli(const TestImage& input, const CompressParams& jparams,
|
||||
j_compress_ptr cinfo) {
|
||||
cinfo->image_width = input.xsize;
|
||||
cinfo->image_height = input.ysize;
|
||||
cinfo->input_components = input.components;
|
||||
if (jparams.xyb_mode) {
|
||||
jpegli_set_xyb_mode(cinfo);
|
||||
}
|
||||
if (jparams.libjpeg_mode) {
|
||||
jpegli_enable_adaptive_quantization(cinfo, FALSE);
|
||||
jpegli_use_standard_quant_tables(cinfo);
|
||||
jpegli_set_progressive_level(cinfo, 0);
|
||||
}
|
||||
jpegli_set_defaults(cinfo);
|
||||
cinfo->in_color_space = static_cast<J_COLOR_SPACE>(input.color_space);
|
||||
jpegli_default_colorspace(cinfo);
|
||||
if (jparams.override_JFIF >= 0) {
|
||||
cinfo->write_JFIF_header = jparams.override_JFIF;
|
||||
}
|
||||
if (jparams.override_Adobe >= 0) {
|
||||
cinfo->write_Adobe_marker = jparams.override_Adobe;
|
||||
}
|
||||
if (jparams.set_jpeg_colorspace) {
|
||||
jpegli_set_colorspace(cinfo,
|
||||
static_cast<J_COLOR_SPACE>(jparams.jpeg_color_space));
|
||||
}
|
||||
if (!jparams.comp_ids.empty()) {
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
cinfo->comp_info[c].component_id = jparams.comp_ids[c];
|
||||
}
|
||||
}
|
||||
if (!jparams.h_sampling.empty()) {
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
cinfo->comp_info[c].h_samp_factor = jparams.h_sampling[c];
|
||||
cinfo->comp_info[c].v_samp_factor = jparams.v_sampling[c];
|
||||
}
|
||||
}
|
||||
jpegli_set_quality(cinfo, jparams.quality, TRUE);
|
||||
if (!jparams.quant_indexes.empty()) {
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
cinfo->comp_info[c].quant_tbl_no = jparams.quant_indexes[c];
|
||||
}
|
||||
for (const auto& table : jparams.quant_tables) {
|
||||
if (table.add_raw) {
|
||||
cinfo->quant_tbl_ptrs[table.slot_idx] =
|
||||
jpegli_alloc_quant_table(reinterpret_cast<j_common_ptr>(cinfo));
|
||||
for (int k = 0; k < DCTSIZE2; ++k) {
|
||||
cinfo->quant_tbl_ptrs[table.slot_idx]->quantval[k] =
|
||||
table.quantval[k];
|
||||
}
|
||||
cinfo->quant_tbl_ptrs[table.slot_idx]->sent_table = FALSE;
|
||||
} else {
|
||||
jpegli_add_quant_table(cinfo, table.slot_idx, table.basic_table.data(),
|
||||
table.scale_factor,
|
||||
TO_JXL_BOOL(table.force_baseline));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (jparams.simple_progression) {
|
||||
jpegli_simple_progression(cinfo);
|
||||
Check(jparams.progressive_mode == -1);
|
||||
}
|
||||
if (jparams.progressive_mode > 2) {
|
||||
const ScanScript& script = kTestScript[jparams.progressive_mode - 3];
|
||||
cinfo->scan_info = script.scans;
|
||||
cinfo->num_scans = script.num_scans;
|
||||
} else if (jparams.progressive_mode >= 0) {
|
||||
jpegli_set_progressive_level(cinfo, jparams.progressive_mode);
|
||||
}
|
||||
jpegli_set_input_format(cinfo, input.data_type, input.endianness);
|
||||
jpegli_enable_adaptive_quantization(
|
||||
cinfo, TO_JXL_BOOL(jparams.use_adaptive_quantization));
|
||||
cinfo->restart_interval = jparams.restart_interval;
|
||||
cinfo->restart_in_rows = jparams.restart_in_rows;
|
||||
cinfo->smoothing_factor = jparams.smoothing_factor;
|
||||
if (jparams.optimize_coding == 1) {
|
||||
cinfo->optimize_coding = TRUE;
|
||||
} else if (jparams.optimize_coding == 0) {
|
||||
cinfo->optimize_coding = FALSE;
|
||||
}
|
||||
cinfo->raw_data_in = TO_JXL_BOOL(!input.raw_data.empty());
|
||||
if (jparams.optimize_coding == 0 && jparams.use_flat_dc_luma_code) {
|
||||
JHUFF_TBL* tbl = cinfo->dc_huff_tbl_ptrs[0];
|
||||
memset(tbl, 0, sizeof(*tbl));
|
||||
tbl->bits[4] = 15;
|
||||
for (int i = 0; i < 15; ++i) tbl->huffval[i] = i;
|
||||
}
|
||||
if (input.coeffs.empty()) {
|
||||
bool write_all_tables = TRUE;
|
||||
if (jparams.optimize_coding == 0 && !jparams.use_flat_dc_luma_code &&
|
||||
jparams.omit_standard_tables) {
|
||||
write_all_tables = FALSE;
|
||||
cinfo->dc_huff_tbl_ptrs[0]->sent_table = TRUE;
|
||||
cinfo->dc_huff_tbl_ptrs[1]->sent_table = TRUE;
|
||||
cinfo->ac_huff_tbl_ptrs[0]->sent_table = TRUE;
|
||||
cinfo->ac_huff_tbl_ptrs[1]->sent_table = TRUE;
|
||||
}
|
||||
jpegli_start_compress(cinfo, TO_JXL_BOOL(write_all_tables));
|
||||
if (jparams.add_marker) {
|
||||
jpegli_write_marker(cinfo, kSpecialMarker0, kMarkerData,
|
||||
sizeof(kMarkerData));
|
||||
jpegli_write_m_header(cinfo, kSpecialMarker1, sizeof(kMarkerData));
|
||||
for (uint8_t c : kMarkerData) {
|
||||
jpegli_write_m_byte(cinfo, c);
|
||||
}
|
||||
for (size_t i = 0; i < kMarkerSequenceLen; ++i) {
|
||||
jpegli_write_marker(cinfo, kMarkerSequence[i], kMarkerData,
|
||||
((i + 2) % sizeof(kMarkerData)));
|
||||
}
|
||||
}
|
||||
if (!jparams.icc.empty()) {
|
||||
jpegli_write_icc_profile(cinfo, jparams.icc.data(), jparams.icc.size());
|
||||
}
|
||||
}
|
||||
if (cinfo->raw_data_in) {
|
||||
// Need to copy because jpeg API requires non-const pointers.
|
||||
std::vector<std::vector<uint8_t>> raw_data = input.raw_data;
|
||||
size_t max_lines = jparams.max_v_sample() * DCTSIZE;
|
||||
std::vector<std::vector<JSAMPROW>> rowdata(cinfo->num_components);
|
||||
std::vector<JSAMPARRAY> data(cinfo->num_components);
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
rowdata[c].resize(jparams.v_samp(c) * DCTSIZE);
|
||||
data[c] = rowdata[c].data();
|
||||
}
|
||||
while (cinfo->next_scanline < cinfo->image_height) {
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
size_t cwidth = cinfo->comp_info[c].width_in_blocks * DCTSIZE;
|
||||
size_t cheight = cinfo->comp_info[c].height_in_blocks * DCTSIZE;
|
||||
size_t num_lines = jparams.v_samp(c) * DCTSIZE;
|
||||
size_t y0 = (cinfo->next_scanline / max_lines) * num_lines;
|
||||
for (size_t i = 0; i < num_lines; ++i) {
|
||||
rowdata[c][i] =
|
||||
(y0 + i < cheight ? &raw_data[c][(y0 + i) * cwidth] : nullptr);
|
||||
}
|
||||
}
|
||||
size_t num_lines = jpegli_write_raw_data(cinfo, data.data(), max_lines);
|
||||
Check(num_lines == max_lines);
|
||||
}
|
||||
} else if (!input.coeffs.empty()) {
|
||||
j_common_ptr comptr = reinterpret_cast<j_common_ptr>(cinfo);
|
||||
jvirt_barray_ptr* coef_arrays = reinterpret_cast<jvirt_barray_ptr*>((
|
||||
*cinfo->mem->alloc_small)(
|
||||
comptr, JPOOL_IMAGE, cinfo->num_components * sizeof(jvirt_barray_ptr)));
|
||||
for (int c = 0; c < cinfo->num_components; ++c) {
|
||||
size_t xsize_blocks = jparams.comp_width(input, c) / DCTSIZE;
|
||||
size_t ysize_blocks = jparams.comp_height(input, c) / DCTSIZE;
|
||||
coef_arrays[c] = (*cinfo->mem->request_virt_barray)(
|
||||
comptr, JPOOL_IMAGE, FALSE, xsize_blocks, ysize_blocks,
|
||||
cinfo->comp_info[c].v_samp_factor);
|
||||
}
|
||||
jpegli_write_coefficients(cinfo, coef_arrays);
|
||||
if (jparams.add_marker) {
|
||||
jpegli_write_marker(cinfo, kSpecialMarker0, kMarkerData,
|
||||
sizeof(kMarkerData));
|
||||
jpegli_write_m_header(cinfo, kSpecialMarker1, sizeof(kMarkerData));
|
||||
for (uint8_t c : kMarkerData) {
|
||||
jpegli_write_m_byte(cinfo, c);
|
||||
}
|
||||
}
|
||||
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 blocks = (*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(blocks[0], &input.coeffs[c][offset], stride);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
size_t stride = cinfo->image_width * cinfo->input_components *
|
||||
jpegli_bytes_per_sample(input.data_type);
|
||||
std::vector<uint8_t> row_bytes(stride);
|
||||
for (size_t y = 0; y < cinfo->image_height; ++y) {
|
||||
memcpy(row_bytes.data(), &input.pixels[y * stride], stride);
|
||||
JSAMPROW row[] = {row_bytes.data()};
|
||||
jpegli_write_scanlines(cinfo, row, 1);
|
||||
}
|
||||
}
|
||||
jpegli_finish_compress(cinfo);
|
||||
}
|
||||
|
||||
bool EncodeWithJpegli(const TestImage& input, const CompressParams& jparams,
|
||||
std::vector<uint8_t>* compressed) {
|
||||
uint8_t* buffer = nullptr;
|
||||
unsigned long buffer_size = 0; // NOLINT
|
||||
jpeg_compress_struct cinfo;
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
jpegli_create_compress(&cinfo);
|
||||
jpegli_mem_dest(&cinfo, &buffer, &buffer_size);
|
||||
EncodeWithJpegli(input, jparams, &cinfo);
|
||||
return true;
|
||||
};
|
||||
bool success = try_catch_block();
|
||||
jpegli_destroy_compress(&cinfo);
|
||||
if (success) {
|
||||
compressed->resize(buffer_size);
|
||||
std::copy_n(buffer, buffer_size, compressed->data());
|
||||
}
|
||||
if (buffer) std::free(buffer);
|
||||
return success;
|
||||
}
|
||||
|
||||
int NumTestScanScripts() { return kNumTestScripts; }
|
||||
|
||||
void DumpImage(const TestImage& image, const std::string& fn) {
|
||||
Check(image.components == 1 || image.components == 3);
|
||||
size_t bytes_per_sample = jpegli_bytes_per_sample(image.data_type);
|
||||
uint32_t maxval = (1u << (8 * bytes_per_sample)) - 1;
|
||||
char type = image.components == 1 ? '5' : '6';
|
||||
std::ofstream out(fn.c_str(), std::ofstream::binary);
|
||||
out << "P" << type << "\n"
|
||||
<< image.xsize << " " << image.ysize << "\n"
|
||||
<< maxval << "\n";
|
||||
out.write(reinterpret_cast<const char*>(image.pixels.data()),
|
||||
image.pixels.size());
|
||||
out.close();
|
||||
}
|
||||
|
||||
double DistanceRms(const TestImage& input, const TestImage& output,
|
||||
size_t start_line, size_t num_lines, double* max_diff) {
|
||||
size_t stride = input.xsize * input.components;
|
||||
size_t start_offset = start_line * stride;
|
||||
auto get_sample = [&](const TestImage& im, const std::vector<uint8_t>& data,
|
||||
size_t idx) -> double {
|
||||
size_t bytes_per_sample = jpegli_bytes_per_sample(im.data_type);
|
||||
bool is_little_endian =
|
||||
(im.endianness == JPEGLI_LITTLE_ENDIAN ||
|
||||
(im.endianness == JPEGLI_NATIVE_ENDIAN && IsLittleEndian()));
|
||||
size_t offset = start_offset + idx * bytes_per_sample;
|
||||
Check(offset < data.size());
|
||||
const uint8_t* p = &data[offset];
|
||||
if (im.data_type == JPEGLI_TYPE_UINT8) {
|
||||
static const double mul8 = 1.0 / 255.0;
|
||||
return p[0] * mul8;
|
||||
} else if (im.data_type == JPEGLI_TYPE_UINT16) {
|
||||
static const double mul16 = 1.0 / 65535.0;
|
||||
return (is_little_endian ? LoadLE16(p) : LoadBE16(p)) * mul16;
|
||||
} else if (im.data_type == JPEGLI_TYPE_FLOAT) {
|
||||
return (is_little_endian ? LoadLEFloat(p) : LoadBEFloat(p));
|
||||
}
|
||||
return 0.0;
|
||||
};
|
||||
double diff2 = 0.0;
|
||||
size_t num_samples = 0;
|
||||
if (max_diff) *max_diff = 0.0;
|
||||
if (!input.pixels.empty() && !output.pixels.empty()) {
|
||||
num_samples = num_lines * stride;
|
||||
for (size_t i = 0; i < num_samples; ++i) {
|
||||
double sample_orig = get_sample(input, input.pixels, i);
|
||||
double sample_output = get_sample(output, output.pixels, i);
|
||||
double diff = sample_orig - sample_output;
|
||||
if (max_diff) *max_diff = std::max(*max_diff, 255.0 * std::abs(diff));
|
||||
diff2 += diff * diff;
|
||||
}
|
||||
} else {
|
||||
Check(!input.raw_data.empty());
|
||||
Check(!output.raw_data.empty());
|
||||
for (size_t c = 0; c < input.raw_data.size(); ++c) {
|
||||
Check(c < output.raw_data.size());
|
||||
num_samples += input.raw_data[c].size();
|
||||
for (size_t i = 0; i < input.raw_data[c].size(); ++i) {
|
||||
double sample_orig = get_sample(input, input.raw_data[c], i);
|
||||
double sample_output = get_sample(output, output.raw_data[c], i);
|
||||
double diff = sample_orig - sample_output;
|
||||
if (max_diff) *max_diff = std::max(*max_diff, 255.0 * std::abs(diff));
|
||||
diff2 += diff * diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::sqrt(diff2 / num_samples) * 255.0;
|
||||
}
|
||||
|
||||
double DistanceRms(const TestImage& input, const TestImage& output,
|
||||
double* max_diff) {
|
||||
return DistanceRms(input, output, 0, output.ysize, max_diff);
|
||||
}
|
||||
|
||||
void VerifyOutputImage(const TestImage& input, const TestImage& output,
|
||||
size_t start_line, size_t num_lines, double max_rms,
|
||||
double max_diff) {
|
||||
double max_d;
|
||||
double rms = DistanceRms(input, output, start_line, num_lines, &max_d);
|
||||
printf("rms: %f, max_rms: %f, max_d: %f, max_diff: %f\n", rms, max_rms,
|
||||
max_d, max_diff);
|
||||
Check(rms <= max_rms);
|
||||
Check(max_d <= max_diff);
|
||||
}
|
||||
|
||||
void VerifyOutputImage(const TestImage& input, const TestImage& output,
|
||||
double max_rms, double max_diff) {
|
||||
Check(output.xsize == input.xsize);
|
||||
Check(output.ysize == input.ysize);
|
||||
Check(output.components == input.components);
|
||||
Check(output.color_space == input.color_space);
|
||||
if (!input.coeffs.empty()) {
|
||||
Check(input.coeffs.size() == input.components);
|
||||
Check(output.coeffs.size() == input.components);
|
||||
for (size_t c = 0; c < input.components; ++c) {
|
||||
Check(output.coeffs[c].size() == input.coeffs[c].size());
|
||||
Check(0 == memcmp(input.coeffs[c].data(), output.coeffs[c].data(),
|
||||
input.coeffs[c].size()));
|
||||
}
|
||||
} else {
|
||||
VerifyOutputImage(input, output, 0, output.ysize, max_rms, max_diff);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jpegli
|
||||
@@ -1,127 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_TEST_UTILS_H_
|
||||
#define LIB_JPEGLI_TEST_UTILS_H_
|
||||
|
||||
#include <csetjmp>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/test_params.h"
|
||||
#include "lib/jpegli/types.h"
|
||||
#include "lib/jxl/base/include_jpeglib.h" // NOLINT
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
#define ERROR_HANDLER_SETUP(flavor) \
|
||||
jpeg_error_mgr jerr; \
|
||||
jmp_buf env; \
|
||||
cinfo.err = flavor##_std_error(&jerr); \
|
||||
if (setjmp(env)) { \
|
||||
return false; \
|
||||
} \
|
||||
cinfo.client_data = reinterpret_cast<void*>(&env); \
|
||||
cinfo.err->error_exit = [](j_common_ptr cinfo) { \
|
||||
(*cinfo->err->output_message)(cinfo); \
|
||||
jmp_buf* env = reinterpret_cast<jmp_buf*>(cinfo->client_data); \
|
||||
flavor##_destroy(cinfo); \
|
||||
longjmp(*env, 1); \
|
||||
};
|
||||
|
||||
std::string IOMethodName(JpegliDataType data_type, JpegliEndianness endianness);
|
||||
|
||||
std::string ColorSpaceName(J_COLOR_SPACE colorspace);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const TestImage& input);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const CompressParams& jparams);
|
||||
|
||||
int NumTestScanScripts();
|
||||
|
||||
void VerifyHeader(const CompressParams& jparams, j_decompress_ptr cinfo);
|
||||
void VerifyScanHeader(const CompressParams& jparams, j_decompress_ptr cinfo);
|
||||
|
||||
void SetDecompressParams(const DecompressParams& dparams,
|
||||
j_decompress_ptr cinfo);
|
||||
void SetScanDecompressParams(const DecompressParams& dparams,
|
||||
j_decompress_ptr cinfo, int scan_number);
|
||||
|
||||
void CopyCoefficients(j_decompress_ptr cinfo, jvirt_barray_ptr* coef_arrays,
|
||||
TestImage* output);
|
||||
|
||||
void UnmapColors(uint8_t* row, size_t xsize, int components,
|
||||
JSAMPARRAY colormap, size_t num_colors);
|
||||
|
||||
std::string GetTestDataPath(const std::string& filename);
|
||||
jxl::StatusOr<std::vector<uint8_t>> ReadTestData(const std::string& filename);
|
||||
|
||||
class PNMParser {
|
||||
public:
|
||||
explicit PNMParser(const uint8_t* data, const size_t len)
|
||||
: pos_(data), end_(data + len) {}
|
||||
|
||||
// Sets "pos" to the first non-header byte/pixel on success.
|
||||
bool ParseHeader(const uint8_t** pos, size_t* xsize, size_t* ysize,
|
||||
size_t* num_channels, size_t* bitdepth);
|
||||
|
||||
private:
|
||||
static bool IsLineBreak(const uint8_t c) { return c == '\r' || c == '\n'; }
|
||||
static bool IsWhitespace(const uint8_t c) {
|
||||
return IsLineBreak(c) || c == '\t' || c == ' ';
|
||||
}
|
||||
|
||||
bool ParseUnsigned(size_t* number);
|
||||
|
||||
bool SkipWhitespace();
|
||||
|
||||
const uint8_t* pos_;
|
||||
const uint8_t* const end_;
|
||||
};
|
||||
|
||||
bool ReadPNM(const std::vector<uint8_t>& data, size_t* xsize, size_t* ysize,
|
||||
size_t* num_channels, size_t* bitdepth,
|
||||
std::vector<uint8_t>* pixels);
|
||||
|
||||
jxl::Status SetNumChannels(J_COLOR_SPACE colorspace, size_t* channels);
|
||||
|
||||
void ConvertToGrayscale(TestImage* img);
|
||||
|
||||
void GeneratePixels(TestImage* img);
|
||||
|
||||
void GenerateRawData(const CompressParams& jparams, TestImage* img);
|
||||
|
||||
void GenerateCoeffs(const CompressParams& jparams, TestImage* img);
|
||||
|
||||
void EncodeWithJpegli(const TestImage& input, const CompressParams& jparams,
|
||||
j_compress_ptr cinfo);
|
||||
|
||||
bool EncodeWithJpegli(const TestImage& input, const CompressParams& jparams,
|
||||
std::vector<uint8_t>* compressed);
|
||||
|
||||
double DistanceRms(const TestImage& input, const TestImage& output,
|
||||
size_t start_line, size_t num_lines,
|
||||
double* max_diff = nullptr);
|
||||
|
||||
double DistanceRms(const TestImage& input, const TestImage& output,
|
||||
double* max_diff = nullptr);
|
||||
|
||||
void VerifyOutputImage(const TestImage& input, const TestImage& output,
|
||||
size_t start_line, size_t num_lines, double max_rms,
|
||||
double max_diff = 255.0);
|
||||
|
||||
void VerifyOutputImage(const TestImage& input, const TestImage& output,
|
||||
double max_rms, double max_diff = 255.0);
|
||||
|
||||
void Check(bool ok);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_TEST_UTILS_H_
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_TESTING_H_
|
||||
#define LIB_JPEGLI_TESTING_H_
|
||||
|
||||
// GTest specific macros / wrappers.
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
// googletest before 1.10 didn't define INSTANTIATE_TEST_SUITE_P() but instead
|
||||
// used INSTANTIATE_TEST_CASE_P which is now deprecated.
|
||||
#ifdef INSTANTIATE_TEST_SUITE_P
|
||||
#define JPEGLI_INSTANTIATE_TEST_SUITE_P INSTANTIATE_TEST_SUITE_P
|
||||
#else
|
||||
#define JPEGLI_INSTANTIATE_TEST_SUITE_P INSTANTIATE_TEST_CASE_P
|
||||
#endif
|
||||
|
||||
// Replacement for ASSERT_TRUE inside try-catch blocks.
|
||||
#define JPEGLI_TEST_ENSURE_TRUE(C) \
|
||||
if (!(C)) return false;
|
||||
|
||||
#define QUIT(M) FAIL() << M
|
||||
|
||||
// Ensures that we don't make our test bounds too lax, effectively disabling the
|
||||
// tests.
|
||||
#define EXPECT_SLIGHTLY_BELOW(A, E) \
|
||||
{ \
|
||||
double _actual = (A); \
|
||||
double _expected = (E); \
|
||||
EXPECT_LE(_actual, _expected); \
|
||||
EXPECT_GE(_actual, 0.75 * _expected); \
|
||||
}
|
||||
|
||||
#endif // LIB_JPEGLI_TESTING_H_
|
||||
@@ -1,142 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jpegli/common.h"
|
||||
#include "lib/jpegli/decode.h"
|
||||
#include "lib/jpegli/encode.h"
|
||||
#include "lib/jpegli/libjpeg_test_util.h"
|
||||
#include "lib/jpegli/test_params.h"
|
||||
#include "lib/jpegli/test_utils.h"
|
||||
#include "lib/jpegli/testing.h"
|
||||
|
||||
namespace jpegli {
|
||||
namespace {
|
||||
|
||||
void TranscodeWithJpegli(const std::vector<uint8_t>& jpeg_input,
|
||||
const CompressParams& jparams,
|
||||
std::vector<uint8_t>* jpeg_output) {
|
||||
jpeg_decompress_struct dinfo = {};
|
||||
jpeg_compress_struct cinfo = {};
|
||||
uint8_t* transcoded_data = nullptr;
|
||||
unsigned long transcoded_size; // NOLINT
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
ERROR_HANDLER_SETUP(jpegli);
|
||||
dinfo.err = cinfo.err;
|
||||
dinfo.client_data = cinfo.client_data;
|
||||
jpegli_create_decompress(&dinfo);
|
||||
jpegli_mem_src(&dinfo, jpeg_input.data(), jpeg_input.size());
|
||||
EXPECT_EQ(JPEG_REACHED_SOS,
|
||||
jpegli_read_header(&dinfo, /*require_image=*/TRUE));
|
||||
jvirt_barray_ptr* coef_arrays = jpegli_read_coefficients(&dinfo);
|
||||
JPEGLI_TEST_ENSURE_TRUE(coef_arrays != nullptr);
|
||||
jpegli_create_compress(&cinfo);
|
||||
jpegli_mem_dest(&cinfo, &transcoded_data, &transcoded_size);
|
||||
jpegli_copy_critical_parameters(&dinfo, &cinfo);
|
||||
jpegli_set_progressive_level(&cinfo, jparams.progressive_mode);
|
||||
cinfo.optimize_coding = jparams.optimize_coding;
|
||||
jpegli_write_coefficients(&cinfo, coef_arrays);
|
||||
jpegli_finish_compress(&cinfo);
|
||||
jpegli_finish_decompress(&dinfo);
|
||||
return true;
|
||||
};
|
||||
ASSERT_TRUE(try_catch_block());
|
||||
jpegli_destroy_decompress(&dinfo);
|
||||
jpegli_destroy_compress(&cinfo);
|
||||
if (transcoded_data) {
|
||||
jpeg_output->assign(transcoded_data, transcoded_data + transcoded_size);
|
||||
free(transcoded_data);
|
||||
}
|
||||
}
|
||||
|
||||
struct TestConfig {
|
||||
TestImage input;
|
||||
CompressParams jparams;
|
||||
};
|
||||
|
||||
class TranscodeAPITestParam : public ::testing::TestWithParam<TestConfig> {};
|
||||
|
||||
TEST_P(TranscodeAPITestParam, TestAPI) {
|
||||
TestConfig config = GetParam();
|
||||
CompressParams& jparams = config.jparams;
|
||||
GeneratePixels(&config.input);
|
||||
|
||||
// Start with sequential non-optimized jpeg.
|
||||
jparams.progressive_mode = 0;
|
||||
jparams.optimize_coding = 0;
|
||||
std::vector<uint8_t> compressed;
|
||||
ASSERT_TRUE(EncodeWithJpegli(config.input, jparams, &compressed));
|
||||
TestImage output0;
|
||||
DecodeWithLibjpeg(jparams, DecompressParams(), compressed, &output0);
|
||||
|
||||
// Transcode first to a sequential optimized jpeg, and then further to
|
||||
// a progressive jpeg.
|
||||
for (int progr : {0, 2}) {
|
||||
std::vector<uint8_t> transcoded;
|
||||
jparams.progressive_mode = progr;
|
||||
jparams.optimize_coding = 1;
|
||||
TranscodeWithJpegli(compressed, jparams, &transcoded);
|
||||
|
||||
// We expect a size reduction of at least 2%.
|
||||
EXPECT_LT(transcoded.size(), compressed.size() * 0.98f);
|
||||
|
||||
// Verify that transcoding is lossless.
|
||||
TestImage output1;
|
||||
DecodeWithLibjpeg(jparams, DecompressParams(), transcoded, &output1);
|
||||
ASSERT_EQ(output0.pixels.size(), output1.pixels.size());
|
||||
EXPECT_EQ(0, memcmp(output0.pixels.data(), output1.pixels.data(),
|
||||
output0.pixels.size()));
|
||||
compressed = transcoded;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TestConfig> GenerateTests() {
|
||||
std::vector<TestConfig> all_tests;
|
||||
const size_t xsize0 = 1024;
|
||||
const size_t ysize0 = 768;
|
||||
for (int dxsize : {0, 1, 8, 9}) {
|
||||
for (int dysize : {0, 1, 8, 9}) {
|
||||
for (int h_sampling : {1, 2}) {
|
||||
for (int v_sampling : {1, 2}) {
|
||||
TestConfig config;
|
||||
config.input.xsize = xsize0 + dxsize;
|
||||
config.input.ysize = ysize0 + dysize;
|
||||
config.jparams.h_sampling = {h_sampling, 1, 1};
|
||||
config.jparams.v_sampling = {v_sampling, 1, 1};
|
||||
all_tests.push_back(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return all_tests;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const TestConfig& c) {
|
||||
os << c.input;
|
||||
os << c.jparams;
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string TestDescription(
|
||||
const testing::TestParamInfo<TranscodeAPITestParam::ParamType>& info) {
|
||||
std::stringstream name;
|
||||
name << info.param;
|
||||
return name.str();
|
||||
}
|
||||
|
||||
JPEGLI_INSTANTIATE_TEST_SUITE_P(TranscodeAPITest, TranscodeAPITestParam,
|
||||
testing::ValuesIn(GenerateTests()),
|
||||
TestDescription);
|
||||
|
||||
} // namespace
|
||||
} // namespace jpegli
|
||||
@@ -1,115 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#if defined(LIB_JPEGLI_TRANSPOSE_INL_H_) == defined(HWY_TARGET_TOGGLE)
|
||||
#ifdef LIB_JPEGLI_TRANSPOSE_INL_H_
|
||||
#undef LIB_JPEGLI_TRANSPOSE_INL_H_
|
||||
#else
|
||||
#define LIB_JPEGLI_TRANSPOSE_INL_H_
|
||||
#endif
|
||||
|
||||
#include <hwy/highway.h>
|
||||
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jpegli {
|
||||
namespace HWY_NAMESPACE {
|
||||
namespace {
|
||||
|
||||
#if HWY_CAP_GE256
|
||||
JXL_INLINE void Transpose8x8Block(const float* JXL_RESTRICT from,
|
||||
float* JXL_RESTRICT to) {
|
||||
const HWY_CAPPED(float, 8) d;
|
||||
auto i0 = Load(d, from);
|
||||
auto i1 = Load(d, from + 1 * 8);
|
||||
auto i2 = Load(d, from + 2 * 8);
|
||||
auto i3 = Load(d, from + 3 * 8);
|
||||
auto i4 = Load(d, from + 4 * 8);
|
||||
auto i5 = Load(d, from + 5 * 8);
|
||||
auto i6 = Load(d, from + 6 * 8);
|
||||
auto i7 = Load(d, from + 7 * 8);
|
||||
|
||||
const auto q0 = InterleaveLower(d, i0, i2);
|
||||
const auto q1 = InterleaveLower(d, i1, i3);
|
||||
const auto q2 = InterleaveUpper(d, i0, i2);
|
||||
const auto q3 = InterleaveUpper(d, i1, i3);
|
||||
const auto q4 = InterleaveLower(d, i4, i6);
|
||||
const auto q5 = InterleaveLower(d, i5, i7);
|
||||
const auto q6 = InterleaveUpper(d, i4, i6);
|
||||
const auto q7 = InterleaveUpper(d, i5, i7);
|
||||
|
||||
const auto r0 = InterleaveLower(d, q0, q1);
|
||||
const auto r1 = InterleaveUpper(d, q0, q1);
|
||||
const auto r2 = InterleaveLower(d, q2, q3);
|
||||
const auto r3 = InterleaveUpper(d, q2, q3);
|
||||
const auto r4 = InterleaveLower(d, q4, q5);
|
||||
const auto r5 = InterleaveUpper(d, q4, q5);
|
||||
const auto r6 = InterleaveLower(d, q6, q7);
|
||||
const auto r7 = InterleaveUpper(d, q6, q7);
|
||||
|
||||
i0 = ConcatLowerLower(d, r4, r0);
|
||||
i1 = ConcatLowerLower(d, r5, r1);
|
||||
i2 = ConcatLowerLower(d, r6, r2);
|
||||
i3 = ConcatLowerLower(d, r7, r3);
|
||||
i4 = ConcatUpperUpper(d, r4, r0);
|
||||
i5 = ConcatUpperUpper(d, r5, r1);
|
||||
i6 = ConcatUpperUpper(d, r6, r2);
|
||||
i7 = ConcatUpperUpper(d, r7, r3);
|
||||
|
||||
Store(i0, d, to);
|
||||
Store(i1, d, to + 1 * 8);
|
||||
Store(i2, d, to + 2 * 8);
|
||||
Store(i3, d, to + 3 * 8);
|
||||
Store(i4, d, to + 4 * 8);
|
||||
Store(i5, d, to + 5 * 8);
|
||||
Store(i6, d, to + 6 * 8);
|
||||
Store(i7, d, to + 7 * 8);
|
||||
}
|
||||
#elif HWY_TARGET != HWY_SCALAR
|
||||
JXL_INLINE void Transpose8x8Block(const float* JXL_RESTRICT from,
|
||||
float* JXL_RESTRICT to) {
|
||||
const HWY_CAPPED(float, 4) d;
|
||||
for (size_t n = 0; n < 8; n += 4) {
|
||||
for (size_t m = 0; m < 8; m += 4) {
|
||||
auto p0 = Load(d, from + n * 8 + m);
|
||||
auto p1 = Load(d, from + (n + 1) * 8 + m);
|
||||
auto p2 = Load(d, from + (n + 2) * 8 + m);
|
||||
auto p3 = Load(d, from + (n + 3) * 8 + m);
|
||||
const auto q0 = InterleaveLower(d, p0, p2);
|
||||
const auto q1 = InterleaveLower(d, p1, p3);
|
||||
const auto q2 = InterleaveUpper(d, p0, p2);
|
||||
const auto q3 = InterleaveUpper(d, p1, p3);
|
||||
|
||||
const auto r0 = InterleaveLower(d, q0, q1);
|
||||
const auto r1 = InterleaveUpper(d, q0, q1);
|
||||
const auto r2 = InterleaveLower(d, q2, q3);
|
||||
const auto r3 = InterleaveUpper(d, q2, q3);
|
||||
Store(r0, d, to + m * 8 + n);
|
||||
Store(r1, d, to + (1 + m) * 8 + n);
|
||||
Store(r2, d, to + (2 + m) * 8 + n);
|
||||
Store(r3, d, to + (3 + m) * 8 + n);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
static JXL_INLINE void Transpose8x8Block(const float* JXL_RESTRICT from,
|
||||
float* JXL_RESTRICT to) {
|
||||
for (size_t n = 0; n < 8; ++n) {
|
||||
for (size_t m = 0; m < 8; ++m) {
|
||||
to[8 * n + m] = from[8 * m + n];
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
#endif // LIB_JPEGLI_TRANSPOSE_INL_H_
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_TYPES_H_
|
||||
#define LIB_JPEGLI_TYPES_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//
|
||||
// New API structs and functions that are not available in libjpeg
|
||||
//
|
||||
// NOTE: This part of the API is still experimental and will probably change in
|
||||
// the future.
|
||||
//
|
||||
|
||||
typedef enum {
|
||||
JPEGLI_TYPE_FLOAT = 0,
|
||||
JPEGLI_TYPE_UINT8 = 2,
|
||||
JPEGLI_TYPE_UINT16 = 3,
|
||||
} JpegliDataType;
|
||||
|
||||
typedef enum {
|
||||
JPEGLI_NATIVE_ENDIAN = 0,
|
||||
JPEGLI_LITTLE_ENDIAN = 1,
|
||||
JPEGLI_BIG_ENDIAN = 2,
|
||||
} JpegliEndianness;
|
||||
|
||||
int jpegli_bytes_per_sample(JpegliDataType data_type);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // LIB_JPEGLI_TYPES_H_
|
||||
@@ -1,139 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/jpegli/upsample.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
#undef HWY_TARGET_INCLUDE
|
||||
#define HWY_TARGET_INCLUDE "lib/jpegli/upsample.cc"
|
||||
#include <hwy/foreach_target.h>
|
||||
#include <hwy/highway.h>
|
||||
|
||||
HWY_BEFORE_NAMESPACE();
|
||||
namespace jpegli {
|
||||
namespace HWY_NAMESPACE {
|
||||
|
||||
// These templates are not found via ADL.
|
||||
using hwy::HWY_NAMESPACE::Mul;
|
||||
using hwy::HWY_NAMESPACE::MulAdd;
|
||||
using hwy::HWY_NAMESPACE::Vec;
|
||||
|
||||
#if HWY_CAP_GE512
|
||||
using hwy::HWY_NAMESPACE::Half;
|
||||
using hwy::HWY_NAMESPACE::Vec;
|
||||
template <size_t i, class DF, class V>
|
||||
HWY_INLINE Vec<Half<Half<DF>>> Quarter(const DF df, V v) {
|
||||
using HF = Half<DF>;
|
||||
using HHF = Half<HF>;
|
||||
auto half = i >= 2 ? UpperHalf(HF(), v) : LowerHalf(HF(), v);
|
||||
return i & 1 ? UpperHalf(HHF(), half) : LowerHalf(HHF(), half);
|
||||
}
|
||||
|
||||
template <class DF, class V>
|
||||
HWY_INLINE Vec<DF> Concat4(const DF df, V v0, V v1, V v2, V v3) {
|
||||
using HF = Half<DF>;
|
||||
return Combine(DF(), Combine(HF(), v3, v2), Combine(HF(), v1, v0));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Stores v0[0], v1[0], v0[1], v1[1], ... to mem, in this order. Mem must be
|
||||
// aligned.
|
||||
template <class DF, class V, typename T>
|
||||
void StoreInterleaved(const DF df, V v0, V v1, T* mem) {
|
||||
static_assert(sizeof(T) == 4, "only use StoreInterleaved for 4-byte types");
|
||||
#if HWY_TARGET == HWY_SCALAR
|
||||
Store(v0, df, mem);
|
||||
Store(v1, df, mem + 1);
|
||||
#elif !HWY_CAP_GE256
|
||||
Store(InterleaveLower(df, v0, v1), df, mem);
|
||||
Store(InterleaveUpper(df, v0, v1), df, mem + Lanes(df));
|
||||
#else
|
||||
if (!HWY_CAP_GE512 || Lanes(df) == 8) {
|
||||
auto t0 = InterleaveLower(df, v0, v1);
|
||||
auto t1 = InterleaveUpper(df, v0, v1);
|
||||
Store(ConcatLowerLower(df, t1, t0), df, mem);
|
||||
Store(ConcatUpperUpper(df, t1, t0), df, mem + Lanes(df));
|
||||
} else {
|
||||
#if HWY_CAP_GE512
|
||||
auto t0 = InterleaveLower(df, v0, v1);
|
||||
auto t1 = InterleaveUpper(df, v0, v1);
|
||||
Store(Concat4(df, Quarter<0>(df, t0), Quarter<0>(df, t1),
|
||||
Quarter<1>(df, t0), Quarter<1>(df, t1)),
|
||||
df, mem);
|
||||
Store(Concat4(df, Quarter<2>(df, t0), Quarter<2>(df, t1),
|
||||
Quarter<3>(df, t0), Quarter<3>(df, t1)),
|
||||
df, mem + Lanes(df));
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Upsample2Horizontal(float* JXL_RESTRICT row,
|
||||
float* JXL_RESTRICT scratch_space, size_t len_out) {
|
||||
HWY_FULL(float) df;
|
||||
auto threefour = Set(df, 0.75f);
|
||||
auto onefour = Set(df, 0.25f);
|
||||
const size_t len_in = (len_out + 1) >> 1;
|
||||
memcpy(scratch_space, row, len_in * sizeof(row[0]));
|
||||
scratch_space[-1] = scratch_space[0];
|
||||
scratch_space[len_in] = scratch_space[len_in - 1];
|
||||
for (size_t x = 0; x < len_in; x += Lanes(df)) {
|
||||
auto current = Mul(Load(df, scratch_space + x), threefour);
|
||||
auto prev = LoadU(df, scratch_space + x - 1);
|
||||
auto next = LoadU(df, scratch_space + x + 1);
|
||||
auto left = MulAdd(onefour, prev, current);
|
||||
auto right = MulAdd(onefour, next, current);
|
||||
StoreInterleaved(df, left, right, row + x * 2);
|
||||
}
|
||||
}
|
||||
|
||||
void Upsample2Vertical(const float* JXL_RESTRICT row_top,
|
||||
const float* JXL_RESTRICT row_mid,
|
||||
const float* JXL_RESTRICT row_bot,
|
||||
float* JXL_RESTRICT row_out0,
|
||||
float* JXL_RESTRICT row_out1, size_t len) {
|
||||
HWY_FULL(float) df;
|
||||
auto threefour = Set(df, 0.75f);
|
||||
auto onefour = Set(df, 0.25f);
|
||||
for (size_t x = 0; x < len; x += Lanes(df)) {
|
||||
auto it = Load(df, row_top + x);
|
||||
auto im = Load(df, row_mid + x);
|
||||
auto ib = Load(df, row_bot + x);
|
||||
auto im_scaled = Mul(im, threefour);
|
||||
Store(MulAdd(it, onefour, im_scaled), df, row_out0 + x);
|
||||
Store(MulAdd(ib, onefour, im_scaled), df, row_out1 + x);
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-readability-namespace-comments)
|
||||
} // namespace HWY_NAMESPACE
|
||||
} // namespace jpegli
|
||||
HWY_AFTER_NAMESPACE();
|
||||
|
||||
#if HWY_ONCE
|
||||
namespace jpegli {
|
||||
|
||||
HWY_EXPORT(Upsample2Horizontal);
|
||||
HWY_EXPORT(Upsample2Vertical);
|
||||
|
||||
void Upsample2Horizontal(float* JXL_RESTRICT row,
|
||||
float* JXL_RESTRICT scratch_space, size_t len_out) {
|
||||
HWY_DYNAMIC_DISPATCH(Upsample2Horizontal)(row, scratch_space, len_out);
|
||||
}
|
||||
|
||||
void Upsample2Vertical(const float* JXL_RESTRICT row_top,
|
||||
const float* JXL_RESTRICT row_mid,
|
||||
const float* JXL_RESTRICT row_bot,
|
||||
float* JXL_RESTRICT row_out0,
|
||||
float* JXL_RESTRICT row_out1, size_t len) {
|
||||
HWY_DYNAMIC_DISPATCH(Upsample2Vertical)
|
||||
(row_top, row_mid, row_bot, row_out0, row_out1, len);
|
||||
}
|
||||
} // namespace jpegli
|
||||
#endif // HWY_ONCE
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_JPEGLI_UPSAMPLE_H_
|
||||
#define LIB_JPEGLI_UPSAMPLE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
namespace jpegli {
|
||||
|
||||
void Upsample2Horizontal(float* JXL_RESTRICT row,
|
||||
float* JXL_RESTRICT scratch_space, size_t len_out);
|
||||
|
||||
void Upsample2Vertical(const float* JXL_RESTRICT row_top,
|
||||
const float* JXL_RESTRICT row_mid,
|
||||
const float* JXL_RESTRICT row_bot,
|
||||
float* JXL_RESTRICT row_out0,
|
||||
float* JXL_RESTRICT row_out1, size_t len);
|
||||
|
||||
} // namespace jpegli
|
||||
|
||||
#endif // LIB_JPEGLI_UPSAMPLE_H_
|
||||
+3
-18
@@ -5,9 +5,9 @@
|
||||
|
||||
include(jxl_lists.cmake)
|
||||
|
||||
# Object library for those parts of extras that do not depend on jxl internals
|
||||
# or jpegli. We will create two versions of these object files, one with and one
|
||||
# without external codec support compiled in.
|
||||
# Object library for those parts of extras that do not depend on jxl internals.
|
||||
# We will create two versions of these object files, one with and one without
|
||||
# external codec support compiled in.
|
||||
list(APPEND JPEGXL_EXTRAS_CORE_SOURCES
|
||||
"${JPEGXL_INTERNAL_EXTRAS_SOURCES}"
|
||||
"${JPEGXL_INTERNAL_CODEC_APNG_SOURCES}"
|
||||
@@ -64,17 +64,6 @@ if (JPEGXL_ENABLE_SJPEG)
|
||||
list(APPEND JXL_EXTRAS_CODEC_INTERNAL_LIBRARIES sjpeg)
|
||||
endif()
|
||||
|
||||
if(JPEGXL_ENABLE_JPEGLI)
|
||||
add_library(jxl_extras_jpegli-obj OBJECT
|
||||
"${JPEGXL_INTERNAL_CODEC_JPEGLI_SOURCES}"
|
||||
)
|
||||
target_include_directories(jxl_extras_jpegli-obj PRIVATE
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/include/jpegli"
|
||||
)
|
||||
list(APPEND JXL_EXTRAS_OBJECT_LIBRARIES jxl_extras_jpegli-obj)
|
||||
list(APPEND JXL_EXTRAS_OBJECTS $<TARGET_OBJECTS:jxl_extras_jpegli-obj>)
|
||||
endif()
|
||||
|
||||
if(NOT JPEGXL_BUNDLE_LIBPNG)
|
||||
find_package(PNG)
|
||||
endif()
|
||||
@@ -150,10 +139,6 @@ target_link_libraries(jxl_extras-internal PRIVATE
|
||||
jxl_threads
|
||||
)
|
||||
target_link_libraries(jxl_extras-internal PUBLIC jxl-internal)
|
||||
if(JPEGXL_ENABLE_JPEGLI)
|
||||
target_compile_definitions(jxl_extras-internal PUBLIC -DJPEGXL_ENABLE_JPEGLI=1)
|
||||
target_link_libraries(jxl_extras-internal PRIVATE jpegli-static)
|
||||
endif()
|
||||
|
||||
### Library that does not depend on internal parts of jxl library.
|
||||
### Used by cjxl and djxl binaries.
|
||||
|
||||
@@ -65,13 +65,6 @@ libjxl_codec_gif_sources = [
|
||||
"extras/dec/gif.h",
|
||||
]
|
||||
|
||||
libjxl_codec_jpegli_sources = [
|
||||
"extras/dec/jpegli.cc",
|
||||
"extras/dec/jpegli.h",
|
||||
"extras/enc/jpegli.cc",
|
||||
"extras/enc/jpegli.h",
|
||||
]
|
||||
|
||||
libjxl_codec_jpg_sources = [
|
||||
"extras/dec/jpg.cc",
|
||||
"extras/dec/jpg.h",
|
||||
@@ -463,94 +456,6 @@ libjxl_gbench_sources = [
|
||||
"jxl/tf_gbench.cc",
|
||||
]
|
||||
|
||||
libjxl_jpegli_lib_version = 62
|
||||
|
||||
libjxl_jpegli_libjpeg_helper_files = [
|
||||
"jpegli/libjpeg_test_util.cc",
|
||||
"jpegli/libjpeg_test_util.h",
|
||||
]
|
||||
|
||||
libjxl_jpegli_sources = [
|
||||
"jpegli/adaptive_quantization.cc",
|
||||
"jpegli/adaptive_quantization.h",
|
||||
"jpegli/bit_writer.cc",
|
||||
"jpegli/bit_writer.h",
|
||||
"jpegli/bitstream.cc",
|
||||
"jpegli/bitstream.h",
|
||||
"jpegli/color_quantize.cc",
|
||||
"jpegli/color_quantize.h",
|
||||
"jpegli/color_transform.cc",
|
||||
"jpegli/color_transform.h",
|
||||
"jpegli/common.cc",
|
||||
"jpegli/common.h",
|
||||
"jpegli/common_internal.h",
|
||||
"jpegli/dct-inl.h",
|
||||
"jpegli/decode.cc",
|
||||
"jpegli/decode.h",
|
||||
"jpegli/decode_internal.h",
|
||||
"jpegli/decode_marker.cc",
|
||||
"jpegli/decode_marker.h",
|
||||
"jpegli/decode_scan.cc",
|
||||
"jpegli/decode_scan.h",
|
||||
"jpegli/destination_manager.cc",
|
||||
"jpegli/downsample.cc",
|
||||
"jpegli/downsample.h",
|
||||
"jpegli/encode.cc",
|
||||
"jpegli/encode.h",
|
||||
"jpegli/encode_finish.cc",
|
||||
"jpegli/encode_finish.h",
|
||||
"jpegli/encode_internal.h",
|
||||
"jpegli/encode_streaming.cc",
|
||||
"jpegli/encode_streaming.h",
|
||||
"jpegli/entropy_coding-inl.h",
|
||||
"jpegli/entropy_coding.cc",
|
||||
"jpegli/entropy_coding.h",
|
||||
"jpegli/error.cc",
|
||||
"jpegli/error.h",
|
||||
"jpegli/huffman.cc",
|
||||
"jpegli/huffman.h",
|
||||
"jpegli/idct.cc",
|
||||
"jpegli/idct.h",
|
||||
"jpegli/input.cc",
|
||||
"jpegli/input.h",
|
||||
"jpegli/memory_manager.cc",
|
||||
"jpegli/memory_manager.h",
|
||||
"jpegli/quant.cc",
|
||||
"jpegli/quant.h",
|
||||
"jpegli/render.cc",
|
||||
"jpegli/render.h",
|
||||
"jpegli/simd.cc",
|
||||
"jpegli/simd.h",
|
||||
"jpegli/source_manager.cc",
|
||||
"jpegli/transpose-inl.h",
|
||||
"jpegli/types.h",
|
||||
"jpegli/upsample.cc",
|
||||
"jpegli/upsample.h",
|
||||
]
|
||||
|
||||
libjxl_jpegli_testlib_files = [
|
||||
"jpegli/fuzztest.h",
|
||||
"jpegli/test_params.h",
|
||||
"jpegli/test_utils-inl.h",
|
||||
"jpegli/test_utils.cc",
|
||||
"jpegli/test_utils.h",
|
||||
]
|
||||
|
||||
libjxl_jpegli_tests = [
|
||||
"jpegli/decode_api_test.cc",
|
||||
"jpegli/encode_api_test.cc",
|
||||
"jpegli/error_handling_test.cc",
|
||||
"jpegli/input_suspension_test.cc",
|
||||
"jpegli/output_suspension_test.cc",
|
||||
"jpegli/source_manager_test.cc",
|
||||
"jpegli/streaming_test.cc",
|
||||
"jpegli/transcode_api_test.cc",
|
||||
]
|
||||
|
||||
libjxl_jpegli_wrapper_sources = [
|
||||
"jpegli/libjpeg_wrapper.cc",
|
||||
]
|
||||
|
||||
libjxl_major_version = 0
|
||||
|
||||
libjxl_minor_version = 12
|
||||
@@ -596,7 +501,6 @@ libjxl_tests = [
|
||||
"extras/dec/color_description_test.cc",
|
||||
"extras/dec/pgx_test.cc",
|
||||
"extras/gain_map_test.cc",
|
||||
"extras/jpegli_test.cc",
|
||||
"jxl/ac_strategy_test.cc",
|
||||
"jxl/alpha_test.cc",
|
||||
"jxl/ans_common_test.cc",
|
||||
|
||||
@@ -62,13 +62,6 @@ set(JPEGXL_INTERNAL_CODEC_GIF_SOURCES
|
||||
extras/dec/gif.h
|
||||
)
|
||||
|
||||
set(JPEGXL_INTERNAL_CODEC_JPEGLI_SOURCES
|
||||
extras/dec/jpegli.cc
|
||||
extras/dec/jpegli.h
|
||||
extras/enc/jpegli.cc
|
||||
extras/enc/jpegli.h
|
||||
)
|
||||
|
||||
set(JPEGXL_INTERNAL_CODEC_JPG_SOURCES
|
||||
extras/dec/jpg.cc
|
||||
extras/dec/jpg.h
|
||||
@@ -460,92 +453,6 @@ set(JPEGXL_INTERNAL_GBENCH_SOURCES
|
||||
jxl/tf_gbench.cc
|
||||
)
|
||||
|
||||
set(JPEGXL_INTERNAL_JPEGLI_LIBJPEG_HELPER_FILES
|
||||
jpegli/libjpeg_test_util.cc
|
||||
jpegli/libjpeg_test_util.h
|
||||
)
|
||||
|
||||
set(JPEGXL_INTERNAL_JPEGLI_SOURCES
|
||||
jpegli/adaptive_quantization.cc
|
||||
jpegli/adaptive_quantization.h
|
||||
jpegli/bit_writer.cc
|
||||
jpegli/bit_writer.h
|
||||
jpegli/bitstream.cc
|
||||
jpegli/bitstream.h
|
||||
jpegli/color_quantize.cc
|
||||
jpegli/color_quantize.h
|
||||
jpegli/color_transform.cc
|
||||
jpegli/color_transform.h
|
||||
jpegli/common.cc
|
||||
jpegli/common.h
|
||||
jpegli/common_internal.h
|
||||
jpegli/dct-inl.h
|
||||
jpegli/decode.cc
|
||||
jpegli/decode.h
|
||||
jpegli/decode_internal.h
|
||||
jpegli/decode_marker.cc
|
||||
jpegli/decode_marker.h
|
||||
jpegli/decode_scan.cc
|
||||
jpegli/decode_scan.h
|
||||
jpegli/destination_manager.cc
|
||||
jpegli/downsample.cc
|
||||
jpegli/downsample.h
|
||||
jpegli/encode.cc
|
||||
jpegli/encode.h
|
||||
jpegli/encode_finish.cc
|
||||
jpegli/encode_finish.h
|
||||
jpegli/encode_internal.h
|
||||
jpegli/encode_streaming.cc
|
||||
jpegli/encode_streaming.h
|
||||
jpegli/entropy_coding-inl.h
|
||||
jpegli/entropy_coding.cc
|
||||
jpegli/entropy_coding.h
|
||||
jpegli/error.cc
|
||||
jpegli/error.h
|
||||
jpegli/huffman.cc
|
||||
jpegli/huffman.h
|
||||
jpegli/idct.cc
|
||||
jpegli/idct.h
|
||||
jpegli/input.cc
|
||||
jpegli/input.h
|
||||
jpegli/memory_manager.cc
|
||||
jpegli/memory_manager.h
|
||||
jpegli/quant.cc
|
||||
jpegli/quant.h
|
||||
jpegli/render.cc
|
||||
jpegli/render.h
|
||||
jpegli/simd.cc
|
||||
jpegli/simd.h
|
||||
jpegli/source_manager.cc
|
||||
jpegli/transpose-inl.h
|
||||
jpegli/types.h
|
||||
jpegli/upsample.cc
|
||||
jpegli/upsample.h
|
||||
)
|
||||
|
||||
set(JPEGXL_INTERNAL_JPEGLI_TESTLIB_FILES
|
||||
jpegli/fuzztest.h
|
||||
jpegli/test_params.h
|
||||
jpegli/test_utils-inl.h
|
||||
jpegli/test_utils.cc
|
||||
jpegli/test_utils.h
|
||||
)
|
||||
|
||||
set(JPEGXL_INTERNAL_JPEGLI_TESTS
|
||||
jpegli/decode_api_test.cc
|
||||
jpegli/encode_api_test.cc
|
||||
jpegli/error_handling_test.cc
|
||||
jpegli/input_suspension_test.cc
|
||||
jpegli/output_suspension_test.cc
|
||||
jpegli/source_manager_test.cc
|
||||
jpegli/streaming_test.cc
|
||||
jpegli/transcode_api_test.cc
|
||||
)
|
||||
|
||||
set(JPEGXL_INTERNAL_JPEGLI_WRAPPER_SOURCES
|
||||
jpegli/libjpeg_wrapper.cc
|
||||
)
|
||||
|
||||
set(JPEGXL_INTERNAL_PUBLIC_HEADERS
|
||||
include/jxl/cms.h
|
||||
include/jxl/cms_interface.h
|
||||
@@ -585,7 +492,6 @@ set(JPEGXL_INTERNAL_TESTS
|
||||
extras/dec/color_description_test.cc
|
||||
extras/dec/pgx_test.cc
|
||||
extras/gain_map_test.cc
|
||||
extras/jpegli_test.cc
|
||||
jxl/ac_strategy_test.cc
|
||||
jxl/alpha_test.cc
|
||||
jxl/ans_common_test.cc
|
||||
|
||||
@@ -22,10 +22,6 @@ libjxl_deps_testdata = ["//:testdata"]
|
||||
libjxl_deps_webp = ["@libwebp//:webp"]
|
||||
|
||||
libjxl_test_shards = {
|
||||
"jpegli/decode_api_test": 10,
|
||||
"jpegli/encode_api_test": 4,
|
||||
"jpegli/input_suspension_test": 6,
|
||||
"jpegli/output_suspension_test": 2,
|
||||
"jxl/ans_test": 2,
|
||||
"jxl/linalg_test": 2,
|
||||
"jxl/modular_test": 4,
|
||||
|
||||
-96
@@ -63,13 +63,6 @@ libjxl_codec_gif_sources = [
|
||||
"extras/dec/gif.h",
|
||||
]
|
||||
|
||||
libjxl_codec_jpegli_sources = [
|
||||
"extras/dec/jpegli.cc",
|
||||
"extras/dec/jpegli.h",
|
||||
"extras/enc/jpegli.cc",
|
||||
"extras/enc/jpegli.h",
|
||||
]
|
||||
|
||||
libjxl_codec_jpg_sources = [
|
||||
"extras/dec/jpg.cc",
|
||||
"extras/dec/jpg.h",
|
||||
@@ -461,94 +454,6 @@ libjxl_gbench_sources = [
|
||||
"jxl/tf_gbench.cc",
|
||||
]
|
||||
|
||||
libjxl_jpegli_lib_version = 62
|
||||
|
||||
libjxl_jpegli_libjpeg_helper_files = [
|
||||
"jpegli/libjpeg_test_util.cc",
|
||||
"jpegli/libjpeg_test_util.h",
|
||||
]
|
||||
|
||||
libjxl_jpegli_sources = [
|
||||
"jpegli/adaptive_quantization.cc",
|
||||
"jpegli/adaptive_quantization.h",
|
||||
"jpegli/bit_writer.cc",
|
||||
"jpegli/bit_writer.h",
|
||||
"jpegli/bitstream.cc",
|
||||
"jpegli/bitstream.h",
|
||||
"jpegli/color_quantize.cc",
|
||||
"jpegli/color_quantize.h",
|
||||
"jpegli/color_transform.cc",
|
||||
"jpegli/color_transform.h",
|
||||
"jpegli/common.cc",
|
||||
"jpegli/common.h",
|
||||
"jpegli/common_internal.h",
|
||||
"jpegli/dct-inl.h",
|
||||
"jpegli/decode.cc",
|
||||
"jpegli/decode.h",
|
||||
"jpegli/decode_internal.h",
|
||||
"jpegli/decode_marker.cc",
|
||||
"jpegli/decode_marker.h",
|
||||
"jpegli/decode_scan.cc",
|
||||
"jpegli/decode_scan.h",
|
||||
"jpegli/destination_manager.cc",
|
||||
"jpegli/downsample.cc",
|
||||
"jpegli/downsample.h",
|
||||
"jpegli/encode.cc",
|
||||
"jpegli/encode.h",
|
||||
"jpegli/encode_finish.cc",
|
||||
"jpegli/encode_finish.h",
|
||||
"jpegli/encode_internal.h",
|
||||
"jpegli/encode_streaming.cc",
|
||||
"jpegli/encode_streaming.h",
|
||||
"jpegli/entropy_coding-inl.h",
|
||||
"jpegli/entropy_coding.cc",
|
||||
"jpegli/entropy_coding.h",
|
||||
"jpegli/error.cc",
|
||||
"jpegli/error.h",
|
||||
"jpegli/huffman.cc",
|
||||
"jpegli/huffman.h",
|
||||
"jpegli/idct.cc",
|
||||
"jpegli/idct.h",
|
||||
"jpegli/input.cc",
|
||||
"jpegli/input.h",
|
||||
"jpegli/memory_manager.cc",
|
||||
"jpegli/memory_manager.h",
|
||||
"jpegli/quant.cc",
|
||||
"jpegli/quant.h",
|
||||
"jpegli/render.cc",
|
||||
"jpegli/render.h",
|
||||
"jpegli/simd.cc",
|
||||
"jpegli/simd.h",
|
||||
"jpegli/source_manager.cc",
|
||||
"jpegli/transpose-inl.h",
|
||||
"jpegli/types.h",
|
||||
"jpegli/upsample.cc",
|
||||
"jpegli/upsample.h",
|
||||
]
|
||||
|
||||
libjxl_jpegli_testlib_files = [
|
||||
"jpegli/fuzztest.h",
|
||||
"jpegli/test_params.h",
|
||||
"jpegli/test_utils-inl.h",
|
||||
"jpegli/test_utils.cc",
|
||||
"jpegli/test_utils.h",
|
||||
]
|
||||
|
||||
libjxl_jpegli_tests = [
|
||||
"jpegli/decode_api_test.cc",
|
||||
"jpegli/encode_api_test.cc",
|
||||
"jpegli/error_handling_test.cc",
|
||||
"jpegli/input_suspension_test.cc",
|
||||
"jpegli/output_suspension_test.cc",
|
||||
"jpegli/source_manager_test.cc",
|
||||
"jpegli/streaming_test.cc",
|
||||
"jpegli/transcode_api_test.cc",
|
||||
]
|
||||
|
||||
libjxl_jpegli_wrapper_sources = [
|
||||
"jpegli/libjpeg_wrapper.cc",
|
||||
]
|
||||
|
||||
libjxl_major_version = 0
|
||||
|
||||
libjxl_minor_version = 12
|
||||
@@ -594,7 +499,6 @@ libjxl_tests = [
|
||||
"extras/dec/color_description_test.cc",
|
||||
"extras/dec/pgx_test.cc",
|
||||
"extras/gain_map_test.cc",
|
||||
"extras/jpegli_test.cc",
|
||||
"jxl/ac_strategy_test.cc",
|
||||
"jxl/alpha_test.cc",
|
||||
"jxl/ans_common_test.cc",
|
||||
|
||||
@@ -154,14 +154,6 @@ if(JPEGXL_ENABLE_TOOLS)
|
||||
)
|
||||
list(APPEND TOOL_BINARIES djxl)
|
||||
|
||||
if(JPEGXL_ENABLE_JPEGLI)
|
||||
# Depends on parts of jxl_extras that are only built if libjpeg is found and
|
||||
# jpegli is enabled.
|
||||
add_executable(cjpegli cjpegli.cc)
|
||||
add_executable(djpegli djpegli.cc)
|
||||
list(APPEND INTERNAL_TOOL_BINARIES cjpegli djpegli)
|
||||
endif()
|
||||
|
||||
add_executable(jxlinfo jxlinfo.cc)
|
||||
target_link_libraries(jxlinfo jxl jxl_extras_nocodec-internal)
|
||||
list(APPEND TOOL_BINARIES jxlinfo)
|
||||
@@ -226,15 +218,6 @@ if(JPEGXL_ENABLE_DEVTOOLS)
|
||||
jxl_extras-internal
|
||||
jxl_tool
|
||||
)
|
||||
if(JPEGXL_ENABLE_JPEGLI)
|
||||
list(APPEND FUZZER_CORPUS_BINARIES jpegli_dec_fuzzer_corpus)
|
||||
add_executable(jpegli_dec_fuzzer_corpus jpegli_dec_fuzzer_corpus.cc)
|
||||
target_link_libraries(jpegli_dec_fuzzer_corpus
|
||||
jpegli-static
|
||||
jxl_tool
|
||||
jxl_threads
|
||||
)
|
||||
endif()
|
||||
endif() # JPEGXL_ENABLE_DEVTOOLS
|
||||
|
||||
# Benchmark tools.
|
||||
@@ -376,9 +359,6 @@ if(JPEGXL_ENABLE_TOOLS)
|
||||
set_from_bytes_fuzzer
|
||||
transforms_fuzzer
|
||||
)
|
||||
if(JPEGXL_ENABLE_JPEGLI)
|
||||
list(APPEND FUZZER_BINARIES jpegli_dec_fuzzer)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Fuzzers.
|
||||
@@ -401,8 +381,6 @@ foreach(FUZZER IN LISTS FUZZER_BINARIES)
|
||||
jxl_threads
|
||||
jxl_tool
|
||||
)
|
||||
elseif(FUZZER STREQUAL jpegli_dec_fuzzer)
|
||||
target_link_libraries("${BINARY}" jpegli-static)
|
||||
else()
|
||||
target_link_libraries("${BINARY}"
|
||||
jxl_testlib-internal
|
||||
@@ -499,30 +477,6 @@ if (JNI_FOUND AND Java_FOUND)
|
||||
)
|
||||
get_target_property(JXL_JNI_WRAPPER_TEST_JAR jxl_jni_wrapper_test JAR_FILE)
|
||||
|
||||
# jpegli wrapper
|
||||
|
||||
if(JPEGXL_ENABLE_JPEGLI)
|
||||
add_library(jpegli_jni SHARED jni/org/jpeg/jpegli/wrapper/encoder_jni.cc)
|
||||
target_include_directories(jpegli_jni PRIVATE "${JNI_INCLUDE_DIRS}" "${PROJECT_SOURCE_DIR}")
|
||||
target_include_directories(jpegli_jni PRIVATE
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/include/jpegli"
|
||||
)
|
||||
target_link_libraries(jpegli_jni PUBLIC jpegli-static)
|
||||
|
||||
add_jar(jpegli_jni_wrapper SOURCES
|
||||
jni/org/jpeg/jpegli/wrapper/Encoder.java
|
||||
jni/org/jpeg/jpegli/wrapper/JniHelper.java
|
||||
OUTPUT_NAME org.jpeg.jpegli
|
||||
)
|
||||
get_target_property(JPEGLI_JNI_WRAPPER_JAR jpegli_jni_wrapper JAR_FILE)
|
||||
|
||||
add_jar(jpegli_jni_wrapper_test
|
||||
SOURCES jni/org/jpeg/jpegli/wrapper/EncoderTest.java
|
||||
INCLUDE_JARS jpegli_jni_wrapper
|
||||
)
|
||||
get_target_property(JPEGLI_JNI_WRAPPER_TEST_JAR jpegli_jni_wrapper_test JAR_FILE)
|
||||
endif()
|
||||
|
||||
if(NOT SANITIZER MATCHES ".san")
|
||||
# NB: Vanilla OpenJDK 8 / 11 are known to work well (i.e. either
|
||||
# "which java" or JAVA_HOME environment variable point to the path like
|
||||
@@ -534,15 +488,6 @@ if (JNI_FOUND AND Java_FOUND)
|
||||
-Dorg.jpeg.jpegxl.wrapper.lib=$<TARGET_FILE:jxl_jni>
|
||||
org.jpeg.jpegxl.wrapper.DecoderTest
|
||||
)
|
||||
if(JPEGXL_ENABLE_JPEGLI)
|
||||
add_test(
|
||||
NAME test_jpegli_jni_wrapper
|
||||
COMMAND ${Java_JAVA_EXECUTABLE}
|
||||
-cp "${JPEGLI_JNI_WRAPPER_JAR}:${JPEGLI_JNI_WRAPPER_TEST_JAR}"
|
||||
-Dorg.jpeg.jpegli.wrapper.lib=$<TARGET_FILE:jpegli_jni>
|
||||
org.jpeg.jpegli.wrapper.EncoderTest
|
||||
)
|
||||
endif()
|
||||
endif() # JPEGXL_ENABLE_FUZZERS
|
||||
endif() # JNI_FOUND & Java_FOUND
|
||||
endif() # JPEGXL_ENABLE_JNI
|
||||
@@ -556,9 +501,6 @@ if (BASH_PROGRAM)
|
||||
if (JPEG_FOUND AND JPEGXL_ENABLE_TRANSCODE_JPEG)
|
||||
list(APPEND TEST_SCRIPTS roundtrip_test)
|
||||
endif()
|
||||
if (JPEG_FOUND AND JPEGXL_ENABLE_JPEGLI)
|
||||
list(APPEND TEST_SCRIPTS jpegli_tools_test)
|
||||
endif()
|
||||
foreach(SCRIPT IN LISTS TEST_SCRIPTS)
|
||||
add_test(NAME ${SCRIPT}
|
||||
COMMAND ${BASH_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/${SCRIPT}.sh
|
||||
|
||||
@@ -19,25 +19,18 @@
|
||||
#include <vector>
|
||||
|
||||
#include "lib/extras/codec.h"
|
||||
#include "lib/extras/enc/encode.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "tools/benchmark/benchmark_args.h"
|
||||
#include "tools/benchmark/benchmark_codec.h"
|
||||
#include "tools/speed_stats.h"
|
||||
|
||||
#if JPEGXL_ENABLE_JPEGLI
|
||||
#include "lib/extras/dec/jpegli.h"
|
||||
#endif
|
||||
#include "lib/extras/dec/jpg.h"
|
||||
#if JPEGXL_ENABLE_JPEGLI
|
||||
#include "lib/extras/enc/jpegli.h"
|
||||
#endif
|
||||
#include "lib/extras/enc/encode.h"
|
||||
#include "lib/extras/enc/jpg.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/extras/time.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "tools/benchmark/benchmark_args.h"
|
||||
#include "tools/benchmark/benchmark_codec.h"
|
||||
#include "tools/benchmark/benchmark_utils.h"
|
||||
#include "tools/file_io.h"
|
||||
#include "tools/speed_stats.h"
|
||||
#include "tools/thread_pool_internal.h"
|
||||
|
||||
namespace jpegxl {
|
||||
@@ -109,12 +102,6 @@ class JPEGCodec : public ImageCodec {
|
||||
jpeg_encoder_ = param;
|
||||
return true;
|
||||
}
|
||||
#if JPEGXL_ENABLE_JPEGLI
|
||||
if (param == "enc-jpegli") {
|
||||
jpeg_encoder_ = "jpegli";
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
if (param.compare(0, 3, "yuv") == 0) {
|
||||
chroma_subsampling_ = param.substr(3);
|
||||
return true;
|
||||
@@ -144,28 +131,6 @@ class JPEGCodec : public ImageCodec {
|
||||
enable_adaptive_quant_ = false;
|
||||
return true;
|
||||
}
|
||||
#if JPEGXL_ENABLE_JPEGLI
|
||||
if (param == "xyb") {
|
||||
xyb_mode_ = true;
|
||||
return true;
|
||||
}
|
||||
if (param == "std") {
|
||||
use_std_tables_ = true;
|
||||
return true;
|
||||
}
|
||||
if (param == "dec-jpegli") {
|
||||
jpeg_decoder_ = "jpegli";
|
||||
return true;
|
||||
}
|
||||
if (param.substr(0, 2) == "bd") {
|
||||
bitdepth_ = strtol(param.substr(2).c_str(), nullptr, 10);
|
||||
return true;
|
||||
}
|
||||
if (param.substr(0, 6) == "cquant") {
|
||||
num_colors_ = strtol(param.substr(6).c_str(), nullptr, 10);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -216,94 +181,54 @@ class JPEGCodec : public ImageCodec {
|
||||
}
|
||||
|
||||
double elapsed = 0.0;
|
||||
if (jpeg_encoder_ == "jpegli") {
|
||||
#if JPEGXL_ENABLE_JPEGLI
|
||||
jxl::extras::JpegSettings settings;
|
||||
settings.xyb = xyb_mode_;
|
||||
if (!xyb_mode_) {
|
||||
settings.use_std_quant_tables = use_std_tables_;
|
||||
}
|
||||
if (enc_quality_set_) {
|
||||
settings.quality = q_target_;
|
||||
} else {
|
||||
settings.distance = butteraugli_target_;
|
||||
}
|
||||
if (progressive_id_ >= 0) {
|
||||
settings.progressive_level = progressive_id_;
|
||||
}
|
||||
if (psnr_target_ > 0) {
|
||||
settings.psnr_target = psnr_target_;
|
||||
}
|
||||
if (jpegargs->search_tolerance > 0) {
|
||||
settings.search_tolerance = 0.01f * jpegargs->search_tolerance;
|
||||
}
|
||||
if (jpegargs->search_d_min > 0) {
|
||||
settings.min_distance = jpegargs->search_d_min;
|
||||
}
|
||||
if (jpegargs->search_d_max > 0) {
|
||||
settings.max_distance = jpegargs->search_d_max;
|
||||
}
|
||||
settings.chroma_subsampling = chroma_subsampling_;
|
||||
settings.use_adaptive_quantization = enable_adaptive_quant_;
|
||||
settings.libjpeg_quality = libjpeg_quality_;
|
||||
settings.libjpeg_chroma_subsampling = libjpeg_chroma_subsampling_;
|
||||
settings.optimize_coding = !fix_codes_;
|
||||
const double start = jxl::Now();
|
||||
JXL_RETURN_IF_ERROR(
|
||||
jxl::extras::EncodeJpeg(ppf, settings, pool, compressed));
|
||||
const double end = jxl::Now();
|
||||
elapsed = end - start;
|
||||
#endif
|
||||
} else {
|
||||
jxl::extras::EncodedImage encoded;
|
||||
std::unique_ptr<jxl::extras::Encoder> encoder =
|
||||
jxl::extras::GetJPEGEncoder();
|
||||
if (!encoder) {
|
||||
fprintf(stderr, "libjpeg codec is not supported\n");
|
||||
return false;
|
||||
}
|
||||
std::ostringstream os;
|
||||
os << static_cast<int>(std::round(q_target_));
|
||||
encoder->SetOption("q", os.str());
|
||||
encoder->SetOption("jpeg_encoder", jpeg_encoder_);
|
||||
if (!chroma_subsampling_.empty()) {
|
||||
encoder->SetOption("chroma_subsampling", chroma_subsampling_);
|
||||
}
|
||||
if (progressive_id_ >= 0) {
|
||||
encoder->SetOption("progressive", std::to_string(progressive_id_));
|
||||
}
|
||||
if (libjpeg_quality_ > 0) {
|
||||
encoder->SetOption("libjpeg_quality", std::to_string(libjpeg_quality_));
|
||||
}
|
||||
if (!libjpeg_chroma_subsampling_.empty()) {
|
||||
encoder->SetOption("libjpeg_chroma_subsampling",
|
||||
libjpeg_chroma_subsampling_);
|
||||
}
|
||||
if (fix_codes_) {
|
||||
encoder->SetOption("optimize", "OFF");
|
||||
}
|
||||
if (!enable_adaptive_quant_) {
|
||||
encoder->SetOption("adaptive_q", "OFF");
|
||||
}
|
||||
if (psnr_target_ > 0) {
|
||||
encoder->SetOption("psnr", std::to_string(psnr_target_));
|
||||
}
|
||||
if (!jpegargs->base_quant_fn.empty()) {
|
||||
encoder->SetOption("base_quant_fn", jpegargs->base_quant_fn);
|
||||
}
|
||||
SET_ENCODER_ARG(search_q_start);
|
||||
SET_ENCODER_ARG(search_q_min);
|
||||
SET_ENCODER_ARG(search_q_max);
|
||||
SET_ENCODER_ARG(search_q_precision);
|
||||
SET_ENCODER_ARG(search_tolerance);
|
||||
SET_ENCODER_ARG(search_first_iter_slope);
|
||||
SET_ENCODER_ARG(search_max_iters);
|
||||
const double start = jxl::Now();
|
||||
JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded, pool));
|
||||
const double end = jxl::Now();
|
||||
elapsed = end - start;
|
||||
*compressed = encoded.bitstreams.back();
|
||||
jxl::extras::EncodedImage encoded;
|
||||
std::unique_ptr<jxl::extras::Encoder> encoder =
|
||||
jxl::extras::GetJPEGEncoder();
|
||||
if (!encoder) {
|
||||
fprintf(stderr, "libjpeg codec is not supported\n");
|
||||
return false;
|
||||
}
|
||||
std::ostringstream os;
|
||||
os << static_cast<int>(std::round(q_target_));
|
||||
encoder->SetOption("q", os.str());
|
||||
encoder->SetOption("jpeg_encoder", jpeg_encoder_);
|
||||
if (!chroma_subsampling_.empty()) {
|
||||
encoder->SetOption("chroma_subsampling", chroma_subsampling_);
|
||||
}
|
||||
if (progressive_id_ >= 0) {
|
||||
encoder->SetOption("progressive", std::to_string(progressive_id_));
|
||||
}
|
||||
if (libjpeg_quality_ > 0) {
|
||||
encoder->SetOption("libjpeg_quality", std::to_string(libjpeg_quality_));
|
||||
}
|
||||
if (!libjpeg_chroma_subsampling_.empty()) {
|
||||
encoder->SetOption("libjpeg_chroma_subsampling",
|
||||
libjpeg_chroma_subsampling_);
|
||||
}
|
||||
if (fix_codes_) {
|
||||
encoder->SetOption("optimize", "OFF");
|
||||
}
|
||||
if (!enable_adaptive_quant_) {
|
||||
encoder->SetOption("adaptive_q", "OFF");
|
||||
}
|
||||
if (psnr_target_ > 0) {
|
||||
encoder->SetOption("psnr", std::to_string(psnr_target_));
|
||||
}
|
||||
if (!jpegargs->base_quant_fn.empty()) {
|
||||
encoder->SetOption("base_quant_fn", jpegargs->base_quant_fn);
|
||||
}
|
||||
SET_ENCODER_ARG(search_q_start);
|
||||
SET_ENCODER_ARG(search_q_min);
|
||||
SET_ENCODER_ARG(search_q_max);
|
||||
SET_ENCODER_ARG(search_q_precision);
|
||||
SET_ENCODER_ARG(search_tolerance);
|
||||
SET_ENCODER_ARG(search_first_iter_slope);
|
||||
SET_ENCODER_ARG(search_max_iters);
|
||||
const double start = jxl::Now();
|
||||
JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded, pool));
|
||||
const double end = jxl::Now();
|
||||
elapsed = end - start;
|
||||
*compressed = encoded.bitstreams.back();
|
||||
speed_stats->NotifyElapsed(elapsed);
|
||||
return true;
|
||||
}
|
||||
@@ -312,30 +237,14 @@ class JPEGCodec : public ImageCodec {
|
||||
const Span<const uint8_t> compressed, ThreadPool* pool,
|
||||
jxl::extras::PackedPixelFile* ppf,
|
||||
jpegxl::tools::SpeedStats* speed_stats) override {
|
||||
if (jpeg_decoder_ == "jpegli") {
|
||||
#if JPEGXL_ENABLE_JPEGLI
|
||||
std::vector<uint8_t> jpeg_bytes(compressed.data(),
|
||||
compressed.data() + compressed.size());
|
||||
const double start = jxl::Now();
|
||||
jxl::extras::JpegDecompressParams dparams;
|
||||
dparams.output_data_type =
|
||||
bitdepth_ > 8 ? JXL_TYPE_UINT16 : JXL_TYPE_UINT8;
|
||||
dparams.num_colors = num_colors_;
|
||||
JXL_RETURN_IF_ERROR(
|
||||
jxl::extras::DecodeJpeg(jpeg_bytes, dparams, pool, ppf));
|
||||
const double end = jxl::Now();
|
||||
speed_stats->NotifyElapsed(end - start);
|
||||
#endif
|
||||
} else {
|
||||
const double start = jxl::Now();
|
||||
jxl::extras::JPGDecompressParams dparams;
|
||||
dparams.num_colors = num_colors_;
|
||||
JXL_RETURN_IF_ERROR(
|
||||
jxl::extras::DecodeImageJPG(compressed, jxl::extras::ColorHints(),
|
||||
ppf, /*constraints=*/nullptr, &dparams));
|
||||
const double end = jxl::Now();
|
||||
speed_stats->NotifyElapsed(end - start);
|
||||
}
|
||||
const double start = jxl::Now();
|
||||
jxl::extras::JPGDecompressParams dparams;
|
||||
dparams.num_colors = num_colors_;
|
||||
JXL_RETURN_IF_ERROR(
|
||||
jxl::extras::DecodeImageJPG(compressed, jxl::extras::ColorHints(), ppf,
|
||||
/*constraints=*/nullptr, &dparams));
|
||||
const double end = jxl::Now();
|
||||
speed_stats->NotifyElapsed(end - start);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -349,17 +258,10 @@ class JPEGCodec : public ImageCodec {
|
||||
bool enc_quality_set_ = false;
|
||||
int libjpeg_quality_ = 0;
|
||||
std::string libjpeg_chroma_subsampling_;
|
||||
#if JPEGXL_ENABLE_JPEGLI
|
||||
bool xyb_mode_ = false;
|
||||
bool use_std_tables_ = false;
|
||||
#endif
|
||||
bool enable_adaptive_quant_ = true;
|
||||
// JPEG decoder and its parameters
|
||||
std::string jpeg_decoder_ = "libjpeg";
|
||||
int num_colors_ = 0;
|
||||
#if JPEGXL_ENABLE_JPEGLI
|
||||
size_t bitdepth_ = 8;
|
||||
#endif
|
||||
};
|
||||
|
||||
ImageCodec* CreateNewJPEGCodec(const BenchmarkArgs& args) {
|
||||
|
||||
@@ -1,265 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/extras/dec/decode.h"
|
||||
#include "lib/extras/enc/jpegli.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/extras/time.h"
|
||||
#include "lib/jxl/base/common.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "tools/args.h"
|
||||
#include "tools/cmdline.h"
|
||||
#include "tools/file_io.h"
|
||||
#include "tools/speed_stats.h"
|
||||
|
||||
namespace jpegxl {
|
||||
namespace tools {
|
||||
namespace {
|
||||
|
||||
struct Args {
|
||||
void AddCommandLineOptions(CommandLineParser* cmdline) {
|
||||
std::string input_help("the input can be ");
|
||||
input_help.append(jxl::extras::ListOfDecodeCodecs());
|
||||
cmdline->AddPositionalOption("INPUT", /* required = */ true, input_help,
|
||||
&file_in);
|
||||
cmdline->AddPositionalOption("OUTPUT", /* required = */ true,
|
||||
"the compressed JPEG output file", &file_out);
|
||||
|
||||
cmdline->AddOptionFlag('\0', "disable_output",
|
||||
"No output file will be written (for benchmarking)",
|
||||
&disable_output, &SetBooleanTrue, 1);
|
||||
|
||||
cmdline->AddOptionValue(
|
||||
'x', "dec-hints", "key=value",
|
||||
"color_space indicates the ColorEncoding, see Description();\n"
|
||||
" icc_pathname refers to a binary file containing an ICC profile.",
|
||||
&color_hints_proxy, &ParseAndAppendKeyValue<ColorHintsProxy>, 1);
|
||||
|
||||
opt_distance_id = cmdline->AddOptionValue(
|
||||
'd', "distance", "maxError",
|
||||
"Max. butteraugli distance, lower = higher quality.\n"
|
||||
" 1.0 = visually lossless (default).\n"
|
||||
" Recommended range: 0.5 .. 3.0. Allowed range: 0.0 ... 25.0.\n"
|
||||
" Mutually exclusive with --quality and --target_size.",
|
||||
&settings.distance, &ParseFloat);
|
||||
|
||||
opt_quality_id = cmdline->AddOptionValue(
|
||||
'q', "quality", "QUALITY",
|
||||
"Quality setting (is remapped to --distance)."
|
||||
" Default is quality 90.\n"
|
||||
" Quality values roughly match libjpeg quality.\n"
|
||||
" Recommended range: 68 .. 96. Allowed range: 1 .. 100.\n"
|
||||
" Mutually exclusive with --distance and --target_size.",
|
||||
&quality, &ParseSigned);
|
||||
|
||||
cmdline->AddOptionValue('\0', "chroma_subsampling", "444|440|422|420",
|
||||
"Chroma subsampling setting.",
|
||||
&settings.chroma_subsampling, &ParseString);
|
||||
|
||||
cmdline->AddOptionValue(
|
||||
'p', "progressive_level", "N",
|
||||
"Progressive level setting. Range: 0 .. 2.\n"
|
||||
" Default: 2. Higher number is more scans, 0 means sequential.",
|
||||
&settings.progressive_level, &ParseSigned);
|
||||
|
||||
cmdline->AddOptionFlag('\0', "xyb", "Convert to XYB colorspace",
|
||||
&settings.xyb, &SetBooleanTrue, 1);
|
||||
|
||||
cmdline->AddOptionFlag(
|
||||
'\0', "std_quant",
|
||||
"Use quantization tables based on Annex K of the JPEG standard.",
|
||||
&settings.use_std_quant_tables, &SetBooleanTrue, 1);
|
||||
|
||||
cmdline->AddOptionFlag(
|
||||
'\0', "noadaptive_quantization", "Disable adaptive quantization.",
|
||||
&settings.use_adaptive_quantization, &SetBooleanFalse, 1);
|
||||
|
||||
cmdline->AddOptionFlag(
|
||||
'\0', "fixed_code",
|
||||
"Disable Huffman code optimization. Must be used together with -p 0.",
|
||||
&settings.optimize_coding, &SetBooleanFalse, 1);
|
||||
|
||||
cmdline->AddOptionValue(
|
||||
'\0', "target_size", "N",
|
||||
"If non-zero, set target size in bytes. This is useful for image \n"
|
||||
" quality comparisons, but makes encoding speed up to 20x slower.\n"
|
||||
" Mutually exclusive with --distance and --quality.",
|
||||
&settings.target_size, &ParseUnsigned, 2);
|
||||
|
||||
cmdline->AddOptionValue('\0', "num_reps", "N",
|
||||
"How many times to compress. (For benchmarking).",
|
||||
&num_reps, &ParseUnsigned, 1);
|
||||
|
||||
cmdline->AddOptionFlag('\0', "quiet", "Suppress informative output", &quiet,
|
||||
&SetBooleanTrue, 1);
|
||||
|
||||
cmdline->AddOptionFlag(
|
||||
'v', "verbose",
|
||||
"Verbose output; can be repeated, also applies to help (!).", &verbose,
|
||||
&SetBooleanTrue);
|
||||
}
|
||||
|
||||
const char* file_in = nullptr;
|
||||
const char* file_out = nullptr;
|
||||
bool disable_output = false;
|
||||
ColorHintsProxy color_hints_proxy;
|
||||
jxl::extras::JpegSettings settings;
|
||||
int quality = 90;
|
||||
size_t num_reps = 1;
|
||||
bool quiet = false;
|
||||
bool verbose = false;
|
||||
// References (ids) of specific options to check if they were matched.
|
||||
CommandLineParser::OptionId opt_distance_id = -1;
|
||||
CommandLineParser::OptionId opt_quality_id = -1;
|
||||
};
|
||||
|
||||
bool ValidateArgs(const Args& args) {
|
||||
const jxl::extras::JpegSettings& settings = args.settings;
|
||||
if (settings.distance < 0.0 || settings.distance > 25.0) {
|
||||
fprintf(stderr, "Invalid --distance argument\n");
|
||||
return false;
|
||||
}
|
||||
if (args.quality <= 0 || args.quality > 100) {
|
||||
fprintf(stderr, "Invalid --quality argument\n");
|
||||
return false;
|
||||
}
|
||||
std::string cs = settings.chroma_subsampling;
|
||||
if (!cs.empty() && cs != "444" && cs != "440" && cs != "422" && cs != "420") {
|
||||
fprintf(stderr, "Invalid --chroma_subsampling argument\n");
|
||||
return false;
|
||||
}
|
||||
if (settings.progressive_level < 0 || settings.progressive_level > 2) {
|
||||
fprintf(stderr, "Invalid --progressive_level argument\n");
|
||||
return false;
|
||||
}
|
||||
if (settings.progressive_level > 0 && !settings.optimize_coding) {
|
||||
fprintf(stderr, "--fixed_code must be used together with -p 0\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetDistance(const Args& args, const CommandLineParser& cmdline,
|
||||
jxl::extras::JpegSettings* settings) {
|
||||
bool distance_set = cmdline.GetOption(args.opt_distance_id)->matched();
|
||||
bool quality_set = cmdline.GetOption(args.opt_quality_id)->matched();
|
||||
int num_quality_settings = (distance_set ? 1 : 0) + (quality_set ? 1 : 0) +
|
||||
(args.settings.target_size > 0 ? 1 : 0);
|
||||
if (num_quality_settings > 1) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Only one of --distance, --quality, or --target_size can be set.\n");
|
||||
return false;
|
||||
}
|
||||
if (quality_set) {
|
||||
settings->quality = args.quality;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int CJpegliMain(int argc, const char* argv[]) {
|
||||
Args args;
|
||||
CommandLineParser cmdline;
|
||||
args.AddCommandLineOptions(&cmdline);
|
||||
|
||||
if (!cmdline.Parse(argc, const_cast<const char**>(argv))) {
|
||||
// Parse already printed the actual error cause.
|
||||
fprintf(stderr, "Use '%s -h' for more information.\n", argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (cmdline.HelpFlagPassed() || !args.file_in) {
|
||||
cmdline.PrintHelp();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
if (!args.file_out && !args.disable_output) {
|
||||
fprintf(stderr,
|
||||
"No output file specified and --disable_output flag not passed.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (args.disable_output && !args.quiet) {
|
||||
fprintf(stderr,
|
||||
"Encoding will be performed, but the result will be discarded.\n");
|
||||
}
|
||||
|
||||
std::vector<uint8_t> input_bytes;
|
||||
if (!ReadFile(args.file_in, &input_bytes)) {
|
||||
fprintf(stderr, "Failed to read input image %s\n", args.file_in);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
jxl::extras::PackedPixelFile ppf;
|
||||
if (!jxl::extras::DecodeBytes(jxl::Bytes(input_bytes),
|
||||
args.color_hints_proxy.target, &ppf)) {
|
||||
fprintf(stderr, "Failed to decode input image %s\n", args.file_in);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!args.quiet) {
|
||||
fprintf(stderr, "Read %ux%u image, %" PRIuS " bytes.\n", ppf.info.xsize,
|
||||
ppf.info.ysize, input_bytes.size());
|
||||
}
|
||||
|
||||
if (!ValidateArgs(args) || !SetDistance(args, cmdline, &args.settings)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!args.quiet) {
|
||||
const jxl::extras::JpegSettings& s = args.settings;
|
||||
fprintf(stderr, "Encoding [%s%s d%.3f%s %sAQ p%d %s]\n",
|
||||
s.xyb ? "XYB" : "YUV", s.chroma_subsampling.c_str(), s.distance,
|
||||
s.use_std_quant_tables ? " StdQuant" : "",
|
||||
s.use_adaptive_quantization ? "" : "no", s.progressive_level,
|
||||
s.optimize_coding ? "OPT" : "FIX");
|
||||
}
|
||||
|
||||
jpegxl::tools::SpeedStats stats;
|
||||
std::vector<uint8_t> jpeg_bytes;
|
||||
for (size_t num_rep = 0; num_rep < args.num_reps; ++num_rep) {
|
||||
const double t0 = jxl::Now();
|
||||
if (!jxl::extras::EncodeJpeg(ppf, args.settings, nullptr, &jpeg_bytes)) {
|
||||
fprintf(stderr, "jpegli encoding failed\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const double t1 = jxl::Now();
|
||||
stats.NotifyElapsed(t1 - t0);
|
||||
stats.SetImageSize(ppf.info.xsize, ppf.info.ysize);
|
||||
}
|
||||
|
||||
if (args.file_out && !args.disable_output) {
|
||||
if (!WriteFile(args.file_out, jpeg_bytes)) {
|
||||
fprintf(stderr, "Could not write jpeg to %s\n", args.file_out);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
if (!args.quiet) {
|
||||
fprintf(stderr, "Compressed to %" PRIuS " bytes ", jpeg_bytes.size());
|
||||
const double num_pixels =
|
||||
static_cast<double>(ppf.info.xsize) * ppf.info.ysize;
|
||||
const double bpp =
|
||||
static_cast<double>(jpeg_bytes.size() * jxl::kBitsPerByte) / num_pixels;
|
||||
fprintf(stderr, "(%.3f bpp).\n", bpp);
|
||||
stats.Print(1);
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tools
|
||||
} // namespace jpegxl
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
return jpegxl::tools::CJpegliMain(argc, argv);
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <jxl/types.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/extras/dec/jpegli.h"
|
||||
#include "lib/extras/enc/encode.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/extras/time.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
#include "tools/cmdline.h"
|
||||
#include "tools/file_io.h"
|
||||
#include "tools/speed_stats.h"
|
||||
|
||||
namespace jpegxl {
|
||||
namespace tools {
|
||||
namespace {
|
||||
|
||||
struct Args {
|
||||
void AddCommandLineOptions(CommandLineParser* cmdline) {
|
||||
std::string output_help("The output can be ");
|
||||
output_help.append(jxl::extras::ListOfEncodeCodecs());
|
||||
cmdline->AddPositionalOption("INPUT", /* required = */ true,
|
||||
"The JPEG input file.", &file_in);
|
||||
|
||||
cmdline->AddPositionalOption("OUTPUT", /* required = */ true, output_help,
|
||||
&file_out);
|
||||
cmdline->AddOptionFlag('\0', "disable_output",
|
||||
"No output file will be written (for benchmarking)",
|
||||
&disable_output, &SetBooleanTrue);
|
||||
|
||||
cmdline->AddOptionValue('\0', "bitdepth", "8|16",
|
||||
"Sets the output bitdepth for integer based "
|
||||
"formats, can be 8 (default) "
|
||||
"or 16. Has no impact on PFM output.",
|
||||
&bitdepth, &ParseUnsigned);
|
||||
|
||||
cmdline->AddOptionValue('\0', "num_reps", "N",
|
||||
"Sets the number of times to decompress the image. "
|
||||
"Used for benchmarking, the default is 1.",
|
||||
&num_reps, &ParseUnsigned);
|
||||
|
||||
cmdline->AddOptionFlag('\0', "quiet", "Silence output (except for errors).",
|
||||
&quiet, &SetBooleanTrue);
|
||||
}
|
||||
|
||||
const char* file_in = nullptr;
|
||||
const char* file_out = nullptr;
|
||||
bool disable_output = false;
|
||||
size_t bitdepth = 8;
|
||||
size_t num_reps = 1;
|
||||
bool quiet = false;
|
||||
};
|
||||
|
||||
bool ValidateArgs(const Args& args) {
|
||||
if (args.bitdepth != 8 && args.bitdepth != 16) {
|
||||
fprintf(stderr, "Invalid --bitdepth argument\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetDecompressParams(const Args& args, const std::string& extension,
|
||||
jxl::extras::JpegDecompressParams* params) {
|
||||
if (extension == ".pfm") {
|
||||
params->output_data_type = JXL_TYPE_FLOAT;
|
||||
params->output_endianness = JXL_BIG_ENDIAN;
|
||||
} else if (args.bitdepth == 16) {
|
||||
params->output_data_type = JXL_TYPE_UINT16;
|
||||
params->output_endianness = JXL_BIG_ENDIAN;
|
||||
}
|
||||
if (extension == ".pgm") {
|
||||
params->force_grayscale = true;
|
||||
} else if (extension == ".ppm") {
|
||||
params->force_rgb = true;
|
||||
}
|
||||
}
|
||||
|
||||
int DJpegliMain(int argc, const char* argv[]) {
|
||||
Args args;
|
||||
CommandLineParser cmdline;
|
||||
args.AddCommandLineOptions(&cmdline);
|
||||
|
||||
if (!cmdline.Parse(argc, const_cast<const char**>(argv))) {
|
||||
// Parse already printed the actual error cause.
|
||||
fprintf(stderr, "Use '%s -h' for more information.\n", argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (cmdline.HelpFlagPassed() || !args.file_in) {
|
||||
cmdline.PrintHelp();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
if (!args.file_out && !args.disable_output) {
|
||||
fprintf(stderr,
|
||||
"No output file specified and --disable_output flag not passed.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (args.disable_output && !args.quiet) {
|
||||
fprintf(stderr,
|
||||
"Decoding will be performed, but the result will be discarded.\n");
|
||||
}
|
||||
|
||||
if (!ValidateArgs(args)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> jpeg_bytes;
|
||||
if (!ReadFile(args.file_in, &jpeg_bytes)) {
|
||||
fprintf(stderr, "Failed to read input image %s\n", args.file_in);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!args.quiet) {
|
||||
fprintf(stderr, "Read %" PRIuS " compressed bytes.\n", jpeg_bytes.size());
|
||||
}
|
||||
|
||||
std::string filename_out;
|
||||
std::string extension;
|
||||
if (args.file_out) {
|
||||
filename_out = std::string(args.file_out);
|
||||
size_t pos = filename_out.find_last_of('.');
|
||||
if (pos >= filename_out.size()) {
|
||||
fprintf(stderr, "Unrecognized output extension.\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
extension = filename_out.substr(pos);
|
||||
}
|
||||
|
||||
jxl::extras::JpegDecompressParams dparams;
|
||||
SetDecompressParams(args, extension, &dparams);
|
||||
|
||||
jxl::extras::PackedPixelFile ppf;
|
||||
jpegxl::tools::SpeedStats stats;
|
||||
for (size_t num_rep = 0; num_rep < args.num_reps; ++num_rep) {
|
||||
const double t0 = jxl::Now();
|
||||
if (!jxl::extras::DecodeJpeg(jpeg_bytes, dparams, nullptr, &ppf)) {
|
||||
fprintf(stderr, "jpegli decoding failed\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
const double t1 = jxl::Now();
|
||||
stats.NotifyElapsed(t1 - t0);
|
||||
stats.SetImageSize(ppf.info.xsize, ppf.info.ysize);
|
||||
}
|
||||
|
||||
if (!args.quiet) {
|
||||
stats.Print(1);
|
||||
}
|
||||
|
||||
if (args.disable_output) {
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
if (extension == ".pnm") {
|
||||
extension = ppf.info.num_color_channels == 3 ? ".ppm" : ".pgm";
|
||||
}
|
||||
|
||||
std::unique_ptr<jxl::extras::Encoder> encoder =
|
||||
jxl::extras::Encoder::FromExtension(extension);
|
||||
if (encoder == nullptr) {
|
||||
fprintf(stderr, "Can't decode to the file extension '%s'\n",
|
||||
extension.c_str());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
jxl::extras::EncodedImage encoded_image;
|
||||
if (!encoder->Encode(ppf, &encoded_image, nullptr) ||
|
||||
encoded_image.bitstreams.empty()) {
|
||||
fprintf(stderr, "Encode failed\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!WriteFile(filename_out, encoded_image.bitstreams[0])) {
|
||||
fprintf(stderr, "Failed to write output file %s\n", filename_out.c_str());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace tools
|
||||
} // namespace jpegxl
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
return jpegxl::tools::DJpegliMain(argc, argv);
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org.jpeg.jpegli.wrapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
|
||||
/** Jpegli JNI encoder wrapper. */
|
||||
public class Encoder {
|
||||
static {
|
||||
JniHelper.ensureInitialized();
|
||||
}
|
||||
|
||||
/** Utility library, disable object construction. */
|
||||
private Encoder() {}
|
||||
|
||||
// NB(eustas): moving serialized to native might improve performance
|
||||
public static class Config {
|
||||
private enum Options {
|
||||
QUALITY,
|
||||
}
|
||||
|
||||
private int[] serialized = new int[33];
|
||||
|
||||
public Config setQuality(int quality) {
|
||||
setOption(Options.QUALITY, quality);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void setOption(Options option, int value) {
|
||||
int index = option.ordinal();
|
||||
serialized[32] |= 1 << index;
|
||||
serialized[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static native int nativeInit();
|
||||
|
||||
private static native int nativeEncode(
|
||||
int width, int height, int[] config, int[] data, WritableByteChannel output);
|
||||
|
||||
private static class InitHelper {
|
||||
private static final int STATUS = nativeInit();
|
||||
}
|
||||
|
||||
public static boolean ensureInitialized() {
|
||||
return (InitHelper.STATUS == 0);
|
||||
}
|
||||
|
||||
/** One-shot encoding. */
|
||||
public static void encode(int[] color, int width, int height, Config config,
|
||||
WritableByteChannel output) throws IOException {
|
||||
if (!ensureInitialized()) {
|
||||
throw new IllegalStateException("Native library not initialized");
|
||||
}
|
||||
if (output == null) {
|
||||
throw new IllegalArgumentException("output is null");
|
||||
}
|
||||
if (color == null) {
|
||||
throw new IllegalArgumentException("color is null");
|
||||
}
|
||||
if ((width <= 0) || (height <= 0) || (color.length != width * height)) {
|
||||
throw new IllegalArgumentException("invalid image dimensions");
|
||||
}
|
||||
|
||||
// TODO(eustas): what is colorspace?
|
||||
|
||||
int status = nativeEncode(width, height, config.serialized, color, output);
|
||||
if (status != 0) {
|
||||
throw new IOException("Jpegli wrapper nativeProcess return code: " + status);
|
||||
}
|
||||
}
|
||||
|
||||
/** One-shot encoding. */
|
||||
public static void encode(int[] color, int width, int height, int quality, OutputStream output)
|
||||
throws IOException {
|
||||
if (output == null) {
|
||||
throw new IllegalArgumentException("output is null");
|
||||
}
|
||||
try (WritableByteChannel channel = Channels.newChannel(output)) {
|
||||
encode(color, width, height, new Config().setQuality(quality), channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org.jpeg.jpegli.wrapper;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Tests for jpegli encoder wrapper.
|
||||
*/
|
||||
public class EncoderTest {
|
||||
static void checkTrue(boolean condition) {
|
||||
if (!condition) {
|
||||
throw new IllegalStateException("check failed");
|
||||
}
|
||||
}
|
||||
|
||||
static void test64x64() throws IOException {
|
||||
int width = 64;
|
||||
int height = 64;
|
||||
int[] pixels = new int[width * height];
|
||||
for (int y = 0; y < 64; ++y) {
|
||||
for (int x = 0; x < 64; ++x) {
|
||||
pixels[x + 64 * y] = (x * 4) + ((y * 4) << 8);
|
||||
}
|
||||
}
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Encoder.encode(pixels, width, height, 99, out);
|
||||
System.err.println("Encoded size: " + out.size());
|
||||
checkTrue(out.size() > 0);
|
||||
byte[] encoded = Base64.getEncoder().encode(out.toByteArray());
|
||||
System.err.println("Base64: " + new String(encoded, UTF_8));
|
||||
}
|
||||
|
||||
// Simple executable to avoid extra dependencies.
|
||||
public static void main(String[] args) throws IOException {
|
||||
test64x64();
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package org.jpeg.jpegli.wrapper;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Helper for native library loading.
|
||||
*
|
||||
* Normally the wrapper is responsible for (lazy) library loading; though it is
|
||||
* sometimes necessary to make the moment of loading more deterministic.
|
||||
*/
|
||||
public class JniHelper {
|
||||
/** Utility library, disable object construction. */
|
||||
private JniHelper() {}
|
||||
|
||||
static {
|
||||
String jniLibrary = System.getProperty("org.jpeg.jpegli.wrapper.lib");
|
||||
if (jniLibrary != null) {
|
||||
try {
|
||||
System.load(new File(jniLibrary).getAbsolutePath());
|
||||
} catch (UnsatisfiedLinkError ex) {
|
||||
String message = "If the nested exception message says that some standard library (stdc++, "
|
||||
+ "tcmalloc, etc.) was not found, it is likely that JDK discovered by the "
|
||||
+ "build system overrides library search path. Try specifying a different "
|
||||
+ "JDK via JAVA_HOME environment variable and doing a clean build.";
|
||||
throw new IllegalStateException(message, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ensureInitialized() {
|
||||
// Do nothing, just trigger static initializer.
|
||||
}
|
||||
}
|
||||
Loaded 100 of 108 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user