Bug 1845026 - Update libjxl to 69d06e17771830beae7fb62b76de5978b52546fc r=saschanaz

Differential Revision: https://phabricator.services.mozilla.com/D184331
This commit is contained in:
Updatebot 2023-07-24 12:07:15 +00:00
parent 67fc396208
commit 8340c57e0c
158 changed files with 3263 additions and 4715 deletions

View File

@ -43,6 +43,7 @@ SOURCES += [
"/third_party/jpeg-xl/lib/jxl/dec_patch_dictionary.cc",
"/third_party/jpeg-xl/lib/jxl/dec_xyb.cc",
"/third_party/jpeg-xl/lib/jxl/decode.cc",
"/third_party/jpeg-xl/lib/jxl/enc_debug_image.cc",
"/third_party/jpeg-xl/lib/jxl/entropy_coder.cc",
"/third_party/jpeg-xl/lib/jxl/epf.cc",
"/third_party/jpeg-xl/lib/jxl/fast_dct.cc",

View File

@ -10,9 +10,9 @@ origin:
url: https://github.com/libjxl/libjxl
release: 190d44fbe463c5c3081081c80633639f69478d97 (2023-06-30T21:25:24Z).
release: 69d06e17771830beae7fb62b76de5978b52546fc (2023-07-21T14:55:24Z).
revision: 190d44fbe463c5c3081081c80633639f69478d97
revision: 69d06e17771830beae7fb62b76de5978b52546fc
license: Apache-2.0

View File

@ -10,7 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- encoder API: add `JxlEncoderSetExtraChannelDistance` to adjust the quality
of extra channels (like alpha) separately.
- encoder API: new api functions for streaming encoding:
- `JxlEncoderSetOutputCallback`,
- `JxlEncoderChunkedImageFrameStart`,
- `JxlEncoderChunkedImageFrameAddPart` and new
- `JXL_ENC_FRAME_SETTING_BUFFERING` enum value.
### Removed
- API: the Butteraugli API (`jxl/butteraugli.h`) was removed.
- encoder and decoder API: all deprecated functions were removed:
@ -23,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`JxlDecoderGetICCProfileSize`, and `JxlDecoderGetColorAsICCProfile`
changed: a deprecated unused argument was removed.
### Changed
### Changed
- changed the name of the cjxl flag `photon_noise` to `photon_noise_iso`
## [0.8.0] - 2023-01-18

View File

@ -171,6 +171,10 @@ set(JPEGXL_FORCE_NEON false CACHE BOOL
"Set flags to enable NEON in arm if not enabled by your toolchain.")
set(JPEGXL_TEST_TOOLS false CACHE BOOL
"Run scripts that test the encoding / decoding tools.")
set(JPEGXL_ENABLE_AVX512 false CACHE BOOL
"Build with AVX512 support (faster on CPUs that support it, but larger binary size).")
set(JPEGXL_ENABLE_AVX512_ZEN4 false CACHE BOOL
"Build with Zen4-optimized AVX512 support (faster on CPUs that support it, but larger binary size).")
# Force system dependencies.
set(JPEGXL_FORCE_SYSTEM_BROTLI false CACHE BOOL
@ -206,6 +210,20 @@ endif()
message(STATUS
"Compiled IDs C:${CMAKE_C_COMPILER_ID}, C++:${CMAKE_CXX_COMPILER_ID}")
# Always disable SSSE3 since it is rare to have SSSE3 but not SSE4
set(HWY_DISABLED_TARGETS "HWY_SSSE3")
if (NOT JPEGXL_ENABLE_AVX512)
message(STATUS "Disabled AVX512 (set JPEGXL_ENABLE_AVX512 to enable it)")
set(HWY_DISABLED_TARGETS "${HWY_DISABLED_TARGETS}|HWY_AVX3")
add_definitions(-DFJXL_ENABLE_AVX512=0)
endif()
if (NOT JPEGXL_ENABLE_AVX512_ZEN4)
message(STATUS "Disabled AVX512_ZEN4 (set JPEGXL_ENABLE_AVX512_ZEN4 to enable it)")
set(HWY_DISABLED_TARGETS "${HWY_DISABLED_TARGETS}|HWY_AVX3_ZEN4")
endif()
# CMAKE_EXPORT_COMPILE_COMMANDS is used to generate the compilation database
# used by clang-tidy.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@ -300,9 +318,11 @@ else ()
)
# TODO(eustas): JXL currently compiles, but does not pass tests...
if (NOT JXL_HWY_DISABLED_TARGETS_FORCED AND NOT JPEGXL_ENABLE_SIZELESS_VECTORS)
add_definitions(-DHWY_DISABLED_TARGETS=\(HWY_SVE|HWY_SVE2|HWY_SVE_256|HWY_SVE2_128|HWY_RVV\))
message("Warning: HWY_SVE, HWY_SVE2, HWY_SVE_256, HWY_SVE2_128 and HWY_RVV CPU targets are disabled")
if (NOT JXL_HWY_DISABLED_TARGETS_FORCED)
if (NOT JPEGXL_ENABLE_SIZELESS_VECTORS)
set(HWY_DISABLED_TARGETS "${HWY_DISABLED_TARGETS}|HWY_SVE|HWY_SVE2|HWY_SVE_256|HWY_SVE2_128|HWY_RVV")
endif()
add_definitions(-DHWY_DISABLED_TARGETS=\(${HWY_DISABLED_TARGETS}\))
endif()
# In CMake before 3.12 it is problematic to pass repeated flags like -Xclang.

View File

@ -150,7 +150,7 @@ SUBSTITUTIONS = {
}
YES_DEFINES = [
"C_ARITH_CODING_SUPPORTED", "D_ARITH_CODING_SUPPORTED",
"HAVE_BUILTIN_CTZL"
"HAVE_BUILTIN_CTZL", "MEM_SRCDST_SUPPORTED"
]
NO_DEFINES = [
"WITH_SIMD", "RIGHT_SHIFT_IS_UNSIGNED", "HAVE_INTRIN_H"
@ -167,6 +167,7 @@ SUBSTITUTIONS.update({
template = src + ".in",
out = src,
substitutions = SUBSTITUTIONS,
visibility = ["//visibility:public"],
) for src in ["jconfig.h", "jconfigint.h", "jversion.h"]
]
JPEG16_SOURCES = [
@ -285,6 +286,10 @@ cc_library(
includes = ["."],
visibility = ["//visibility:public"],
)
exports_files([
"jmorecfg.h",
"jpeglib.h",
])
""",
remote = "https://github.com/libjpeg-turbo/libjpeg-turbo.git",
tag = "2.1.91",

View File

@ -18,14 +18,11 @@ test_includes() {
if [ ! -e "$f" ]; then
continue
fi
# Check that the public files (in lib/include/ directory) don't use the full
# path to the public header since users of the library will include the
# library as: #include "jxl/foobar.h".
if [[ "${f#lib/include/}" != "${f}" ]]; then
if grep -i -H -n -E '#include\s*[<"]lib/include/jxl' "$f" >&2; then
echo "Don't add \"include/\" to the include path of public headers." >&2
ret=1
fi
# Check that the full paths to the public headers are not used, since users
# of the library will include the library as: #include "jxl/foobar.h".
if grep -i -H -n -E '#include\s*[<"]lib/include/jxl' "$f" >&2; then
echo "Don't add \"include/\" to the include path of public headers." >&2
ret=1
fi
if [[ "${f#third_party/}" == "$f" ]]; then

View File

@ -5,6 +5,11 @@ Files: *
Copyright: 2020 the JPEG XL Project
License: BSD-3-clause
Files: third_party/libjpeg-turbo/*
Copyright (C)2009-2023 D. R. Commander. All Rights Reserved.
Copyright (C)2015 Viktor Szathmáry. All Rights Reserved.
License: BSD-3-clause
Files: third_party/sjpeg/*
Copyright: 2017 Google, Inc
License: Apache-2.0

View File

@ -19,6 +19,7 @@ THIRD_PARTY_SKCMS="b25b07b4b07990811de121c0356155b2ba0f4318"
THIRD_PARTY_SJPEG="e5ab13008bb214deb66d5f3e17ca2f8dbff150bf"
THIRD_PARTY_ZLIB="cacf7f1d4e3d44d871b605da3b647f07d718623f"
THIRD_PARTY_LIBPNG="a40189cf881e9f0db80511c382292a5604c3c3d1"
THIRD_PARTY_LIBJPEG_TURBO="8ecba3647edb6dd940463fedf38ca33a8e2a73d1" # 2.1.5.1
# Download the target revision from GitHub.
download_github() {
@ -26,7 +27,7 @@ download_github() {
local project="$2"
local varname=`echo "$path" | tr '[:lower:]' '[:upper:]'`
varname="${varname/\//_}"
varname="${varname/[\/-]/_}"
local sha
eval "sha=\${${varname}}"
@ -85,6 +86,7 @@ EOF
"https://skia.googlesource.com/skcms/+archive/"
download_github third_party/zlib madler/zlib
download_github third_party/libpng glennrp/libpng
download_github third_party/libjpeg-turbo libjpeg-turbo/libjpeg-turbo
echo "Done."
}

View File

@ -154,11 +154,9 @@ bool DecodeJpegXlProgressive(const uint8_t* jxl, size_t size,
return false;
}
pixels.resize(xsize * ysize * 4);
void* pixels_buffer = (void*)pixels.data();
size_t pixels_buffer_size = pixels.size() * sizeof(float);
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format,
pixels_buffer,
pixels_buffer_size)) {
pixels.data(),
pixels.size())) {
fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n");
return false;
}

View File

@ -23,6 +23,8 @@ 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",
@ -55,6 +57,7 @@ load(
"libjxl_test_timeouts",
)
load("@bazel_skylib//rules:expand_template.bzl", "expand_template")
load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
DEFAULT_VISIBILITY = ["//:__subpackages__"]
@ -123,6 +126,33 @@ 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 + [JXL_EXPORT_H],
@ -131,6 +161,17 @@ 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")],
@ -190,7 +231,8 @@ cc_library(
compatible_with = DEFAULT_COMPATIBILITY,
deps = [
":jpegxl_private",
] + libjxl_deps_hwy + libjxl_deps_jpeg,
":libjpeg_includes",
] + libjxl_deps_hwy,
)
# TODO(eustas): build codecs separately?
@ -214,7 +256,7 @@ cc_library(
] + libjxl_deps_exr + libjxl_deps_gif + libjxl_deps_jpeg + libjxl_deps_png,
)
TESTLIB_FILES = libjxl_testlib_files + libjxl_jpegli_testlib_files
TESTLIB_FILES = libjxl_testlib_files + libjxl_jpegli_testlib_files + libjxl_jpegli_libjpeg_helper_files
cc_library(
name = "test_utils",

View File

@ -141,8 +141,7 @@ if(JPEGXL_ENABLE_TOOLS)
include(jxl_extras.cmake)
endif()
include(jxl_threads.cmake)
find_package(JPEG)
if (JPEG_FOUND AND JPEGXL_ENABLE_JPEGLI)
if (JPEGXL_ENABLE_JPEGLI)
include(jpegli.cmake)
endif()

View File

@ -8,24 +8,16 @@
#include <jxl/decode.h>
#include <jxl/types.h>
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/status.h"
#if JPEGXL_ENABLE_APNG
#include "lib/extras/enc/apng.h"
#endif
#if JPEGXL_ENABLE_JPEG
#include "lib/extras/enc/jpg.h"
#endif
#if JPEGXL_ENABLE_EXR
#include "lib/extras/enc/exr.h"
#endif
#include "lib/extras/dec/decode.h"
#include "lib/extras/enc/apng.h"
#include "lib/extras/enc/exr.h"
#include "lib/extras/enc/jpg.h"
#include "lib/extras/enc/pgx.h"
#include "lib/extras/enc/pnm.h"
#include "lib/extras/packed_image.h"
#include "lib/extras/packed_image_convert.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/image_bundle.h"
namespace jxl {
@ -68,22 +60,22 @@ Status Encode(const CodecInOut& io, const extras::Codec codec,
std::ostringstream os;
switch (codec) {
case extras::Codec::kPNG:
#if JPEGXL_ENABLE_APNG
encoder = extras::GetAPNGEncoder();
break;
#else
return JXL_FAILURE("JPEG XL was built without (A)PNG support");
#endif
if (encoder) {
break;
} else {
return JXL_FAILURE("JPEG XL was built without (A)PNG support");
}
case extras::Codec::kJPG:
#if JPEGXL_ENABLE_JPEG
format.data_type = JXL_TYPE_UINT8;
encoder = extras::GetJPEGEncoder();
os << io.jpeg_quality;
encoder->SetOption("q", os.str());
break;
#else
return JXL_FAILURE("JPEG XL was built without JPEG support");
#endif
if (encoder) {
os << io.jpeg_quality;
encoder->SetOption("q", os.str());
break;
} else {
return JXL_FAILURE("JPEG XL was built without JPEG support");
}
case extras::Codec::kPNM:
if (io.Main().HasAlpha()) {
encoder = extras::GetPAMEncoder();
@ -103,13 +95,13 @@ Status Encode(const CodecInOut& io, const extras::Codec codec,
case extras::Codec::kGIF:
return JXL_FAILURE("Encoding to GIF is not implemented");
case extras::Codec::kEXR:
#if JPEGXL_ENABLE_EXR
format.data_type = JXL_TYPE_FLOAT;
encoder = extras::GetEXREncoder();
break;
#else
return JXL_FAILURE("JPEG XL was built without OpenEXR support");
#endif
if (encoder) {
break;
} else {
return JXL_FAILURE("JPEG XL was built without OpenEXR support");
}
case extras::Codec::kJXL:
return JXL_FAILURE("TODO: encode using Codec::kJXL");

View File

@ -15,17 +15,9 @@
#include <vector>
#include "lib/extras/dec/decode.h"
#include "lib/extras/dec/pgx.h"
#include "lib/extras/dec/pnm.h"
#include "lib/extras/enc/encode.h"
#include "lib/extras/packed_image_convert.h"
#include "lib/jxl/base/random.h"
#include "lib/jxl/color_management.h"
#include "lib/jxl/enc_butteraugli_comparator.h"
#include "lib/jxl/enc_color_management.h"
#include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/image_test_utils.h"
#include "lib/jxl/test_utils.h"
#include "lib/jxl/testing.h"
@ -40,7 +32,6 @@ using ::testing::AllOf;
using ::testing::Contains;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::NotNull;
using ::testing::SizeIs;
std::string ExtensionFromCodec(Codec codec, const bool is_gray,
@ -57,17 +48,11 @@ std::string ExtensionFromCodec(Codec codec, const bool is_gray,
if (bits_per_sample == 32) return ".pfm";
if (has_alpha) return ".pam";
return is_gray ? ".pgm" : ".ppm";
case Codec::kGIF:
return ".gif";
case Codec::kEXR:
return ".exr";
case Codec::kJXL:
return ".jxl";
case Codec::kUnknown:
default:
return std::string();
}
JXL_UNREACHABLE;
return std::string();
}
void VerifySameImage(const PackedImage& im0, size_t bits_per_sample0,
@ -268,7 +253,10 @@ void TestRoundTrip(const TestImageParams& params, ThreadPool* pool) {
EncodedImage encoded;
auto encoder = Encoder::FromExtension(extension);
ASSERT_TRUE(encoder.get());
if (!encoder) {
fprintf(stderr, "Skipping test because of missing codec support.\n");
return;
}
ASSERT_TRUE(encoder->Encode(ppf_in, &encoded, pool));
ASSERT_EQ(encoded.bitstreams.size(), 1);
@ -325,7 +313,8 @@ TEST(CodecTest, TestRoundTrip) {
params.xsize = 7;
params.ysize = 4;
for (Codec codec : AvailableCodecs()) {
for (Codec codec :
{Codec::kPNG, Codec::kPNM, Codec::kPGX, Codec::kEXR, Codec::kJPG}) {
for (int bits_per_sample : {4, 8, 10, 12, 16, 32}) {
for (bool is_gray : {false, true}) {
for (bool add_alpha : {false, true}) {
@ -381,195 +370,6 @@ TEST(CodecTest, LosslessPNMRoundtrip) {
}
}
void DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
CodecInOut& io,
const ColorHints& color_hints = ColorHints()) {
const PaddedBytes orig = jxl::test::ReadTestData(pathname);
JXL_CHECK(SetFromBytes(Span<const uint8_t>(orig), color_hints, &io, pool));
const ImageBundle& ib1 = io.Main();
// Encode/Decode again to make sure Encode carries through all metadata.
std::vector<uint8_t> encoded;
JXL_CHECK(Encode(io, Codec::kPNG, io.metadata.m.color_encoding,
io.metadata.m.bit_depth.bits_per_sample, &encoded, pool));
CodecInOut io2;
JXL_CHECK(
SetFromBytes(Span<const uint8_t>(encoded), color_hints, &io2, pool));
const ImageBundle& ib2 = io2.Main();
EXPECT_EQ(Description(ib1.metadata()->color_encoding),
Description(ib2.metadata()->color_encoding));
EXPECT_EQ(Description(ib1.c_current()), Description(ib2.c_current()));
size_t bits_per_sample = io2.metadata.m.bit_depth.bits_per_sample;
// "Same" pixels?
double max_l1 = bits_per_sample <= 12 ? 1.3 : 2E-3;
double max_rel = bits_per_sample <= 12 ? 6E-3 : 1E-4;
if (ib1.metadata()->color_encoding.IsGray()) {
max_rel *= 2.0;
} else if (ib1.metadata()->color_encoding.primaries != Primaries::kSRGB) {
// Need more tolerance for large gamuts (anything but sRGB)
max_l1 *= 1.5;
max_rel *= 3.0;
}
JXL_ASSERT_OK(
VerifyRelativeError(ib1.color(), ib2.color(), max_l1, max_rel, _));
// Simulate the encoder removing profile and decoder restoring it.
if (!ib2.metadata()->color_encoding.WantICC()) {
io2.metadata.m.color_encoding.InternalRemoveICC();
EXPECT_TRUE(io2.metadata.m.color_encoding.CreateICC());
}
}
#if 0
TEST(CodecTest, TestMetadataSRGB) {
ThreadPoolForTests pool(12);
const char* paths[] = {"external/raw.pixls/DJI-FC6310-16bit_srgb8_v4_krita.png",
"external/raw.pixls/Google-Pixel2XL-16bit_srgb8_v4_krita.png",
"external/raw.pixls/HUAWEI-EVA-L09-16bit_srgb8_dt.png",
"external/raw.pixls/Nikon-D300-12bit_srgb8_dt.png",
"external/raw.pixls/Sony-DSC-RX1RM2-14bit_srgb8_v4_krita.png"};
for (const char* relative_pathname : paths) {
CodecInOut io;
DecodeRoundtrip(relative_pathname, Codec::kPNG, &pool, io);
EXPECT_EQ(8, io.metadata.m.bit_depth.bits_per_sample);
EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample);
EXPECT_EQ(0, io.metadata.m.bit_depth.exponent_bits_per_sample);
EXPECT_EQ(64, io.xsize());
EXPECT_EQ(64, io.ysize());
EXPECT_FALSE(io.metadata.m.HasAlpha());
const ColorEncoding& c_original = io.metadata.m.color_encoding;
EXPECT_FALSE(c_original.ICC().empty());
EXPECT_EQ(ColorSpace::kRGB, c_original.GetColorSpace());
EXPECT_EQ(WhitePoint::kD65, c_original.white_point);
EXPECT_EQ(Primaries::kSRGB, c_original.primaries);
EXPECT_TRUE(c_original.tf.IsSRGB());
}
}
TEST(CodecTest, TestMetadataLinear) {
ThreadPoolForTests pool(12);
const char* paths[3] = {
"external/raw.pixls/Google-Pixel2XL-16bit_acescg_g1_v4_krita.png",
"external/raw.pixls/HUAWEI-EVA-L09-16bit_709_g1_dt.png",
"external/raw.pixls/Nikon-D300-12bit_2020_g1_dt.png",
};
const WhitePoint white_points[3] = {WhitePoint::kCustom, WhitePoint::kD65,
WhitePoint::kD65};
const Primaries primaries[3] = {Primaries::kCustom, Primaries::kSRGB,
Primaries::k2100};
for (size_t i = 0; i < 3; ++i) {
CodecInOut io;
DecodeRoundtrip(paths[i], Codec::kPNG, &pool, io);
EXPECT_EQ(16, io.metadata.m.bit_depth.bits_per_sample);
EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample);
EXPECT_EQ(0, io.metadata.m.bit_depth.exponent_bits_per_sample);
EXPECT_EQ(64, io.xsize());
EXPECT_EQ(64, io.ysize());
EXPECT_FALSE(io.metadata.m.HasAlpha());
const ColorEncoding& c_original = io.metadata.m.color_encoding;
EXPECT_FALSE(c_original.ICC().empty());
EXPECT_EQ(ColorSpace::kRGB, c_original.GetColorSpace());
EXPECT_EQ(white_points[i], c_original.white_point);
EXPECT_EQ(primaries[i], c_original.primaries);
EXPECT_TRUE(c_original.tf.IsLinear());
}
}
TEST(CodecTest, TestMetadataICC) {
ThreadPoolForTests pool(12);
const char* paths[] = {
"external/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png",
"external/raw.pixls/Sony-DSC-RX1RM2-14bit_709_v4_krita.png",
};
for (const char* relative_pathname : paths) {
CodecInOut io;
DecodeRoundtrip(relative_pathname, Codec::kPNG, &pool, io);
EXPECT_GE(16, io.metadata.m.bit_depth.bits_per_sample);
EXPECT_LE(14, io.metadata.m.bit_depth.bits_per_sample);
EXPECT_EQ(64, io.xsize());
EXPECT_EQ(64, io.ysize());
EXPECT_FALSE(io.metadata.m.HasAlpha());
const ColorEncoding& c_original = io.metadata.m.color_encoding;
EXPECT_FALSE(c_original.ICC().empty());
EXPECT_EQ(RenderingIntent::kPerceptual, c_original.rendering_intent);
EXPECT_EQ(ColorSpace::kRGB, c_original.GetColorSpace());
EXPECT_EQ(WhitePoint::kD65, c_original.white_point);
EXPECT_EQ(Primaries::kSRGB, c_original.primaries);
EXPECT_EQ(TransferFunction::k709, c_original.tf.GetTransferFunction());
}
}
TEST(CodecTest, Testexternal/pngsuite) {
ThreadPoolForTests pool(12);
// Ensure we can load PNG with text, japanese UTF-8, compressed text.
CodecInOut tmp1;
DecodeRoundtrip("external/pngsuite/ct1n0g04.png", Codec::kPNG, &pool, tmp1);
CodecInOut tmp2;
DecodeRoundtrip("external/pngsuite/ctjn0g04.png", Codec::kPNG, &pool, tmp2);
CodecInOut tmp3;
DecodeRoundtrip("external/pngsuite/ctzn0g04.png", Codec::kPNG, &pool, tmp3);
// Extract gAMA
CodecInOut b1;
DecodeRoundtrip("external/pngsuite/g10n3p04.png", Codec::kPNG, &pool, b1);
EXPECT_TRUE(b1.metadata.color_encoding.tf.IsLinear());
// Extract cHRM
CodecInOut b_p;
DecodeRoundtrip("external/pngsuite/ccwn2c08.png", Codec::kPNG, &pool, b_p);
EXPECT_EQ(Primaries::kSRGB, b_p.metadata.color_encoding.primaries);
EXPECT_EQ(WhitePoint::kD65, b_p.metadata.color_encoding.white_point);
// Extract EXIF from (new-style) dedicated chunk
CodecInOut b_exif;
DecodeRoundtrip("external/pngsuite/exif2c08.png", Codec::kPNG, &pool, b_exif);
EXPECT_EQ(978, b_exif.blobs.exif.size());
}
#endif
void VerifyWideGamutMetadata(const std::string& relative_pathname,
const Primaries primaries, ThreadPool* pool) {
CodecInOut io;
DecodeRoundtrip(relative_pathname, pool, io);
EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample);
EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample);
EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample);
const ColorEncoding& c_original = io.metadata.m.color_encoding;
EXPECT_FALSE(c_original.ICC().empty());
EXPECT_EQ(RenderingIntent::kAbsolute, c_original.rendering_intent);
EXPECT_EQ(ColorSpace::kRGB, c_original.GetColorSpace());
EXPECT_EQ(WhitePoint::kD65, c_original.white_point);
EXPECT_EQ(primaries, c_original.primaries);
}
TEST(CodecTest, TestWideGamut) {
ThreadPoolForTests pool(12);
// VerifyWideGamutMetadata("external/wide-gamut-tests/P3-sRGB-color-bars.png",
// Primaries::kP3, &pool);
VerifyWideGamutMetadata("external/wide-gamut-tests/P3-sRGB-color-ring.png",
Primaries::kP3, &pool);
// VerifyWideGamutMetadata("external/wide-gamut-tests/R2020-sRGB-color-bars.png",
// Primaries::k2100, &pool);
// VerifyWideGamutMetadata("external/wide-gamut-tests/R2020-sRGB-color-ring.png",
// Primaries::k2100, &pool);
}
TEST(CodecTest, TestPNM) { TestCodecPNM(); }
TEST(CodecTest, FormatNegotiation) {
@ -611,7 +411,10 @@ TEST(CodecTest, EncodeToPNG) {
ThreadPool* const pool = nullptr;
std::unique_ptr<Encoder> png_encoder = Encoder::FromExtension(".png");
ASSERT_THAT(png_encoder, NotNull());
if (!png_encoder) {
fprintf(stderr, "Skipping test because of missing codec support.\n");
return;
}
const PaddedBytes original_png = jxl::test::ReadTestData(
"external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");

View File

@ -52,11 +52,14 @@
#include "lib/jxl/base/scope_guard.h"
#include "lib/jxl/common.h"
#include "lib/jxl/sanitizers.h"
#if JPEGXL_ENABLE_APNG
#include "png.h" /* original (unpatched) libpng is ok */
#endif
namespace jxl {
namespace extras {
#if JPEGXL_ENABLE_APNG
namespace {
constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
@ -558,10 +561,20 @@ int processing_finish(png_structp png_ptr, png_infop info_ptr,
}
} // namespace
#endif
bool CanDecodeAPNG() {
#if JPEGXL_ENABLE_APNG
return true;
#else
return false;
#endif
}
Status DecodeImageAPNG(const Span<const uint8_t> bytes,
const ColorHints& color_hints, PackedPixelFile* ppf,
const SizeConstraints* constraints) {
#if JPEGXL_ENABLE_APNG
Reader r;
unsigned int id, j, w, h, w0, h0, x0, y0;
unsigned int delay_num, delay_den, dop, bop, rowbytes, imagesize;
@ -956,6 +969,9 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
ppf->frames.back().frame_info.is_last = true;
return true;
#else
return false;
#endif
}
} // namespace extras

View File

@ -23,6 +23,8 @@ struct SizeConstraints;
namespace extras {
bool CanDecodeAPNG();
// Decodes `bytes` into `ppf`.
Status DecodeImageAPNG(Span<const uint8_t> bytes, const ColorHints& color_hints,
PackedPixelFile* ppf,

View File

@ -7,18 +7,10 @@
#include <locale>
#if JPEGXL_ENABLE_APNG
#include "lib/extras/dec/apng.h"
#endif
#if JPEGXL_ENABLE_EXR
#include "lib/extras/dec/exr.h"
#endif
#if JPEGXL_ENABLE_GIF
#include "lib/extras/dec/gif.h"
#endif
#if JPEGXL_ENABLE_JPEG
#include "lib/extras/dec/jpg.h"
#endif
#include "lib/extras/dec/jxl.h"
#include "lib/extras/dec/pgx.h"
#include "lib/extras/dec/pnm.h"
@ -53,25 +45,6 @@ void BasenameAndExtension(std::string path, std::string* basename,
} // namespace
std::vector<Codec> AvailableCodecs() {
std::vector<Codec> out;
#if JPEGXL_ENABLE_APNG
out.push_back(Codec::kPNG);
#endif
#if JPEGXL_ENABLE_EXR
out.push_back(Codec::kEXR);
#endif
#if JPEGXL_ENABLE_GIF
out.push_back(Codec::kGIF);
#endif
#if JPEGXL_ENABLE_JPEG
out.push_back(Codec::kJPG);
#endif
out.push_back(Codec::kPGX);
out.push_back(Codec::kPNM);
return out;
}
Codec CodecFromPath(std::string path, size_t* JXL_RESTRICT bits_per_sample,
std::string* basename, std::string* extension) {
std::string base;
@ -106,6 +79,25 @@ Codec CodecFromPath(std::string path, size_t* JXL_RESTRICT bits_per_sample,
return Codec::kUnknown;
}
bool CanDecode(Codec codec) {
switch (codec) {
case Codec::kEXR:
return CanDecodeEXR();
case Codec::kGIF:
return CanDecodeGIF();
case Codec::kJPG:
return CanDecodeJPG();
case Codec::kPNG:
return CanDecodeAPNG();
case Codec::kPNM:
case Codec::kPGX:
case Codec::kJXL:
return true;
default:
return false;
}
}
Status DecodeBytes(const Span<const uint8_t> bytes,
const ColorHints& color_hints, extras::PackedPixelFile* ppf,
const SizeConstraints* constraints, Codec* orig_codec) {
@ -118,11 +110,9 @@ Status DecodeBytes(const Span<const uint8_t> bytes,
ppf->info.orientation = JXL_ORIENT_IDENTITY;
const auto choose_codec = [&]() -> Codec {
#if JPEGXL_ENABLE_APNG
if (DecodeImageAPNG(bytes, color_hints, ppf, constraints)) {
return Codec::kPNG;
}
#endif
if (DecodeImagePGX(bytes, color_hints, ppf, constraints)) {
return Codec::kPGX;
}
@ -135,21 +125,15 @@ Status DecodeBytes(const Span<const uint8_t> bytes,
ppf)) {
return Codec::kJXL;
}
#if JPEGXL_ENABLE_GIF
if (DecodeImageGIF(bytes, color_hints, ppf, constraints)) {
return Codec::kGIF;
}
#endif
#if JPEGXL_ENABLE_JPEG
if (DecodeImageJPG(bytes, color_hints, ppf, constraints)) {
return Codec::kJPG;
}
#endif
#if JPEGXL_ENABLE_EXR
if (DecodeImageEXR(bytes, color_hints, ppf, constraints)) {
return Codec::kEXR;
}
#endif
return Codec::kUnknown;
};

View File

@ -24,7 +24,7 @@ struct SizeConstraints;
namespace extras {
// Codecs supported by CodecInOut::Encode.
// Codecs supported by DecodeBytes.
enum class Codec : uint32_t {
kUnknown, // for CodecFromPath
kPNG,
@ -36,7 +36,7 @@ enum class Codec : uint32_t {
kJXL
};
std::vector<Codec> AvailableCodecs();
bool CanDecode(Codec codec);
// If and only if extension is ".pfm", *bits_per_sample is updated to 32 so
// that Encode() would encode to PFM instead of PPM.

View File

@ -5,20 +5,22 @@
#include "lib/extras/dec/exr.h"
#if JPEGXL_ENABLE_EXR
#include <ImfChromaticitiesAttribute.h>
#include <ImfIO.h>
#include <ImfRgbaFile.h>
#include <ImfStandardAttributes.h>
#endif
#include <vector>
namespace jxl {
namespace extras {
#if JPEGXL_ENABLE_EXR
namespace {
namespace OpenEXR = OPENEXR_IMF_NAMESPACE;
namespace Imath = IMATH_NAMESPACE;
// OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using
// uint64_t as recommended causes build failures with previous OpenEXR versions
@ -60,10 +62,20 @@ class InMemoryIStream : public OpenEXR::IStream {
};
} // namespace
#endif
bool CanDecodeEXR() {
#if JPEGXL_ENABLE_EXR
return true;
#else
return false;
#endif
}
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
PackedPixelFile* ppf,
const SizeConstraints* constraints) {
#if JPEGXL_ENABLE_EXR
InMemoryIStream is(bytes);
#ifdef __EXCEPTIONS
@ -145,6 +157,7 @@ Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
std::min(input.dataWindow().max.x, input.displayWindow().max.x);
++exr_x) {
const int image_x = exr_x - input.displayWindow().min.x;
// TODO(eustas): UB: OpenEXR::Rgba is not TriviallyCopyable
memcpy(row + image_x * pixel_size,
input_row + (exr_x - input.dataWindow().min.x), pixel_size);
}
@ -179,6 +192,9 @@ Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
}
ppf->info.intensity_target = intensity_target;
return true;
#else
return false;
#endif
}
} // namespace extras

View File

@ -21,6 +21,8 @@ struct SizeConstraints;
namespace extras {
bool CanDecodeEXR();
// Decodes `bytes` into `ppf`. color_hints are ignored.
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
PackedPixelFile* ppf,

View File

@ -5,7 +5,9 @@
#include "lib/extras/dec/gif.h"
#if JPEGXL_ENABLE_GIF
#include <gif_lib.h>
#endif
#include <jxl/codestream_header.h>
#include <string.h>
@ -20,6 +22,7 @@
namespace jxl {
namespace extras {
#if JPEGXL_ENABLE_GIF
namespace {
struct ReadState {
@ -53,12 +56,21 @@ void ensure_have_alpha(PackedFrame* frame) {
std::fill_n(static_cast<uint8_t*>(frame->extra_channels[0].pixels()),
frame->color.xsize * frame->color.ysize, 255u);
}
} // namespace
#endif
bool CanDecodeGIF() {
#if JPEGXL_ENABLE_GIF
return true;
#else
return false;
#endif
}
Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
PackedPixelFile* ppf,
const SizeConstraints* constraints) {
#if JPEGXL_ENABLE_GIF
int error = GIF_OK;
ReadState state = {bytes};
const auto ReadFromSpan = [](GifFileType* const gif, GifByteType* const bytes,
@ -394,6 +406,9 @@ Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
}
}
return true;
#else
return false;
#endif
}
} // namespace extras

View File

@ -22,6 +22,8 @@ struct SizeConstraints;
namespace extras {
bool CanDecodeGIF();
// Decodes `bytes` into `ppf`. color_hints are ignored.
Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
PackedPixelFile* ppf,

View File

@ -5,8 +5,10 @@
#include "lib/extras/dec/jpg.h"
#if JPEGXL_ENABLE_JPEG
#include <jpeglib.h>
#include <setjmp.h>
#endif
#include <stdint.h>
#include <algorithm>
@ -21,6 +23,7 @@
namespace jxl {
namespace extras {
#if JPEGXL_ENABLE_JPEG
namespace {
constexpr unsigned char kICCSignature[12] = {
@ -175,11 +178,21 @@ void UnmapColors(uint8_t* row, size_t xsize, int components,
}
} // namespace
#endif
bool CanDecodeJPG() {
#if JPEGXL_ENABLE_JPEG
return true;
#else
return false;
#endif
}
Status DecodeImageJPG(const Span<const uint8_t> bytes,
const ColorHints& color_hints, PackedPixelFile* ppf,
const SizeConstraints* constraints,
const JPGDecompressParams* dparams) {
#if JPEGXL_ENABLE_JPEG
// Don't do anything for non-JPEG files (no need to report an error)
if (!IsJPG(bytes)) return false;
@ -316,6 +329,9 @@ Status DecodeImageJPG(const Span<const uint8_t> bytes,
};
return try_catch_block();
#else
return false;
#endif
}
} // namespace extras

View File

@ -23,6 +23,8 @@ struct SizeConstraints;
namespace extras {
bool CanDecodeJPG();
struct JPGDecompressParams {
int num_colors = 0;
bool two_pass_quant = false;

View File

@ -347,21 +347,23 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
}
size_t icc_size = 0;
JxlColorProfileTarget target = JXL_COLOR_PROFILE_TARGET_DATA;
if (JXL_DEC_SUCCESS !=
JxlDecoderGetICCProfileSize(dec, target, &icc_size)) {
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
}
if (icc_size != 0) {
ppf->icc.resize(icc_size);
if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile(
dec, target, ppf->icc.data(), icc_size)) {
fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
return false;
}
}
ppf->color_encoding.color_space = JXL_COLOR_SPACE_UNKNOWN;
if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsEncodedProfile(
dec, target, &ppf->color_encoding)) {
ppf->color_encoding.color_space = JXL_COLOR_SPACE_UNKNOWN;
dec, target, &ppf->color_encoding) ||
dparams.need_icc) {
// only get ICC if it is not an Enum color encoding
if (JXL_DEC_SUCCESS !=
JxlDecoderGetICCProfileSize(dec, target, &icc_size)) {
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
}
if (icc_size != 0) {
ppf->icc.resize(icc_size);
if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile(
dec, target, ppf->icc.data(), icc_size)) {
fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
return false;
}
}
}
icc_size = 0;
target = JXL_COLOR_PROFILE_TARGET_ORIGINAL;

View File

@ -41,6 +41,10 @@ struct JXLDecompressParams {
// Whether truncated input should be treated as an error.
bool allow_partial_input = false;
// Set to true if an ICC profile has to be synthesized for Enum color
// encodings
bool need_icc = false;
// How many passes to decode at most. By default, decode everything.
uint32_t max_passes = std::numeric_limits<uint32_t>::max();

View File

@ -45,11 +45,14 @@
#include "lib/extras/exif.h"
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/printf_macros.h"
#if JPEGXL_ENABLE_APNG
#include "png.h" /* original (unpatched) libpng is ok */
#endif
namespace jxl {
namespace extras {
#if JPEGXL_ENABLE_APNG
namespace {
constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
@ -158,7 +161,7 @@ class BlobsWriterPNG {
}
};
void MaybeAddCICP(JxlColorEncoding c_enc, png_structp png_ptr,
void MaybeAddCICP(const JxlColorEncoding& c_enc, png_structp png_ptr,
png_infop info_ptr) {
png_byte cicp_data[4] = {};
png_unknown_chunk cicp_chunk;
@ -195,6 +198,51 @@ void MaybeAddCICP(JxlColorEncoding c_enc, png_structp png_ptr,
png_set_unknown_chunks(png_ptr, info_ptr, &cicp_chunk, 1);
}
bool MaybeAddSRGB(const JxlColorEncoding& c_enc, png_structp png_ptr,
png_infop info_ptr) {
if (c_enc.transfer_function == JXL_TRANSFER_FUNCTION_SRGB &&
(c_enc.color_space == JXL_COLOR_SPACE_GRAY ||
(c_enc.color_space == JXL_COLOR_SPACE_RGB &&
c_enc.primaries == JXL_PRIMARIES_SRGB &&
c_enc.white_point == JXL_WHITE_POINT_D65))) {
png_set_sRGB(png_ptr, info_ptr, c_enc.rendering_intent);
png_set_cHRM_fixed(png_ptr, info_ptr, 31270, 32900, 64000, 33000, 30000,
60000, 15000, 6000);
png_set_gAMA_fixed(png_ptr, info_ptr, 45455);
return true;
}
return false;
}
void MaybeAddCHRM(const JxlColorEncoding& c_enc, png_structp png_ptr,
png_infop info_ptr) {
if (c_enc.color_space != JXL_COLOR_SPACE_RGB) return;
if (c_enc.primaries == 0) return;
png_set_cHRM(png_ptr, info_ptr, c_enc.white_point_xy[0],
c_enc.white_point_xy[1], c_enc.primaries_red_xy[0],
c_enc.primaries_red_xy[1], c_enc.primaries_green_xy[0],
c_enc.primaries_green_xy[1], c_enc.primaries_blue_xy[0],
c_enc.primaries_blue_xy[1]);
}
void MaybeAddGAMA(const JxlColorEncoding& c_enc, png_structp png_ptr,
png_infop info_ptr) {
switch (c_enc.transfer_function) {
case JXL_TRANSFER_FUNCTION_LINEAR:
png_set_gAMA_fixed(png_ptr, info_ptr, PNG_FP_1);
break;
case JXL_TRANSFER_FUNCTION_SRGB:
png_set_gAMA_fixed(png_ptr, info_ptr, 45455);
break;
case JXL_TRANSFER_FUNCTION_GAMMA:
png_set_gAMA(png_ptr, info_ptr, c_enc.gamma);
break;
default:;
// No gAMA chunk.
}
}
Status APNGEncoder::EncodePackedPixelFileToAPNG(
const PackedPixelFile& ppf, ThreadPool* pool,
std::vector<uint8_t>* bytes) const {
@ -274,11 +322,17 @@ Status APNGEncoder::EncodePackedPixelFileToAPNG(
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
PNG_FILTER_TYPE_BASE);
if (count == 0) {
MaybeAddCICP(ppf.color_encoding, png_ptr, info_ptr);
if (!ppf.icc.empty()) {
png_set_benign_errors(png_ptr, 1);
png_set_iCCP(png_ptr, info_ptr, "1", 0, ppf.icc.data(), ppf.icc.size());
if (!MaybeAddSRGB(ppf.color_encoding, png_ptr, info_ptr)) {
MaybeAddCICP(ppf.color_encoding, png_ptr, info_ptr);
if (!ppf.icc.empty()) {
png_set_benign_errors(png_ptr, 1);
png_set_iCCP(png_ptr, info_ptr, "1", 0, ppf.icc.data(),
ppf.icc.size());
}
MaybeAddCHRM(ppf.color_encoding, png_ptr, info_ptr);
MaybeAddGAMA(ppf.color_encoding, png_ptr, info_ptr);
}
std::vector<std::string> textstrings;
JXL_RETURN_IF_ERROR(BlobsWriterPNG::Encode(ppf.metadata, &textstrings));
for (size_t kk = 0; kk + 1 < textstrings.size(); kk += 2) {
@ -362,9 +416,14 @@ Status APNGEncoder::EncodePackedPixelFileToAPNG(
}
} // namespace
#endif
std::unique_ptr<Encoder> GetAPNGEncoder() {
#if JPEGXL_ENABLE_APNG
return jxl::make_unique<APNGEncoder>();
#else
return nullptr;
#endif
}
} // namespace extras

View File

@ -7,15 +7,9 @@
#include <locale>
#if JPEGXL_ENABLE_APNG
#include "lib/extras/enc/apng.h"
#endif
#if JPEGXL_ENABLE_EXR
#include "lib/extras/enc/exr.h"
#endif
#if JPEGXL_ENABLE_JPEG
#include "lib/extras/enc/jpg.h"
#endif
#include "lib/extras/enc/npy.h"
#include "lib/extras/enc/pgx.h"
#include "lib/extras/enc/pnm.h"
@ -139,28 +133,16 @@ std::unique_ptr<Encoder> Encoder::FromExtension(std::string extension) {
std::transform(
extension.begin(), extension.end(), extension.begin(),
[](char c) { return std::tolower(c, std::locale::classic()); });
#if JPEGXL_ENABLE_APNG
if (extension == ".png" || extension == ".apng") return GetAPNGEncoder();
#endif
#if JPEGXL_ENABLE_JPEG
if (extension == ".jpg") return GetJPEGEncoder();
if (extension == ".jpeg") return GetJPEGEncoder();
#endif
if (extension == ".npy") return GetNumPyEncoder();
if (extension == ".pgx") return GetPGXEncoder();
if (extension == ".pam") return GetPAMEncoder();
if (extension == ".pgm") return GetPGMEncoder();
if (extension == ".ppm") return GetPPMEncoder();
if (extension == ".pfm") return GetPFMEncoder();
#if JPEGXL_ENABLE_EXR
if (extension == ".exr") return GetEXREncoder();
#endif
return nullptr;
}

View File

@ -5,10 +5,12 @@
#include "lib/extras/enc/exr.h"
#if JPEGXL_ENABLE_EXR
#include <ImfChromaticitiesAttribute.h>
#include <ImfIO.h>
#include <ImfRgbaFile.h>
#include <ImfStandardAttributes.h>
#endif
#include <jxl/codestream_header.h>
#include <vector>
@ -19,6 +21,7 @@
namespace jxl {
namespace extras {
#if JPEGXL_ENABLE_EXR
namespace {
namespace OpenEXR = OPENEXR_IMF_NAMESPACE;
@ -191,9 +194,14 @@ class EXREncoder : public Encoder {
};
} // namespace
#endif
std::unique_ptr<Encoder> GetEXREncoder() {
#if JPEGXL_ENABLE_EXR
return jxl::make_unique<EXREncoder>();
#else
return nullptr;
#endif
}
} // namespace extras

View File

@ -29,9 +29,6 @@ void MyErrorExit(j_common_ptr cinfo) {
Status VerifyInput(const PackedPixelFile& ppf) {
const JxlBasicInfo& info = ppf.info;
JXL_RETURN_IF_ERROR(Encoder::VerifyBasicInfo(info));
if (info.alpha_bits > 0) {
return JXL_FAILURE("Alpha is not supported for JPEG output.");
}
if (ppf.frames.size() != 1) {
return JXL_FAILURE("JPEG input must have exactly one frame.");
}
@ -51,12 +48,12 @@ Status VerifyInput(const PackedPixelFile& ppf) {
return true;
}
Status GetColorEncoding(const PackedPixelFile& ppf,
Status GetColorEncoding(const PackedPixelFile& ppf, const JxlCmsInterface* cms,
ColorEncoding* color_encoding) {
if (!ppf.icc.empty()) {
PaddedBytes icc;
icc.assign(ppf.icc.data(), ppf.icc.data() + ppf.icc.size());
JXL_RETURN_IF_ERROR(color_encoding->SetICC(std::move(icc)));
JXL_RETURN_IF_ERROR(color_encoding->SetICC(std::move(icc), cms));
} else {
JXL_RETURN_IF_ERROR(ConvertExternalToInternalColorEncoding(
ppf.color_encoding, color_encoding));
@ -329,10 +326,12 @@ Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings,
}
JXL_RETURN_IF_ERROR(VerifyInput(ppf));
ColorEncoding color_encoding;
JXL_RETURN_IF_ERROR(GetColorEncoding(ppf, &color_encoding));
const JxlCmsInterface& cms = GetJxlCms();
ColorSpaceTransform c_transform(GetJxlCms());
ColorEncoding color_encoding;
JXL_RETURN_IF_ERROR(GetColorEncoding(ppf, &cms, &color_encoding));
ColorSpaceTransform c_transform(cms);
ColorEncoding xyb_encoding;
if (jpeg_settings.xyb) {
if (ppf.info.num_color_channels != 3) {
@ -491,10 +490,25 @@ Status EncodeJpeg(const PackedPixelFile& ppf, const JpegSettings& jpeg_settings,
}
} else {
row_bytes.resize(image.stride);
for (size_t y = 0; y < info.ysize; ++y) {
memcpy(&row_bytes[0], pixels + y * image.stride, image.stride);
JSAMPROW row[] = {row_bytes.data()};
jpegli_write_scanlines(&cinfo, row, 1);
if (cinfo.num_components == (int)image.format.num_channels) {
for (size_t y = 0; y < info.ysize; ++y) {
memcpy(&row_bytes[0], 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) {
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);

View File

@ -5,8 +5,10 @@
#include "lib/extras/enc/jpg.h"
#if JPEGXL_ENABLE_JPEG
#include <jpeglib.h>
#include <setjmp.h>
#endif
#include <stdint.h>
#include <algorithm>
@ -31,6 +33,7 @@
namespace jxl {
namespace extras {
#if JPEGXL_ENABLE_JPEG
namespace {
constexpr unsigned char kICCSignature[12] = {
@ -313,13 +316,39 @@ Status EncodeWithLibJpeg(const PackedImage& image, const JxlBasicInfo& info,
if (cinfo.input_components > 3 || cinfo.input_components < 0)
return JXL_FAILURE("invalid numbers of components");
std::vector<uint8_t> raw_bytes(image.pixels_size);
memcpy(&raw_bytes[0], reinterpret_cast<const uint8_t*>(image.pixels()),
image.pixels_size);
for (size_t y = 0; y < info.ysize; ++y) {
JSAMPROW row[] = {raw_bytes.data() + y * image.stride};
jpeg_write_scanlines(&cinfo, row, 1);
std::vector<uint8_t> row_bytes(image.stride);
const uint8_t* pixels = reinterpret_cast<const uint8_t*>(image.pixels());
if (cinfo.num_components == (int)image.format.num_channels &&
image.format.data_type == JXL_TYPE_UINT8) {
for (size_t y = 0; y < info.ysize; ++y) {
memcpy(&row_bytes[0], pixels + y * image.stride, image.stride);
JSAMPROW row[] = {row_bytes.data()};
jpeg_write_scanlines(&cinfo, row, 1);
}
} else if (image.format.data_type == JXL_TYPE_UINT8) {
for (size_t y = 0; y < info.ysize; ++y) {
const uint8_t* image_row = pixels + y * image.stride;
for (size_t x = 0; x < info.xsize; ++x) {
const uint8_t* image_pixel = image_row + x * image.pixel_stride();
memcpy(&row_bytes[x * cinfo.num_components], image_pixel,
cinfo.num_components);
}
JSAMPROW row[] = {row_bytes.data()};
jpeg_write_scanlines(&cinfo, row, 1);
}
} else {
for (size_t y = 0; y < info.ysize; ++y) {
const uint8_t* image_row = pixels + y * image.stride;
for (size_t x = 0; x < info.xsize; ++x) {
const uint8_t* image_pixel = image_row + x * image.pixel_stride();
for (int c = 0; c < cinfo.num_components; ++c) {
uint32_t val16 = (image_pixel[2 * c] << 8) + image_pixel[2 * c + 1];
row_bytes[x * cinfo.num_components + c] = (val16 + 128) / 257;
}
}
JSAMPROW row[] = {row_bytes.data()};
jpeg_write_scanlines(&cinfo, row, 1);
}
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
@ -412,6 +441,12 @@ Status EncodeWithSJpeg(const PackedImage& image, const JxlBasicInfo& info,
#if !JPEGXL_ENABLE_SJPEG
return JXL_FAILURE("JPEG XL was built without sjpeg support");
#else
if (image.format.data_type != JXL_TYPE_UINT8) {
return JXL_FAILURE("Unsupported pixel data type");
}
if (info.alpha_bits > 0) {
return JXL_FAILURE("alpha is not supported");
}
sjpeg::EncoderParam param(params.quality);
if (!icc.empty()) {
param.iccp.assign(icc.begin(), icc.end());
@ -474,12 +509,6 @@ Status EncodeImageJPG(const PackedImage& image, const JxlBasicInfo& info,
std::vector<uint8_t> exif, JpegEncoder encoder,
const JpegParams& params, ThreadPool* pool,
std::vector<uint8_t>* bytes) {
if (image.format.data_type != JXL_TYPE_UINT8) {
return JXL_FAILURE("Unsupported pixel data type");
}
if (info.alpha_bits > 0) {
return JXL_FAILURE("alpha is not supported");
}
if (params.quality > 100) {
return JXL_FAILURE("please specify a 0-100 JPEG quality");
}
@ -503,13 +532,17 @@ Status EncodeImageJPG(const PackedImage& image, const JxlBasicInfo& info,
class JPEGEncoder : public Encoder {
std::vector<JxlPixelFormat> AcceptedFormats() const override {
std::vector<JxlPixelFormat> formats;
for (const uint32_t num_channels : {1, 3}) {
for (const uint32_t num_channels : {1, 2, 3, 4}) {
for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) {
formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
/*data_type=*/JXL_TYPE_UINT8,
/*endianness=*/endianness,
/*align=*/0});
}
formats.push_back(JxlPixelFormat{/*num_channels=*/num_channels,
/*data_type=*/JXL_TYPE_UINT16,
/*endianness=*/JXL_BIG_ENDIAN,
/*align=*/0});
}
return formats;
}
@ -583,9 +616,14 @@ class JPEGEncoder : public Encoder {
};
} // namespace
#endif
std::unique_ptr<Encoder> GetJPEGEncoder() {
#if JPEGXL_ENABLE_JPEG
return jxl::make_unique<JPEGEncoder>();
#else
return nullptr;
#endif
}
} // namespace extras

View File

@ -62,6 +62,13 @@ bool EncodeImageJXL(const JXLCompressParams& params, const PackedPixelFile& ppf,
fprintf(stderr, "Setting frame distance failed.\n");
return false;
}
if (params.debug_image) {
JxlEncoderSetDebugImageCallback(settings, params.debug_image,
params.debug_image_opaque);
}
if (params.stats) {
JxlEncoderCollectStats(settings, params.stats);
}
bool use_boxes = !ppf.metadata.exif.empty() || !ppf.metadata.xmp.empty() ||
!ppf.metadata.jumbf.empty() || !ppf.metadata.iptc.empty();

View File

@ -58,7 +58,9 @@ struct JXLCompressParams {
// If runner_opaque is set, the decoder uses this parallel runner.
JxlParallelRunner runner = JxlThreadParallelRunner;
void* runner_opaque = nullptr;
JxlDebugImageCallback debug_image = nullptr;
void* debug_image_opaque = nullptr;
JxlEncoderStats* stats = nullptr;
bool allow_expert_options = false;
void AddOption(JxlEncoderFrameSettingId id, int64_t val) {

View File

@ -3,7 +3,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#if JPEGXL_ENABLE_JPEG && JPEGXL_ENABLE_JPEGLI
#if JPEGXL_ENABLE_JPEGLI
#include "lib/extras/dec/jpegli.h"
@ -95,6 +95,7 @@ float BitsPerPixel(const PackedPixelFile& ppf,
}
TEST(JpegliTest, JpegliSRGBDecodeTest) {
TEST_LIBJPEG_SUPPORT();
std::string testimage = "jxl/flower/flower_small.rgb.depth8.ppm";
PackedPixelFile ppf0;
ASSERT_TRUE(ReadTestImage(testimage, &ppf0));
@ -113,6 +114,7 @@ TEST(JpegliTest, JpegliSRGBDecodeTest) {
}
TEST(JpegliTest, JpegliGrayscaleDecodeTest) {
TEST_LIBJPEG_SUPPORT();
std::string testimage = "jxl/flower/flower_small.g.depth8.pgm";
PackedPixelFile ppf0;
ASSERT_TRUE(ReadTestImage(testimage, &ppf0));
@ -131,6 +133,7 @@ TEST(JpegliTest, JpegliGrayscaleDecodeTest) {
}
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));
@ -149,6 +152,7 @@ TEST(JpegliTest, JpegliXYBEncodeTest) {
}
TEST(JpegliTest, JpegliDecodeTestLargeSmoothArea) {
TEST_LIBJPEG_SUPPORT();
TestImage t;
const size_t xsize = 2070;
const size_t ysize = 1063;
@ -178,6 +182,7 @@ TEST(JpegliTest, JpegliDecodeTestLargeSmoothArea) {
}
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));
@ -196,6 +201,7 @@ TEST(JpegliTest, JpegliYUVEncodeTest) {
}
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));
@ -217,6 +223,7 @@ TEST(JpegliTest, JpegliYUVChromaSubsamplingEncodeTest) {
}
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));
@ -320,6 +327,7 @@ 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;
@ -402,4 +410,4 @@ JXL_GTEST_INSTANTIATE_TEST_SUITE_P(JpegliColorQuantTest,
} // namespace
} // namespace extras
} // namespace jxl
#endif // JPEGXL_ENABLE_JPEG
#endif // JPEGXL_ENABLE_JPEGLI

View File

@ -115,7 +115,8 @@ Status ConvertPackedPixelFileToCodecInOut(const PackedPixelFile& ppf,
if (!ppf.icc.empty()) {
PaddedBytes icc;
icc.append(ppf.icc);
if (!io->metadata.m.color_encoding.SetICC(std::move(icc))) {
const JxlCmsInterface& cms = GetJxlCms();
if (!io->metadata.m.color_encoding.SetICC(std::move(icc), &cms)) {
fprintf(stderr, "Warning: error setting ICC profile, assuming SRGB\n");
io->metadata.m.color_encoding = ColorEncoding::SRGB(is_gray);
} else {

View File

@ -25,6 +25,21 @@
extern "C" {
#endif
/** Parses an ICC profile and populates @p c and @p cmyk with the data.
*
* @param user_data JxlCmsInterface::set_fields_data passed as-is.
* @param icc_data the ICC data to parse.
* @param icc_size how many bytes of icc_data are valid.
* @param c a JxlColorEncoding to populate if applicable.
* @param cmyk a boolean to set to whether the colorspace is a CMYK colorspace.
* @return Whether the relevant fields in @p c were successfully populated.
*/
typedef JXL_BOOL (*jpegxl_cms_set_fields_from_icc_func)(void* user_data,
const uint8_t* icc_data,
size_t icc_size,
JxlColorEncoding* c,
JXL_BOOL* cmyk);
/** Represents an input or output colorspace to a color transform, as a
* serialized ICC profile. */
typedef struct {
@ -207,6 +222,11 @@ typedef void (*jpegxl_cms_destroy_func)(void*);
* @enddot
*/
typedef struct {
/** CMS-specific data that will be passed to @ref set_fields_from_icc. */
void* set_fields_data;
/** Populates a JxlColorEncoding from an ICC profile. */
jpegxl_cms_set_fields_from_icc_func set_fields_from_icc;
/** CMS-specific data that will be passed to @ref init. */
void* init_data;
/** Prepares a colorspace transform as described in the documentation of @ref

View File

@ -18,6 +18,7 @@
#include <jxl/jxl_export.h>
#include <jxl/memory_manager.h>
#include <jxl/parallel_runner.h>
#include <jxl/stats.h>
#include <jxl/version.h>
#if defined(__cplusplus) || defined(c_plusplus)
@ -335,6 +336,19 @@ typedef enum {
*/
JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES = 33,
/** Control what kind of buffering is used, when using chunked image frames.
* 0 = buffers everything, basically the same as non-streamed code path
(mainly for testing)
* 1 = can buffer internal data (the tokens)
* 2 = can buffer the output
* 3 = minimize buffer usage: streamed input and chunked output, writing TOC
last (will not work with progressive)
When the image dimensions is smaller than 2048 x 2048 all the options are the
same. Using 1, 2 or 3 can result increasingly in less compression density.
*/
JXL_ENC_FRAME_SETTING_BUFFERING = 34,
/** Enum value not to be used as an option. This value is added to force the
* C compiler to have the enum to take a known size.
*/
@ -630,6 +644,49 @@ JXL_EXPORT JxlEncoderStatus JxlEncoderAddImageFrame(
const JxlEncoderFrameSettings* frame_settings,
const JxlPixelFormat* pixel_format, const void* buffer, size_t size);
/**
* TODO(firsching): add documentation
*
*/
typedef void (*JxlEncoderOutputCallback)(void* run_opaque, size_t pos,
size_t num_bytes);
/**
* TODO(firsching): add documentation
*
*/
JXL_EXPORT JxlEncoderStatus
JxlEncoderSetOutputCallback(JxlEncoderOutputCallback callback);
/**
* TODO(firsching): add documentation
*
* @param frame_settings
* @return JXL_ENC_SUCCESS on success, JXL_ENC_ERROR on error
*/
JXL_EXPORT JxlEncoderStatus
JxlEncoderChunkedImageFrameStart(const JxlEncoderFrameSettings* frame_settings);
/**
* TODO(firsching): add documentation
* We process exactly one 2048x2048 DC-group.
*
* @param frame_settings
* @param x horizontal position of the top-left corner of the processed group.
* Must be divisible by 2048.
* @param y vertical position of the top-left corner of the processed group.
* Must be divisible by 2048.
* @param pixel_format for pixels. Object owned by the caller and its contents
* are copied internally.
* @param input_data the input buffer.
* @param input_size size of the input data in bytes.
* @return JXL_EXPORT
*/
JXL_EXPORT JxlEncoderStatus JxlEncoderChunkedImageFrameAddPart(
const JxlEncoderFrameSettings* frame_settings, size_t x, size_t y,
const JxlPixelFormat* pixel_format, const void* input_data,
size_t input_size);
/**
* Sets the buffer to read pixels from for an extra channel at a given index.
* The index must be smaller than the num_extra_channels in the associated
@ -1152,6 +1209,57 @@ JXL_EXPORT void JxlColorEncodingSetToLinearSRGB(
*/
JXL_EXPORT void JxlEncoderAllowExpertOptions(JxlEncoder* enc);
/**
* Function type for @ref JxlEncoderSetDebugImageCallback.
*
* The callback may be called simultaneously by different threads when using a
* threaded parallel runner, on different debug images.
*
* @param opaque optional user data, as given to @ref
* JxlEncoderSetDebugImageCallback.
* @param label label of debug image, can be used in filenames
* @param xsize width of debug image
* @param ysize height of debug image
* @param color color encoding of debug image
* @param pixels pixel data of debug image as big-endian 16-bit unsigned
* samples. The memory is not owned by the user, and is only valid during the
* time the callback is running.
*/
typedef void (*JxlDebugImageCallback)(void* opaque, const char* label,
size_t xsize, size_t ysize,
const JxlColorEncoding* color,
const uint16_t* pixels);
/**
* Sets the given debug image callback that will be used by the encoder to
* output various debug images during encoding.
*
* This only has any effect if the encoder was compiled with the appropriate
* debug build flags.
*
* @param frame_settings set of options and metadata for this frame. Also
* includes reference to the encoder object.
* @param callback used to return the debug image
* @param opaque user supplied parameter to the image callback
*/
JXL_EXPORT void JxlEncoderSetDebugImageCallback(
JxlEncoderFrameSettings* frame_settings, JxlDebugImageCallback callback,
void* opaque);
/**
* Sets the given stats object for gathering various statistics during encoding.
*
* This only has any effect if the encoder was compiled with the appropriate
* debug build flags.
*
* @param frame_settings set of options and metadata for this frame. Also
* includes reference to the encoder object.
* @param stats object that can be used to query the gathered stats (created
* by @ref JxlEncoderStatsCreate)
*/
JXL_EXPORT void JxlEncoderCollectStats(JxlEncoderFrameSettings* frame_settings,
JxlEncoderStats* stats);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif

View File

@ -0,0 +1,103 @@
/* Copyright (c) the JPEG XL Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
/** @addtogroup libjxl_encoder
* @{
* @file stats.h
* @brief API to collect various statistics from JXL encoder.
*/
#ifndef JXL_STATS_H_
#define JXL_STATS_H_
#include <jxl/jxl_export.h>
#include <stddef.h>
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
/**
* Opaque structure that holds the encoder statistics.
*
* Allocated and initialized with JxlEncoderStatsCreate().
* Cleaned up and deallocated with JxlEncoderStatsDestroy().
*/
typedef struct JxlEncoderStatsStruct JxlEncoderStats;
/**
* Creates an instance of JxlEncoderStats and initializes it.
*
* @return pointer to initialized JxlEncoderStats instance
*/
JXL_EXPORT JxlEncoderStats* JxlEncoderStatsCreate();
/**
* Deinitializes and frees JxlEncoderStats instance.
*
* @param stats instance to be cleaned up and deallocated. No-op if stats is
* null pointer.
*/
JXL_EXPORT void JxlEncoderStatsDestroy(JxlEncoderStats* stats);
/** Data type for querying JxlEncoderStats object
*/
typedef enum {
JXL_ENC_STAT_HEADER_BITS,
JXL_ENC_STAT_TOC_BITS,
JXL_ENC_STAT_DICTIONARY_BITS,
JXL_ENC_STAT_SPLINES_BITS,
JXL_ENC_STAT_NOISE_BITS,
JXL_ENC_STAT_QUANT_BITS,
JXL_ENC_STAT_MODULAR_TREE_BITS,
JXL_ENC_STAT_MODULAR_GLOBAL_BITS,
JXL_ENC_STAT_DC_BITS,
JXL_ENC_STAT_MODULAR_DC_GROUP_BITS,
JXL_ENC_STAT_CONTROL_FIELDS_BITS,
JXL_ENC_STAT_COEF_ORDER_BITS,
JXL_ENC_STAT_AC_HISTOGRAM_BITS,
JXL_ENC_STAT_AC_BITS,
JXL_ENC_STAT_MODULAR_AC_GROUP_BITS,
JXL_ENC_STAT_NUM_SMALL_BLOCKS,
JXL_ENC_STAT_NUM_DCT4X8_BLOCKS,
JXL_ENC_STAT_NUM_AFV_BLOCKS,
JXL_ENC_STAT_NUM_DCT8_BLOCKS,
JXL_ENC_STAT_NUM_DCT8X32_BLOCKS,
JXL_ENC_STAT_NUM_DCT16_BLOCKS,
JXL_ENC_STAT_NUM_DCT16X32_BLOCKS,
JXL_ENC_STAT_NUM_DCT32_BLOCKS,
JXL_ENC_STAT_NUM_DCT32X64_BLOCKS,
JXL_ENC_STAT_NUM_DCT64_BLOCKS,
JXL_ENC_STAT_NUM_BUTTERAUGLI_ITERS,
JXL_ENC_NUM_STATS,
} JxlEncoderStatsKey;
/** Returns the value of the statistics corresponding the given key.
*
* @param stats object that was passed to the encoder with a
* @ref JxlEncoderCollectStats function
* @param key the particular statistics to query
*
* @return the value of the statistics
*/
JXL_EXPORT size_t JxlEncoderStatsGet(const JxlEncoderStats* stats,
JxlEncoderStatsKey key);
/** Updates the values of the given stats object with that of an other.
*
* @param stats object whose values will be updated (usually added together)
* @param other stats object whose values will be merged with stats
*/
JXL_EXPORT void JxlEncoderStatsMerge(JxlEncoderStats* stats,
const JxlEncoderStats* other);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
#endif /* JXL_STATS_H_ */
/** @}*/

View File

@ -12,31 +12,67 @@ set(JPEGLI_INTERNAL_LIBS
${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 PUBLIC
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 "${JPEG_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
#
if(BUILD_TESTING)
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} ${JPEGXL_INTERNAL_JPEGLI_TESTLIB_FILES})
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
@ -45,7 +81,11 @@ foreach (TESTFILE IN LISTS JPEGXL_INTERNAL_JPEGLI_TESTS)
)
target_compile_definitions(${TESTNAME} PRIVATE
-DTEST_DATA_PATH="${JPEGXL_TEST_DATA_PATH}")
target_include_directories(${TESTNAME} PRIVATE "${PROJECT_SOURCE_DIR}")
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
@ -73,7 +113,10 @@ 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 PUBLIC "${PROJECT_SOURCE_DIR}")
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}
)
@ -101,6 +144,9 @@ if (JPEGXL_INSTALL_JPEGLI_LIBJPEG)
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

View File

@ -6,11 +6,7 @@
#ifndef LIB_JPEGLI_ADAPTIVE_QUANTIZATION_H_
#define LIB_JPEGLI_ADAPTIVE_QUANTIZATION_H_
/* clang-format off */
#include <stdio.h>
#include <jpeglib.h>
#include <stddef.h>
/* clang-format on */
#include "lib/jpegli/common.h"
namespace jpegli {

View File

@ -6,13 +6,10 @@
#ifndef LIB_JPEGLI_BIT_WRITER_H_
#define LIB_JPEGLI_BIT_WRITER_H_
/* clang-format off */
#include <stdio.h>
#include <jpeglib.h>
#include <stdint.h>
#include <string.h>
/* clang-format on */
#include "lib/jpegli/common.h"
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/compiler_specific.h"

View File

@ -6,10 +6,7 @@
#ifndef LIB_JPEGLI_COLOR_QUANTIZE_H_
#define LIB_JPEGLI_COLOR_QUANTIZE_H_
/* clang-format off */
#include <stdio.h>
#include <jpeglib.h>
/* clang-format on */
#include "lib/jpegli/common.h"
namespace jpegli {

View File

@ -6,11 +6,7 @@
#ifndef LIB_JPEGLI_COLOR_TRANSFORM_H_
#define LIB_JPEGLI_COLOR_TRANSFORM_H_
/* clang-format off */
#include <stdio.h>
#include <jpeglib.h>
/* clang-format on */
#include "lib/jpegli/common.h"
#include "lib/jxl/base/compiler_specific.h"
namespace jpegli {

View File

@ -25,6 +25,8 @@
#include <jpeglib.h>
/* clang-format on */
#include "lib/jpegli/types.h"
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
@ -39,27 +41,6 @@ JQUANT_TBL* jpegli_alloc_quant_table(j_common_ptr cinfo);
JHUFF_TBL* jpegli_alloc_huff_table(j_common_ptr cinfo);
//
// 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);
#if defined(__cplusplus) || defined(c_plusplus)
} // extern "C"
#endif

View File

@ -20,11 +20,6 @@
#ifndef LIB_JPEGLI_DECODE_H_
#define LIB_JPEGLI_DECODE_H_
/* clang-format off */
#include <stdio.h>
#include <jpeglib.h>
/* clang-format on */
#include "lib/jpegli/common.h"
#if defined(__cplusplus) || defined(c_plusplus)

View File

@ -269,7 +269,8 @@ void TestAPINonBuffered(const CompressParams& jparams,
cinfo->scale_denom = 2;
}
jpegli_calc_output_dimensions(cinfo);
SetDecompressParams(dparams, cinfo, /*is_jpegli=*/true);
SetDecompressParams(dparams, cinfo);
jpegli_set_output_format(cinfo, dparams.data_type, dparams.endianness);
VerifyHeader(jparams, cinfo);
jpegli_calc_output_dimensions(cinfo);
EXPECT_LE(expected_output.xsize, cinfo->output_width);
@ -294,7 +295,8 @@ void TestAPIBuffered(const CompressParams& jparams,
EXPECT_EQ(JPEG_REACHED_SOS,
jpegli_read_header(cinfo, /*require_image=*/TRUE));
cinfo->buffered_image = TRUE;
SetDecompressParams(dparams, cinfo, /*is_jpegli=*/true);
SetDecompressParams(dparams, cinfo);
jpegli_set_output_format(cinfo, dparams.data_type, dparams.endianness);
VerifyHeader(jparams, cinfo);
EXPECT_TRUE(jpegli_start_decompress(cinfo));
// start decompress should not read the whole input in buffered image mode
@ -312,8 +314,7 @@ void TestAPIBuffered(const CompressParams& jparams,
if (result == JPEG_REACHED_SOS) ++sos_marker_cnt;
continue;
}
SetScanDecompressParams(dparams, cinfo, cinfo->input_scan_number,
/*is_jpegli=*/true);
SetScanDecompressParams(dparams, cinfo, cinfo->input_scan_number);
EXPECT_TRUE(jpegli_start_output(cinfo, cinfo->input_scan_number));
// start output sets output_scan_number, but does not change
// input_scan_number
@ -924,7 +925,7 @@ std::vector<TestConfig> GenerateTests(bool buffered) {
}
}
// Tests for progressive levels.
for (int p = 0; p < 3 + kNumTestScripts; ++p) {
for (int p = 0; p < 3 + NumTestScanScripts(); ++p) {
TestConfig config;
config.jparams.progressive_mode = p;
all_tests.push_back(config);
@ -1245,7 +1246,7 @@ std::ostream& operator<<(std::ostream& os, const DecompressParams& dparams) {
}
os << IOMethodName(dparams.data_type, dparams.endianness);
if (dparams.set_out_color_space) {
os << "OutColor" << ColorSpaceName(dparams.out_color_space);
os << "OutColor" << ColorSpaceName((J_COLOR_SPACE)dparams.out_color_space);
}
if (dparams.crop_output) {
os << "Crop";
@ -1265,7 +1266,7 @@ std::ostream& operator<<(std::ostream& os, const DecompressParams& dparams) {
if (i > 0) os << "_";
const auto& sparam = dparams.scan_params[i];
os << QuantMode(sparam.color_quant_mode);
os << DitherMode(sparam.dither_mode) << "Dither";
os << DitherMode((J_DITHER_MODE)sparam.dither_mode) << "Dither";
}
}
if (dparams.skip_scans) {

View File

@ -6,11 +6,9 @@
#ifndef LIB_JPEGLI_DECODE_MARKER_H_
#define LIB_JPEGLI_DECODE_MARKER_H_
/* clang-format off */
#include <stdint.h>
#include <stdio.h>
#include <jpeglib.h>
/* clang-format on */
#include "lib/jpegli/common.h"
namespace jpegli {
@ -22,7 +20,7 @@ namespace jpegli {
// 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 the next SOS marker is found;
// * 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* const data,
const size_t len, size_t* pos);

View File

@ -6,11 +6,9 @@
#ifndef LIB_JPEGLI_DECODE_SCAN_H_
#define LIB_JPEGLI_DECODE_SCAN_H_
/* clang-format off */
#include <stdint.h>
#include <stdio.h>
#include <jpeglib.h>
/* clang-format on */
#include "lib/jpegli/common.h"
namespace jpegli {

View File

@ -6,10 +6,7 @@
#ifndef LIB_JPEGLI_DOWNSAMPLE_H_
#define LIB_JPEGLI_DOWNSAMPLE_H_
/* clang-format off */
#include <stdio.h>
#include <jpeglib.h>
/* clang-format on */
#include "lib/jpegli/common.h"
namespace jpegli {

View File

@ -20,11 +20,6 @@
#ifndef LIB_JPEGLI_ENCODE_H_
#define LIB_JPEGLI_ENCODE_H_
/* clang-format off */
#include <stdio.h>
#include <jpeglib.h>
/* clang-format on */
#include "lib/jpegli/common.h"
#if defined(__cplusplus) || defined(c_plusplus)

View File

@ -134,25 +134,13 @@ TEST(EncodeAPITest, ReuseCinfoSameMemOutput) {
EXPECT_TRUE(try_catch_block());
jpegli_destroy_compress(&cinfo);
}
std::vector<TestImage> all_outputs(all_configs.size());
{
jpeg_decompress_struct cinfo = {};
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, buffer, buffer_size);
for (size_t i = 0; i < all_configs.size(); ++i) {
DecodeWithLibjpeg(all_configs[i].jparams, DecompressParams(), &cinfo,
&all_outputs[i]);
}
return true;
};
EXPECT_TRUE(try_catch_block());
jpeg_destroy_decompress(&cinfo);
}
size_t pos = 0;
for (size_t i = 0; i < all_configs.size(); ++i) {
VerifyOutputImage(all_configs[i].input, all_outputs[i],
all_configs[i].max_dist);
TestImage output;
pos +=
DecodeWithLibjpeg(all_configs[i].jparams, DecompressParams(), nullptr,
0, buffer + pos, buffer_size - pos, &output);
VerifyOutputImage(all_configs[i].input, output, all_configs[i].max_dist);
}
if (buffer) free(buffer);
}
@ -175,28 +163,19 @@ TEST(EncodeAPITest, ReuseCinfoSameStdOutput) {
EXPECT_TRUE(try_catch_block());
jpegli_destroy_compress(&cinfo);
}
size_t total_size = ftell(tmpf);
rewind(tmpf);
std::vector<TestImage> all_outputs(all_configs.size());
{
jpeg_decompress_struct cinfo = {};
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpegli);
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, tmpf);
for (size_t i = 0; i < all_configs.size(); ++i) {
DecodeWithLibjpeg(all_configs[i].jparams, DecompressParams(), &cinfo,
&all_outputs[i]);
}
return true;
};
EXPECT_TRUE(try_catch_block());
jpeg_destroy_decompress(&cinfo);
}
for (size_t i = 0; i < all_configs.size(); ++i) {
VerifyOutputImage(all_configs[i].input, all_outputs[i],
all_configs[i].max_dist);
}
std::vector<uint8_t> compressed(total_size);
JXL_CHECK(total_size == fread(&compressed[0], 1, total_size, tmpf));
fclose(tmpf);
size_t pos = 0;
for (size_t i = 0; i < all_configs.size(); ++i) {
TestImage output;
pos += DecodeWithLibjpeg(all_configs[i].jparams, DecompressParams(),
nullptr, 0, &compressed[pos],
compressed.size() - pos, &output);
VerifyOutputImage(all_configs[i].input, output, all_configs[i].max_dist);
}
}
TEST(EncodeAPITest, ReuseCinfoChangeParams) {
@ -298,32 +277,15 @@ TEST(EncodeAPITest, AbbreviatedStreams) {
EXPECT_LT(data_stream_size, 50);
jpegli_destroy_compress(&cinfo);
}
{
jpeg_decompress_struct cinfo = {};
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpeg);
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, table_stream, table_stream_size);
jpeg_read_header(&cinfo, FALSE);
jpeg_mem_src(&cinfo, data_stream, data_stream_size);
jpeg_read_header(&cinfo, TRUE);
EXPECT_EQ(1, cinfo.image_width);
EXPECT_EQ(1, cinfo.image_height);
EXPECT_EQ(3, cinfo.num_components);
jpeg_start_decompress(&cinfo);
JSAMPLE image[3] = {0};
JSAMPROW row[] = {image};
jpeg_read_scanlines(&cinfo, row, 1);
jxl::msan::UnpoisonMemory(image, 3);
EXPECT_EQ(0, image[0]);
EXPECT_EQ(0, image[1]);
EXPECT_EQ(0, image[2]);
jpeg_finish_decompress(&cinfo);
return true;
};
EXPECT_TRUE(try_catch_block());
jpeg_destroy_decompress(&cinfo);
}
TestImage output;
DecodeWithLibjpeg(CompressParams(), DecompressParams(), table_stream,
table_stream_size, data_stream, data_stream_size, &output);
EXPECT_EQ(1, output.xsize);
EXPECT_EQ(1, output.ysize);
EXPECT_EQ(3, output.components);
EXPECT_EQ(0, output.pixels[0]);
EXPECT_EQ(0, output.pixels[1]);
EXPECT_EQ(0, output.pixels[2]);
if (table_stream) free(table_stream);
if (data_stream) free(data_stream);
}
@ -398,8 +360,8 @@ std::vector<TestConfig> GenerateTests() {
if (!progr) {
config.jparams.optimize_coding = optimize;
}
const float kMaxBpp[4] = {1.55, 1.45, 1.45, 1.32};
const float kMaxDist[4] = {1.95, 2.1, 2.1, 2.0};
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);
@ -490,7 +452,7 @@ std::vector<TestConfig> GenerateTests() {
all_tests.push_back(config);
}
}
for (int p = 0; p < 3 + kNumTestScripts; ++p) {
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}) {
@ -508,11 +470,11 @@ std::vector<TestConfig> GenerateTests() {
config.jparams.v_sampling = {samp, 1, 1};
config.jparams.quality = quality;
config.jparams.restart_interval = r;
config.max_bpp = quality == 100 ? 8.0 : 2.0;
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.0;
config.max_dist = quality == 1 ? 20.0 : 2.1;
all_tests.push_back(config);
}
}

View File

@ -6,13 +6,10 @@
#ifndef LIB_JPEGLI_ENCODE_INTERNAL_H_
#define LIB_JPEGLI_ENCODE_INTERNAL_H_
/* clang-format off */
#include <stdint.h>
#include <stdio.h>
#include <jpeglib.h>
/* clang-format on */
#include "lib/jpegli/bit_writer.h"
#include "lib/jpegli/common.h"
#include "lib/jpegli/common_internal.h"
#include "lib/jpegli/encode.h"

View File

@ -30,6 +30,78 @@ 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;

View File

@ -37,76 +37,6 @@ using hwy::HWY_NAMESPACE::Sub;
using DI = HWY_FULL(int32_t);
constexpr DI di;
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]));
}
template <typename DI, class V>
JXL_INLINE V NumBits(DI di, const V x) {
// TODO(szabadka) Add faster implementations for some specific architectures.

View File

@ -103,7 +103,10 @@ void TokenizeACProgressiveScan(j_compress_ptr cinfo, int scan_index,
JBLOCKARRAY ba = (*cinfo->mem->access_virt_barray)(
reinterpret_cast<j_common_ptr>(cinfo), m->coeff_buffers[comp_idx], by,
1, false);
int max_tokens_per_row = comp->width_in_blocks * (Se - Ss + 1);
// 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;
@ -638,7 +641,7 @@ void CopyHuffmanTable(j_compress_ptr cinfo, int index, bool is_dc,
if (index < 0 || index >= NUM_HUFF_TBLS) {
JPEGLI_ERROR("Invalid %s Huffman table index %d", type, index);
}
// Check if we have alreay copied this Huffman table.
// 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;

View File

@ -6,10 +6,7 @@
#ifndef LIB_JPEGLI_ENTROPY_CODING_H_
#define LIB_JPEGLI_ENTROPY_CODING_H_
/* clang-format off */
#include <stdio.h>
#include <jpeglib.h>
/* clang-format on */
#include "lib/jpegli/common.h"
namespace jpegli {

View File

@ -6,12 +6,10 @@
#ifndef LIB_JPEGLI_ERROR_H_
#define LIB_JPEGLI_ERROR_H_
/* clang-format off */
#include <stdint.h>
#include <stdio.h>
#include <jpeglib.h>
#include <stdarg.h>
/* clang-format on */
#include <stdint.h>
#include "lib/jpegli/common.h"
namespace jpegli {

View File

@ -36,27 +36,13 @@ TEST(EncoderErrorHandlingTest, MinimalSuccess) {
EXPECT_TRUE(try_catch_block());
jpegli_destroy_compress(&cinfo);
}
{
jpeg_decompress_struct cinfo = {};
const auto try_catch_block = [&]() -> bool {
ERROR_HANDLER_SETUP(jpeg);
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, buffer, buffer_size);
jpeg_read_header(&cinfo, TRUE);
EXPECT_EQ(1, cinfo.image_width);
EXPECT_EQ(1, cinfo.image_height);
jpeg_start_decompress(&cinfo);
JSAMPLE image[1];
JSAMPROW row[] = {image};
jpeg_read_scanlines(&cinfo, row, 1);
jxl::msan::UnpoisonMemory(image, 1);
EXPECT_EQ(0, image[0]);
jpeg_finish_decompress(&cinfo);
return true;
};
EXPECT_TRUE(try_catch_block());
jpeg_destroy_decompress(&cinfo);
}
TestImage output;
DecodeWithLibjpeg(CompressParams(), DecompressParams(), nullptr, 0, buffer,
buffer_size, &output);
EXPECT_EQ(1, output.xsize);
EXPECT_EQ(1, output.ysize);
EXPECT_EQ(1, output.components);
EXPECT_EQ(0, output.pixels[0]);
if (buffer) free(buffer);
}

View File

@ -6,13 +6,7 @@
#ifndef LIB_JPEGLI_IDCT_H_
#define LIB_JPEGLI_IDCT_H_
/* clang-format off */
#include <stdio.h>
#include <jpeglib.h>
#include <stddef.h>
#include <stdint.h>
/* clang-format on */
#include "lib/jpegli/common.h"
#include "lib/jxl/base/compiler_specific.h"
namespace jpegli {

View File

@ -6,10 +6,7 @@
#ifndef LIB_JPEGLI_INPUT_H_
#define LIB_JPEGLI_INPUT_H_
/* clang-format off */
#include <stdio.h>
#include <jpeglib.h>
/* clang-format on */
#include "lib/jpegli/common.h"
namespace jpegli {

View File

@ -243,7 +243,8 @@ TEST_P(InputSuspensionTestParam, InputOutputLockStepNonBuffered) {
while (jpegli_read_header(&cinfo, TRUE) == JPEG_SUSPENDED) {
JXL_CHECK(src.LoadNextChunk());
}
SetDecompressParams(dparams, &cinfo, true);
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));
@ -299,7 +300,8 @@ TEST_P(InputSuspensionTestParam, InputOutputLockStepBuffered) {
while (jpegli_read_header(&cinfo, TRUE) == JPEG_SUSPENDED) {
JXL_CHECK(src.LoadNextChunk());
}
SetDecompressParams(dparams, &cinfo, true);
SetDecompressParams(dparams, &cinfo);
jpegli_set_output_format(&cinfo, dparams.data_type, dparams.endianness);
cinfo.buffered_image = TRUE;
cinfo.raw_data_out = dparams.output_mode == RAW_DATA;

View File

@ -0,0 +1,261 @@
// 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"
/* clang-format off */
#include <stdio.h>
#include <jpeglib.h>
#include <setjmp.h>
/* clang-format on */
#include "lib/jxl/sanitizers.h"
namespace jpegli {
namespace {
#define JPEG_API_FN(name) jpeg_##name
#include "lib/jpegli/test_utils-inl.h"
#undef JPEG_API_FN
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);
JXL_CHECK(xsize_cropped == cinfo->output_width);
}
output->xsize = xsize_cropped;
output->ysize = ysize_cropped;
output->components = cinfo->out_color_components;
if (cinfo->quantize_colors) {
jxl::msan::UnpoisonMemory(cinfo->colormap, cinfo->out_color_components *
sizeof(cinfo->colormap[0]));
for (int c = 0; c < cinfo->out_color_components; ++c) {
jxl::msan::UnpoisonMemory(
cinfo->colormap[c],
cinfo->actual_number_of_colors * sizeof(cinfo->colormap[c][0]));
}
}
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])};
JXL_CHECK(1 == jpeg_read_scanlines(cinfo, rows, 1));
jxl::msan::UnpoisonMemory(
rows[0], sizeof(JSAMPLE) * cinfo->output_components * output->xsize);
if (cinfo->quantize_colors) {
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;
JXL_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][0];
}
JXL_CHECK(iMCU_height ==
jpeg_read_raw_data(cinfo, &data[0], iMCU_height));
}
}
JXL_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);
}
JXL_CHECK(JPEG_REACHED_SOS ==
jpeg_read_header(cinfo, /*require_image=*/TRUE));
if (!jparams.icc.empty()) {
uint8_t* icc_data = nullptr;
unsigned int icc_len;
JXL_CHECK(jpeg_read_icc_profile(cinfo, &icc_data, &icc_len));
JXL_CHECK(icc_data);
jxl::msan::UnpoisonMemory(icc_data, icc_len);
JXL_CHECK(0 == memcmp(jparams.icc.data(), icc_data, icc_len));
free(icc_data);
}
SetDecompressParams(dparams, cinfo);
VerifyHeader(jparams, cinfo);
if (dparams.output_mode == COEFFICIENTS) {
jvirt_barray_ptr* coef_arrays = jpeg_read_coefficients(cinfo);
JXL_CHECK(coef_arrays != nullptr);
CopyCoefficients(cinfo, coef_arrays, output);
} else {
JXL_CHECK(jpeg_start_decompress(cinfo));
VerifyScanHeader(jparams, cinfo);
ReadOutputPass(cinfo, dparams, output);
}
JXL_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);
}
JXL_CHECK(JPEG_REACHED_SOS ==
jpeg_read_header(&cinfo, /*require_image=*/TRUE));
cinfo.buffered_image = TRUE;
SetDecompressParams(dparams, &cinfo);
VerifyHeader(jparams, &cinfo);
JXL_CHECK(jpeg_start_decompress(&cinfo));
// start decompress should not read the whole input in buffered image mode
JXL_CHECK(!jpeg_input_complete(&cinfo));
JXL_CHECK(cinfo.output_scan_number == 0);
int sos_marker_cnt = 1; // read header reads the first SOS marker
while (!jpeg_input_complete(&cinfo)) {
JXL_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;
}
SetScanDecompressParams(dparams, &cinfo, cinfo.input_scan_number);
JXL_CHECK(jpeg_start_output(&cinfo, cinfo.input_scan_number));
// start output sets output_scan_number, but does not change
// input_scan_number
JXL_CHECK(cinfo.output_scan_number == cinfo.input_scan_number);
JXL_CHECK(cinfo.input_scan_number == sos_marker_cnt);
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) {
JXL_CHECK(cinfo.input_scan_number == sos_marker_cnt);
JXL_CHECK(cinfo.output_scan_number == cinfo.input_scan_number);
}
JXL_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);
JXL_CHECK(coef_arrays != nullptr);
CopyCoefficients(&cinfo, coef_arrays, &output_progression->back());
}
}
JXL_CHECK(jpeg_finish_decompress(&cinfo));
return true;
};
JXL_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);
bytes_read = len - cinfo.src->bytes_in_buffer;
return true;
};
JXL_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

View File

@ -0,0 +1,37 @@
// 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_

View File

@ -7,11 +7,6 @@
// shared library that is API- and ABI-compatible with libjpeg-turbo's version
// of libjpeg.so.
/* clang-format off */
#include <stdio.h>
#include <jpeglib.h>
/* clang-format on */
#include "lib/jpegli/common.h"
#include "lib/jpegli/decode.h"
#include "lib/jpegli/encode.h"

View File

@ -6,11 +6,9 @@
#ifndef LIB_JPEGLI_MEMORY_MANAGER_H_
#define LIB_JPEGLI_MEMORY_MANAGER_H_
/* clang-format off */
#include <stdio.h>
#include <jpeglib.h>
#include <stdlib.h>
/* clang-format on */
#include "lib/jpegli/common.h"
#define JPOOL_PERMANENT_ALIGNED (JPOOL_NUMPOOLS + JPOOL_PERMANENT)
#define JPOOL_IMAGE_ALIGNED (JPOOL_NUMPOOLS + JPOOL_IMAGE)

View File

@ -24,7 +24,7 @@ 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.66986909f;
constexpr float kGlobalScaleYCbCr = 1.73966010f;
static constexpr float kBaseQuantMatrixXYB[] = {
// c = 0
@ -226,212 +226,212 @@ static constexpr float kBaseQuantMatrixXYB[] = {
static const float kBaseQuantMatrixYCbCr[] = {
// c = 0
1.4076321125f,
2.6927082539f,
2.6927735806f,
2.9220938683f,
3.0870633125f,
3.4968640804f,
3.5730612278f,
3.5978596210f,
2.6927082539f,
2.6926636696f,
2.7195601463f,
2.9238407612f,
3.1882488728f,
3.0607142448f,
3.1882314682f,
3.8304426670f,
2.6927735806f,
2.7195601463f,
2.9532215595f,
3.5562388897f,
3.7088179588f,
3.0576279163f,
3.7443304062f,
4.2484717369f,
2.9220938683f,
2.9238407612f,
3.5562388897f,
3.0594384670f,
4.1780085564f,
4.9221563339f,
4.7842588425f,
4.6059336662f,
3.0870633125f,
3.1882488728f,
3.7088179588f,
4.1780085564f,
4.3475294113f,
5.5422372818f,
5.5741071701f,
5.4531836510f,
3.4968640804f,
3.0607142448f,
3.0576279163f,
4.9221563339f,
5.5422372818f,
5.4393601418f,
5.1039180756f,
6.0990614891f,
3.5730612278f,
3.1882314682f,
3.7443304062f,
4.7842588425f,
5.5741071701f,
5.1039180756f,
5.4144043922f,
5.4524297714f,
3.5978596210f,
3.8304426670f,
4.2484717369f,
4.6059336662f,
5.4531836510f,
6.0990614891f,
5.4524297714f,
4.3595433235f,
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.8152642250f,
10.4298934937f,
16.1451492310f,
15.3725156784f,
17.6543502808f,
19.1104965210f,
17.5021877289f,
29.5177459717f,
10.4298934937f,
15.7448558807f,
16.8441677094f,
15.3214502335f,
17.5918464661f,
16.8787574768f,
27.0867996216f,
21.3443832397f,
16.1451492310f,
16.8441677094f,
14.7525558472f,
18.0765247345f,
18.2206096649f,
23.2126445770f,
98.1291885376f,
23.6039886475f,
15.3725156784f,
15.3214502335f,
18.0765247345f,
17.2925109863f,
16.1435356140f,
24.0464611053f,
27.1577339172f,
35.3269882202f,
17.6543502808f,
17.5918464661f,
18.2206096649f,
16.1435356140f,
19.2819595337f,
16.2939300537f,
19.6862888336f,
51.0941123962f,
19.1104965210f,
16.8787574768f,
23.2126445770f,
24.0464611053f,
16.2939300537f,
32.3153648376f,
45.7272338867f,
64.6245880127f,
17.5021877289f,
27.0867996216f,
98.1291885376f,
27.1577339172f,
19.6862888336f,
45.7272338867f,
61.8331909180f,
85.0626754761f,
29.5177459717f,
21.3443832397f,
23.6039886475f,
35.3269882202f,
51.0941123962f,
64.6245880127f,
85.0626754761f,
112.7605514526f,
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.8152642250f,
5.4735932350f,
7.3637795448f,
6.5195322037f,
8.1501169205f,
8.7243938446f,
8.7219915390f,
9.3618907928f,
5.4735932350f,
7.1514792442f,
7.2054982185f,
8.1126995087f,
8.1497650146f,
7.1335659027f,
7.8453893661f,
8.3512821198f,
7.3637795448f,
7.2054982185f,
6.9224662781f,
8.0766754150f,
9.1168527603f,
7.3714752197f,
7.3646650314f,
8.6790895462f,
6.5195322037f,
8.1126995087f,
8.0766754150f,
7.8294739723f,
7.7385902405f,
7.8628563881f,
7.4404106140f,
8.4759435654f,
8.1501169205f,
8.1497650146f,
9.1168527603f,
7.7385902405f,
7.0960793495f,
8.9185447693f,
8.2047510147f,
7.8465061188f,
8.7243938446f,
7.1335659027f,
7.3714752197f,
7.8628563881f,
8.9185447693f,
8.6063842773f,
9.7156696320f,
64.6700744629f,
8.7219915390f,
7.8453893661f,
7.3646650314f,
7.4404106140f,
8.2047510147f,
9.7156696320f,
61.9934043884f,
83.2930450439f,
9.3618907928f,
8.3512821198f,
8.6790895462f,
8.4759435654f,
7.8465061188f,
64.6700744629f,
83.2930450439f,
113.0502548218f,
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, //
};
static const float k420GlobalScale = 1.2;
static const float k420GlobalScale = 1.22;
static const float k420Rescale[64] = {
0.6386, 0.4213, 0.3994, 0.3333, 0.3143, 0.3367, 0.3612, 0.3794, //
0.4213, 0.4026, 0.3309, 0.3344, 0.3059, 0.3118, 0.4069, 0.3595, //
0.3994, 0.3309, 0.4080, 0.2531, 0.2645, 0.3630, 0.3502, 0.3231, //
0.3333, 0.3344, 0.2531, 0.2960, 0.3153, 0.3476, 0.3430, 0.4004, //
0.3143, 0.3059, 0.2645, 0.3153, 0.2733, 0.3296, 0.3338, 0.3418, //
0.3367, 0.3118, 0.3630, 0.3476, 0.3296, 0.3144, 0.2262, 0.1326, //
0.3612, 0.4069, 0.3502, 0.3430, 0.3338, 0.2262, 0.1000, 0.1000, //
0.3794, 0.3595, 0.3231, 0.4004, 0.3418, 0.1326, 0.1000, 0.3366, //
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, //
};
static const float kBaseQuantMatrixStd[] = {

View File

@ -6,10 +6,7 @@
#ifndef LIB_JPEGLI_QUANT_H_
#define LIB_JPEGLI_QUANT_H_
/* clang-format off */
#include <stdio.h>
#include <jpeglib.h>
/* clang-format on */
#include "lib/jpegli/common.h"
namespace jpegli {

View File

@ -13,6 +13,7 @@
#include <cstddef>
#include <cstdint>
#include <hwy/aligned_allocator.h>
#include <vector>
#include "lib/jpegli/color_quantize.h"
#include "lib/jpegli/color_transform.h"

View File

@ -6,13 +6,9 @@
#ifndef LIB_JPEGLI_RENDER_H_
#define LIB_JPEGLI_RENDER_H_
/* clang-format off */
#include <stdint.h>
#include <stdio.h>
#include <jpeglib.h>
/* clang-format on */
#include <vector>
#include "lib/jpegli/common.h"
namespace jpegli {

View File

@ -107,7 +107,7 @@ TEST_P(StreamingTestParam, TestStreaming) {
cinfo.image_width = input.xsize;
cinfo.image_height = input.ysize;
cinfo.input_components = input.components;
cinfo.in_color_space = input.color_space;
cinfo.in_color_space = (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);

View File

@ -0,0 +1,163 @@
// 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_

View File

@ -0,0 +1,430 @@
// 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.
// Sequential non-interleaved.
static 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.
static 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.
static 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},
};
static 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},
};
static 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},
};
static 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},
};
static 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},
};
static 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},
};
static 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},
};
static 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;
};
static 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},
};
static 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 = (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) {
JXL_CHECK(cinfo->out_color_space == JCS_RGB);
cinfo->two_pass_quantize = TRUE;
cinfo->colormap = nullptr;
} else if (sparams->color_quant_mode == CQUANT_EXTERNAL) {
JXL_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(cinfo->colormap, 3 * sizeof(JSAMPROW));
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) {
JXL_CHECK(cinfo->out_color_space == JCS_RGB);
JXL_CHECK(cinfo->colormap);
}
}
}
void SetDecompressParams(const DecompressParams& dparams,
j_decompress_ptr cinfo) {
cinfo->do_block_smoothing = dparams.do_block_smoothing;
cinfo->do_fancy_upsampling = dparams.do_fancy_upsampling;
if (dparams.output_mode == RAW_DATA) {
cinfo->raw_data_out = TRUE;
}
if (dparams.set_out_color_space) {
cinfo->out_color_space = (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;
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;
}
}
SetScanDecompressParams(dparams, cinfo, 1);
} else {
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;
}
}
JXL_CHECK(marker_found);
}
void VerifyHeader(const CompressParams& jparams, j_decompress_ptr cinfo) {
if (jparams.set_jpeg_colorspace) {
JXL_CHECK(cinfo->jpeg_color_space == jparams.jpeg_color_space);
}
if (jparams.override_JFIF >= 0) {
JXL_CHECK(cinfo->saw_JFIF_marker == jparams.override_JFIF);
}
if (jparams.override_Adobe >= 0) {
JXL_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()) {
JXL_CHECK(comp->component_id == jparams.comp_ids[i]);
}
if (!jparams.h_sampling.empty()) {
JXL_CHECK(comp->h_samp_factor == jparams.h_sampling[i]);
}
if (!jparams.v_sampling.empty()) {
JXL_CHECK(comp->v_samp_factor == jparams.v_sampling[i]);
}
if (!jparams.quant_indexes.empty()) {
JXL_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);
}
JXL_CHECK(max_h_samp_factor == cinfo->max_h_samp_factor);
JXL_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];
JXL_CHECK(comp->width_in_blocks ==
DivCeil(cinfo->image_width * comp->h_samp_factor,
max_h_samp_factor * DCTSIZE));
JXL_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]) {
JXL_CHECK(quant_table == nullptr);
continue;
}
JXL_CHECK(quant_table != nullptr);
jxl::msan::UnpoisonMemory(quant_table, sizeof(*quant_table));
for (int k = 0; k < DCTSIZE2; ++k) {
JXL_CHECK(quant_table->quantval[k] == table.quantval[k]);
}
}
}
void VerifyScanHeader(const CompressParams& jparams, j_decompress_ptr cinfo) {
JXL_CHECK(cinfo->input_scan_number > 0);
if (cinfo->progressive_mode) {
JXL_CHECK(cinfo->Ss != 0 || cinfo->Se != 63);
} else {
JXL_CHECK(cinfo->Ss == 0 && cinfo->Se == 63);
}
if (jparams.progressive_mode > 2) {
JXL_CHECK(jparams.progressive_mode < 3 + kNumTestScripts);
const ScanScript& script = kTestScript[jparams.progressive_mode - 3];
JXL_CHECK(cinfo->input_scan_number <= script.num_scans);
const jpeg_scan_info& scan = script.scans[cinfo->input_scan_number - 1];
JXL_CHECK(cinfo->comps_in_scan == scan.comps_in_scan);
for (int i = 0; i < cinfo->comps_in_scan; ++i) {
JXL_CHECK(cinfo->cur_comp_info[i]->component_index ==
scan.component_index[i]);
}
JXL_CHECK(cinfo->Ss == scan.Ss);
JXL_CHECK(cinfo->Se == scan.Se);
JXL_CHECK(cinfo->Ah == scan.Ah);
JXL_CHECK(cinfo->Al == scan.Al);
}
if (jparams.restart_interval > 0) {
JXL_CHECK(cinfo->restart_interval == jparams.restart_interval);
} else if (jparams.restart_in_rows > 0) {
JXL_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) {
JXL_CHECK(cinfo->comp_info[0].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[2].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[0].ac_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].ac_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[2].ac_tbl_no == 0);
} else if (cinfo->jpeg_color_space == JCS_YCbCr) {
JXL_CHECK(cinfo->comp_info[0].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].dc_tbl_no == 1);
JXL_CHECK(cinfo->comp_info[2].dc_tbl_no == 1);
JXL_CHECK(cinfo->comp_info[0].ac_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].ac_tbl_no == 1);
JXL_CHECK(cinfo->comp_info[2].ac_tbl_no == 1);
} else if (cinfo->jpeg_color_space == JCS_CMYK) {
JXL_CHECK(cinfo->comp_info[0].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[2].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[3].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[0].ac_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].ac_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[2].ac_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[3].ac_tbl_no == 0);
} else if (cinfo->jpeg_color_space == JCS_YCCK) {
JXL_CHECK(cinfo->comp_info[0].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].dc_tbl_no == 1);
JXL_CHECK(cinfo->comp_info[2].dc_tbl_no == 1);
JXL_CHECK(cinfo->comp_info[3].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[0].ac_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].ac_tbl_no == 1);
JXL_CHECK(cinfo->comp_info[2].ac_tbl_no == 1);
JXL_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) {
JXL_CHECK(tbl->huffval[i] == i);
}
}
}
}
void UnmapColors(uint8_t* row, size_t xsize, int components,
JSAMPARRAY colormap, size_t num_colors) {
JXL_CHECK(colormap != nullptr);
std::vector<uint8_t> tmp(xsize * components);
for (size_t x = 0; x < xsize; ++x) {
JXL_CHECK(row[x] < num_colors);
for (int c = 0; c < components; ++c) {
tmp[x * components + c] = colormap[c][row[x]];
}
}
memcpy(row, tmp.data(), tmp.size());
}
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 ba = (*cinfo->mem->access_virt_barray)(comptr, coef_arrays[c],
by, 1, true);
size_t stride = comp->width_in_blocks * sizeof(JBLOCK);
size_t offset = by * comp->width_in_blocks * DCTSIZE2;
memcpy(&coeffs[offset], ba[0], stride);
}
output->coeffs.emplace_back(std::move(coeffs));
}
}

View File

@ -21,6 +21,10 @@
namespace jpegli {
#define JPEG_API_FN(name) jpegli_##name
#include "lib/jpegli/test_utils-inl.h"
#undef JPEG_API_FN
#if defined(TEST_DATA_PATH)
std::string GetTestDataPath(const std::string& filename) {
return std::string(TEST_DATA_PATH "/") + filename;
@ -211,7 +215,7 @@ 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(input.color_space);
os << "InputColor" << ColorSpaceName((J_COLOR_SPACE)input.color_space);
}
if (input.color_space == JCS_UNKNOWN) {
os << input.components;
@ -223,7 +227,8 @@ std::ostream& operator<<(std::ostream& os, const CompressParams& jparams) {
os << "Q" << jparams.quality;
os << SamplingId(jparams);
if (jparams.set_jpeg_colorspace) {
os << "JpegColor" << ColorSpaceName(jparams.jpeg_color_space);
os << "JpegColor"
<< ColorSpaceName((J_COLOR_SPACE)jparams.jpeg_color_space);
}
if (!jparams.comp_ids.empty()) {
os << "CID";
@ -406,7 +411,7 @@ void GeneratePixels(TestImage* img) {
size_t in_stride = xsize * in_bytes_per_pixel;
size_t x0 = (xsize - img->xsize) / 2;
size_t y0 = (ysize - img->ysize) / 2;
SetNumChannels(img->color_space, &img->components);
SetNumChannels((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;
@ -420,8 +425,9 @@ void GeneratePixels(TestImage* img) {
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], img->color_space,
img->components, img->data_type, swap_endianness);
ConvertPixel(&pixels[idx_in], &img->pixels[idx_out],
(J_COLOR_SPACE)img->color_space, img->components,
img->data_type, swap_endianness);
}
}
}
@ -485,7 +491,7 @@ void EncodeWithJpegli(const TestImage& input, const CompressParams& jparams,
jpegli_set_progressive_level(cinfo, 0);
}
jpegli_set_defaults(cinfo);
cinfo->in_color_space = input.color_space;
cinfo->in_color_space = (J_COLOR_SPACE)input.color_space;
jpegli_default_colorspace(cinfo);
if (jparams.override_JFIF >= 0) {
cinfo->write_JFIF_header = jparams.override_JFIF;
@ -494,7 +500,7 @@ void EncodeWithJpegli(const TestImage& input, const CompressParams& jparams,
cinfo->write_Adobe_marker = jparams.override_Adobe;
}
if (jparams.set_jpeg_colorspace) {
jpegli_set_colorspace(cinfo, jparams.jpeg_color_space);
jpegli_set_colorspace(cinfo, (J_COLOR_SPACE)jparams.jpeg_color_space);
}
if (!jparams.comp_ids.empty()) {
for (int c = 0; c < cinfo->num_components; ++c) {
@ -672,472 +678,7 @@ bool EncodeWithJpegli(const TestImage& input, const CompressParams& jparams,
return success;
}
void SetScanDecompressParams(const DecompressParams& dparams,
j_decompress_ptr cinfo, int scan_number,
bool is_jpegli) {
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 = 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) {
JXL_CHECK(cinfo->out_color_space = JCS_RGB);
cinfo->two_pass_quantize = TRUE;
cinfo->colormap = nullptr;
} else if (sparams->color_quant_mode == CQUANT_EXTERNAL) {
JXL_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(cinfo->colormap, 3 * sizeof(JSAMPROW));
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) {
if (is_jpegli) {
jpegli_new_colormap(cinfo);
} else {
jpeg_new_colormap(cinfo);
}
}
} else if (sparams->color_quant_mode == CQUANT_REUSE) {
JXL_CHECK(cinfo->out_color_space = JCS_RGB);
JXL_CHECK(cinfo->colormap);
}
}
}
void SetDecompressParams(const DecompressParams& dparams,
j_decompress_ptr cinfo, bool is_jpegli) {
cinfo->do_block_smoothing = dparams.do_block_smoothing;
cinfo->do_fancy_upsampling = dparams.do_fancy_upsampling;
if (dparams.output_mode == RAW_DATA) {
cinfo->raw_data_out = TRUE;
}
if (dparams.set_out_color_space) {
cinfo->out_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;
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;
}
}
SetScanDecompressParams(dparams, cinfo, 1, is_jpegli);
} else {
SetScanDecompressParams(dparams, cinfo, kLastScan, is_jpegli);
}
}
if (is_jpegli) {
jpegli_set_output_format(cinfo, dparams.data_type, dparams.endianness);
}
}
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;
}
}
JXL_CHECK(marker_found);
}
void VerifyHeader(const CompressParams& jparams, j_decompress_ptr cinfo) {
if (jparams.set_jpeg_colorspace) {
JXL_CHECK(cinfo->jpeg_color_space == jparams.jpeg_color_space);
}
if (jparams.override_JFIF >= 0) {
JXL_CHECK(cinfo->saw_JFIF_marker == jparams.override_JFIF);
}
if (jparams.override_Adobe >= 0) {
JXL_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()) {
JXL_CHECK(comp->component_id == jparams.comp_ids[i]);
}
if (!jparams.h_sampling.empty()) {
JXL_CHECK(comp->h_samp_factor == jparams.h_sampling[i]);
}
if (!jparams.v_sampling.empty()) {
JXL_CHECK(comp->v_samp_factor == jparams.v_sampling[i]);
}
if (!jparams.quant_indexes.empty()) {
JXL_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);
}
JXL_CHECK(max_h_samp_factor == cinfo->max_h_samp_factor);
JXL_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];
JXL_CHECK(comp->width_in_blocks ==
DivCeil(cinfo->image_width * comp->h_samp_factor,
max_h_samp_factor * DCTSIZE));
JXL_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]) {
JXL_CHECK(quant_table == nullptr);
continue;
}
JXL_CHECK(quant_table != nullptr);
jxl::msan::UnpoisonMemory(quant_table, sizeof(*quant_table));
for (int k = 0; k < DCTSIZE2; ++k) {
JXL_CHECK(quant_table->quantval[k] == table.quantval[k]);
}
}
}
void VerifyScanHeader(const CompressParams& jparams, j_decompress_ptr cinfo) {
JXL_CHECK(cinfo->input_scan_number > 0);
if (cinfo->progressive_mode) {
JXL_CHECK(cinfo->Ss != 0 || cinfo->Se != 63);
} else {
JXL_CHECK(cinfo->Ss == 0 && cinfo->Se == 63);
}
if (jparams.progressive_mode > 2) {
JXL_CHECK(jparams.progressive_mode < 3 + kNumTestScripts);
const ScanScript& script = kTestScript[jparams.progressive_mode - 3];
JXL_CHECK(cinfo->input_scan_number <= script.num_scans);
const jpeg_scan_info& scan = script.scans[cinfo->input_scan_number - 1];
JXL_CHECK(cinfo->comps_in_scan == scan.comps_in_scan);
for (int i = 0; i < cinfo->comps_in_scan; ++i) {
JXL_CHECK(cinfo->cur_comp_info[i]->component_index ==
scan.component_index[i]);
}
JXL_CHECK(cinfo->Ss == scan.Ss);
JXL_CHECK(cinfo->Se == scan.Se);
JXL_CHECK(cinfo->Ah == scan.Ah);
JXL_CHECK(cinfo->Al == scan.Al);
}
if (jparams.restart_interval > 0) {
JXL_CHECK(cinfo->restart_interval == jparams.restart_interval);
} else if (jparams.restart_in_rows > 0) {
JXL_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) {
JXL_CHECK(cinfo->comp_info[0].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[2].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[0].ac_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].ac_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[2].ac_tbl_no == 0);
} else if (cinfo->jpeg_color_space == JCS_YCbCr) {
JXL_CHECK(cinfo->comp_info[0].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].dc_tbl_no == 1);
JXL_CHECK(cinfo->comp_info[2].dc_tbl_no == 1);
JXL_CHECK(cinfo->comp_info[0].ac_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].ac_tbl_no == 1);
JXL_CHECK(cinfo->comp_info[2].ac_tbl_no == 1);
} else if (cinfo->jpeg_color_space == JCS_CMYK) {
JXL_CHECK(cinfo->comp_info[0].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[2].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[3].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[0].ac_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].ac_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[2].ac_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[3].ac_tbl_no == 0);
} else if (cinfo->jpeg_color_space == JCS_YCCK) {
JXL_CHECK(cinfo->comp_info[0].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].dc_tbl_no == 1);
JXL_CHECK(cinfo->comp_info[2].dc_tbl_no == 1);
JXL_CHECK(cinfo->comp_info[3].dc_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[0].ac_tbl_no == 0);
JXL_CHECK(cinfo->comp_info[1].ac_tbl_no == 1);
JXL_CHECK(cinfo->comp_info[2].ac_tbl_no == 1);
JXL_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) {
JXL_CHECK(tbl->huffval[i] == i);
}
}
}
}
void UnmapColors(uint8_t* row, size_t xsize, int components,
JSAMPARRAY colormap, size_t num_colors) {
JXL_CHECK(colormap != nullptr);
std::vector<uint8_t> tmp(xsize * components);
for (size_t x = 0; x < xsize; ++x) {
JXL_CHECK(row[x] < num_colors);
for (int c = 0; c < components; ++c) {
tmp[x * components + c] = colormap[c][row[x]];
}
}
memcpy(row, tmp.data(), tmp.size());
}
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);
JXL_CHECK(xsize_cropped == cinfo->output_width);
}
output->xsize = xsize_cropped;
output->ysize = ysize_cropped;
output->components = cinfo->out_color_components;
if (cinfo->quantize_colors) {
jxl::msan::UnpoisonMemory(cinfo->colormap, cinfo->out_color_components *
sizeof(cinfo->colormap[0]));
for (int c = 0; c < cinfo->out_color_components; ++c) {
jxl::msan::UnpoisonMemory(
cinfo->colormap[c],
cinfo->actual_number_of_colors * sizeof(cinfo->colormap[c][0]));
}
}
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])};
JXL_CHECK(1 == jpeg_read_scanlines(cinfo, rows, 1));
jxl::msan::UnpoisonMemory(
rows[0], sizeof(JSAMPLE) * cinfo->output_components * output->xsize);
if (cinfo->quantize_colors) {
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;
JXL_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][0];
}
JXL_CHECK(iMCU_height ==
jpeg_read_raw_data(cinfo, &data[0], iMCU_height));
}
}
JXL_CHECK(cinfo->total_iMCU_rows ==
DivCeil(cinfo->image_height, cinfo->max_v_samp_factor * DCTSIZE));
}
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 ba = (*cinfo->mem->access_virt_barray)(comptr, coef_arrays[c],
by, 1, true);
size_t stride = comp->width_in_blocks * sizeof(JBLOCK);
size_t offset = by * comp->width_in_blocks * DCTSIZE2;
memcpy(&coeffs[offset], ba[0], stride);
}
output->coeffs.emplace_back(std::move(coeffs));
}
}
// 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 = [&]() {
ERROR_HANDLER_SETUP(jpeg);
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);
}
JXL_CHECK(JPEG_REACHED_SOS ==
jpeg_read_header(&cinfo, /*require_image=*/TRUE));
cinfo.buffered_image = TRUE;
SetDecompressParams(dparams, &cinfo, /*is_jpegli=*/false);
VerifyHeader(jparams, &cinfo);
JXL_CHECK(jpeg_start_decompress(&cinfo));
// start decompress should not read the whole input in buffered image mode
JXL_CHECK(!jpeg_input_complete(&cinfo));
JXL_CHECK(cinfo.output_scan_number == 0);
int sos_marker_cnt = 1; // read header reads the first SOS marker
while (!jpeg_input_complete(&cinfo)) {
JXL_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;
}
SetScanDecompressParams(dparams, &cinfo, cinfo.input_scan_number,
/*is_jpegli=*/false);
JXL_CHECK(jpeg_start_output(&cinfo, cinfo.input_scan_number));
// start output sets output_scan_number, but does not change
// input_scan_number
JXL_CHECK(cinfo.output_scan_number == cinfo.input_scan_number);
JXL_CHECK(cinfo.input_scan_number == sos_marker_cnt);
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) {
JXL_CHECK(cinfo.input_scan_number == sos_marker_cnt);
JXL_CHECK(cinfo.output_scan_number == cinfo.input_scan_number);
}
JXL_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);
JXL_CHECK(coef_arrays != nullptr);
CopyCoefficients(&cinfo, coef_arrays, &output_progression->back());
}
}
JXL_CHECK(jpeg_finish_decompress(&cinfo));
return true;
};
JXL_CHECK(try_catch_block());
jpeg_destroy_decompress(&cinfo);
}
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);
}
JXL_CHECK(JPEG_REACHED_SOS ==
jpeg_read_header(cinfo, /*require_image=*/TRUE));
if (!jparams.icc.empty()) {
uint8_t* icc_data = nullptr;
unsigned int icc_len;
JXL_CHECK(jpeg_read_icc_profile(cinfo, &icc_data, &icc_len));
JXL_CHECK(icc_data);
jxl::msan::UnpoisonMemory(icc_data, icc_len);
JXL_CHECK(0 == memcmp(jparams.icc.data(), icc_data, icc_len));
free(icc_data);
}
SetDecompressParams(dparams, cinfo, /*is_jpegli=*/false);
VerifyHeader(jparams, cinfo);
if (dparams.output_mode == COEFFICIENTS) {
jvirt_barray_ptr* coef_arrays = jpeg_read_coefficients(cinfo);
JXL_CHECK(coef_arrays != nullptr);
CopyCoefficients(cinfo, coef_arrays, output);
} else {
JXL_CHECK(jpeg_start_decompress(cinfo));
VerifyScanHeader(jparams, cinfo);
ReadOutputPass(cinfo, dparams, output);
}
JXL_CHECK(jpeg_finish_decompress(cinfo));
}
void DecodeWithLibjpeg(const CompressParams& jparams,
const DecompressParams& dparams,
const std::vector<uint8_t>& compressed,
TestImage* output) {
jpeg_decompress_struct cinfo = {};
const auto try_catch_block = [&]() {
ERROR_HANDLER_SETUP(jpeg);
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, compressed.data(), compressed.size());
DecodeWithLibjpeg(jparams, dparams, &cinfo, output);
return true;
};
JXL_CHECK(try_catch_block());
jpeg_destroy_decompress(&cinfo);
}
int NumTestScanScripts() { return kNumTestScripts; }
void DumpImage(const TestImage& image, const std::string fn) {
JXL_CHECK(image.components == 1 || image.components == 3);

View File

@ -20,16 +20,11 @@
/* clang-format on */
#include "lib/jpegli/common.h"
#include "lib/jpegli/libjpeg_test_util.h"
#include "lib/jpegli/test_params.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 ERROR_HANDLER_SETUP(flavor) \
jpeg_error_mgr jerr; \
jmp_buf env; \
@ -45,316 +40,24 @@ constexpr inline T1 DivCeil(T1 a, T2 b) {
longjmp(*env, 1); \
};
#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0]))
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);
// Sequential non-interleaved.
static 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.
static 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.
static 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},
};
static 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},
};
static 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},
};
static 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},
};
static 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},
};
static 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},
};
static 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},
};
static 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;
};
static 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},
};
static constexpr int kNumTestScripts = ARRAY_SIZE(kTestScript);
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);
std::string IOMethodName(JpegliDataType data_type, JpegliEndianness endianness);
std::string ColorSpaceName(J_COLOR_SPACE colorspace);
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;
J_COLOR_SPACE color_space = 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<JCOEF>> coeffs;
void AllocatePixels() {
pixels.resize(ysize * xsize * components *
jpegli_bytes_per_sample(data_type));
}
void Clear() {
pixels.clear();
raw_data.clear();
coeffs.clear();
}
};
std::ostream& operator<<(std::ostream& os, const TestImage& input);
struct CompressParams {
int quality = 90;
bool set_jpeg_colorspace = false;
J_COLOR_SPACE jpeg_color_space = 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() * DCTSIZE) * DCTSIZE;
}
int comp_height(const TestImage& input, int c) const {
return DivCeil(input.ysize * v_samp(c), max_v_sample() * DCTSIZE) * DCTSIZE;
}
};
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);
enum ColorQuantMode {
CQUANT_1PASS,
CQUANT_2PASS,
CQUANT_EXTERNAL,
CQUANT_REUSE,
};
struct ScanDecompressParams {
int max_scan_number;
J_DITHER_MODE 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;
J_COLOR_SPACE out_color_space = 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;
};
void SetDecompressParams(const DecompressParams& dparams,
j_decompress_ptr cinfo, bool is_jpegli);
j_decompress_ptr cinfo);
void SetScanDecompressParams(const DecompressParams& dparams,
j_decompress_ptr cinfo, int scan_number,
bool is_jpegli);
j_decompress_ptr cinfo, int scan_number);
void CopyCoefficients(j_decompress_ptr cinfo, jvirt_barray_ptr* coef_arrays,
TestImage* output);
@ -408,20 +111,6 @@ void EncodeWithJpegli(const TestImage& input, const CompressParams& jparams,
bool EncodeWithJpegli(const TestImage& input, const CompressParams& jparams,
std::vector<uint8_t>* compressed);
// 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);
void DecodeWithLibjpeg(const CompressParams& jparams,
const DecompressParams& dparams, j_decompress_ptr cinfo,
TestImage* output);
void DecodeWithLibjpeg(const CompressParams& jparams,
const DecompressParams& dparams,
const std::vector<uint8_t>& compressed,
TestImage* output);
double DistanceRms(const TestImage& input, const TestImage& output,
size_t start_line, size_t num_lines,
double* max_diff = nullptr);

38
third_party/jpeg-xl/lib/jpegli/types.h vendored Normal file
View File

@ -0,0 +1,38 @@
// 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_
#if defined(__cplusplus) || defined(c_plusplus)
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);
#if defined(__cplusplus) || defined(c_plusplus)
} // extern "C"
#endif
#endif // LIB_JPEGLI_TYPES_H_

View File

@ -63,11 +63,11 @@
#endif
#if JXL_COMPILER_MSVC
#define JXL_UNREACHABLE __assume(false)
#define JXL_UNREACHABLE_BUILTIN __assume(false)
#elif JXL_COMPILER_CLANG || JXL_COMPILER_GCC >= 405
#define JXL_UNREACHABLE __builtin_unreachable()
#define JXL_UNREACHABLE_BUILTIN __builtin_unreachable()
#else
#define JXL_UNREACHABLE
#define JXL_UNREACHABLE_BUILTIN
#endif
#if JXL_COMPILER_MSVC

View File

@ -25,7 +25,8 @@ float LoadFloat16(uint16_t bits16) {
// Subnormal or zero
if (biased_exp == 0) {
const float subnormal = (1.0f / 16384) * (mantissa * (1.0f / 1024));
const float subnormal =
(1.0f / 16384) * (static_cast<float>(mantissa) * (1.0f / 1024));
return sign ? -subnormal : subnormal;
}

View File

@ -68,10 +68,10 @@ namespace jxl {
#define JXL_DEBUG_V_LEVEL 0
#endif // JXL_DEBUG_V_LEVEL
// Pass -DJXL_DEBUG_ON_ABORT=0 to disable the debug messages on JXL_ASSERT,
// JXL_CHECK and JXL_ABORT.
// Pass -DJXL_DEBUG_ON_ABORT={0,1} to force disable/enable the debug messages on
// JXL_ASSERT, JXL_CHECK and JXL_ABORT.
#ifndef JXL_DEBUG_ON_ABORT
#define JXL_DEBUG_ON_ABORT 1
#define JXL_DEBUG_ON_ABORT JXL_DEBUG_ON_ERROR
#endif // JXL_DEBUG_ON_ABORT
// Print a debug message on standard error. You should use the JXL_DEBUG macro
@ -151,6 +151,21 @@ JXL_NORETURN inline JXL_NOINLINE bool Abort() {
__FILE__, __LINE__, ##__VA_ARGS__), \
::jxl::Abort())
// Use this for code paths that are unreachable unless the code would change
// to make it reachable, in which case it will print a warning and abort in
// debug builds. In release builds no code is produced for this, so only use
// this if this path is really unreachable.
#define JXL_UNREACHABLE(format, ...) \
do { \
if (JXL_DEBUG_WARNING) { \
::jxl::Debug(("%s:%d: JXL_UNREACHABLE: " format "\n"), __FILE__, \
__LINE__, ##__VA_ARGS__); \
::jxl::Abort(); \
} else { \
JXL_UNREACHABLE_BUILTIN; \
} \
} while (0)
// Does not guarantee running the code, use only for debug mode checks.
#if JXL_ENABLE_ASSERT
#define JXL_ASSERT(condition) \

View File

@ -80,7 +80,7 @@ void PerformBlending(const float* const* bg, const float* const* fg,
} else if (ec_blending[i].mode == PatchBlendMode::kNone) {
if (xsize) memcpy(tmp.Row(3 + i), bg[3 + i] + x0, xsize * sizeof(**fg));
} else {
JXL_ABORT("Unreachable");
JXL_UNREACHABLE("new PatchBlendMode?");
}
}
size_t alpha = color_blending.alpha_channel;
@ -142,7 +142,7 @@ void PerformBlending(const float* const* bg, const float* const* fg,
memcpy(tmp.Row(p), bg[p] + x0, xsize * sizeof(**fg));
}
} else {
JXL_ABORT("Unreachable");
JXL_UNREACHABLE("new PatchBlendMode?");
}
for (size_t i = 0; i < 3 + num_ec; i++) {
if (xsize != 0) memcpy(out[i] + x0, tmp.Row(i), xsize * sizeof(**out));

View File

@ -182,23 +182,7 @@ void ConvolutionWithTranspose(const ImageF& in,
break;
}
default:
printf("Warning: Unexpected kernel size! %" PRIuS "\n", len);
for (size_t y = 0; y < in.ysize(); ++y) {
const float* BUTTERAUGLI_RESTRICT row_in = in.Row(y);
for (size_t x = border1; x < border2; ++x) {
const int d = x - offset;
float* BUTTERAUGLI_RESTRICT row_out = out->Row(x);
float sum = 0.0f;
size_t j;
for (j = 0; j <= len / 2; ++j) {
sum += row_in[d + j] * scaled_kernel[j];
}
for (; j < len; ++j) {
sum += row_in[d + j] * scaled_kernel[len - 1 - j];
}
row_out[y] = sum;
}
}
JXL_UNREACHABLE("Kernel size %" PRIuS " not implemented", len);
}
// left border
for (size_t x = 0; x < border1; ++x) {

View File

@ -35,7 +35,7 @@ std::string ToString(ColorSpace color_space) {
return "CS?";
}
// Should not happen - visitor fails if enum is invalid.
JXL_ABORT("Invalid ColorSpace %u", static_cast<uint32_t>(color_space));
JXL_UNREACHABLE("Invalid ColorSpace %u", static_cast<uint32_t>(color_space));
}
std::string ToString(WhitePoint white_point) {
@ -50,7 +50,7 @@ std::string ToString(WhitePoint white_point) {
return "DCI";
}
// Should not happen - visitor fails if enum is invalid.
JXL_ABORT("Invalid WhitePoint %u", static_cast<uint32_t>(white_point));
JXL_UNREACHABLE("Invalid WhitePoint %u", static_cast<uint32_t>(white_point));
}
std::string ToString(Primaries primaries) {
@ -65,7 +65,7 @@ std::string ToString(Primaries primaries) {
return "Cst";
}
// Should not happen - visitor fails if enum is invalid.
JXL_ABORT("Invalid Primaries %u", static_cast<uint32_t>(primaries));
JXL_UNREACHABLE("Invalid Primaries %u", static_cast<uint32_t>(primaries));
}
std::string ToString(TransferFunction transfer_function) {
@ -86,8 +86,8 @@ std::string ToString(TransferFunction transfer_function) {
return "TF?";
}
// Should not happen - visitor fails if enum is invalid.
JXL_ABORT("Invalid TransferFunction %u",
static_cast<uint32_t>(transfer_function));
JXL_UNREACHABLE("Invalid TransferFunction %u",
static_cast<uint32_t>(transfer_function));
}
std::string ToString(RenderingIntent rendering_intent) {
@ -102,8 +102,8 @@ std::string ToString(RenderingIntent rendering_intent) {
return "Abs";
}
// Should not happen - visitor fails if enum is invalid.
JXL_ABORT("Invalid RenderingIntent %u",
static_cast<uint32_t>(rendering_intent));
JXL_UNREACHABLE("Invalid RenderingIntent %u",
static_cast<uint32_t>(rendering_intent));
}
static double F64FromCustomxyI32(const int32_t i) { return i * 1E-6; }
@ -131,7 +131,8 @@ Status ConvertExternalToInternalWhitePoint(const JxlWhitePoint external,
*internal = WhitePoint::kDCI;
return true;
}
return JXL_FAILURE("Invalid WhitePoint enum value");
return JXL_FAILURE("Invalid WhitePoint enum value %d",
static_cast<int>(external));
}
Status ConvertExternalToInternalPrimaries(const JxlPrimaries external,
@ -314,7 +315,7 @@ CIExy ColorEncoding::GetWhitePoint() const {
xy.x = xy.y = 1.0 / 3;
return xy;
}
JXL_ABORT("Invalid WhitePoint %u", static_cast<uint32_t>(white_point));
JXL_UNREACHABLE("Invalid WhitePoint %u", static_cast<uint32_t>(white_point));
}
Status ColorEncoding::SetWhitePoint(const CIExy& xy) {
@ -376,7 +377,7 @@ PrimariesCIExy ColorEncoding::GetPrimaries() const {
xy.b.y = 0.060;
return xy;
}
JXL_ABORT("Invalid Primaries %u", static_cast<uint32_t>(primaries));
JXL_UNREACHABLE("Invalid Primaries %u", static_cast<uint32_t>(primaries));
}
Status ColorEncoding::SetPrimaries(const PrimariesCIExy& xy) {
@ -420,6 +421,44 @@ Status ColorEncoding::CreateICC() {
return MaybeCreateProfile(*this, &icc_);
}
Status ColorEncoding::SetFieldsFromICC(const JxlCmsInterface& cms) {
// In case parsing fails, mark the ColorEncoding as invalid.
SetColorSpace(ColorSpace::kUnknown);
tf.SetTransferFunction(TransferFunction::kUnknown);
if (icc_.empty()) return JXL_FAILURE("Empty ICC profile");
JxlColorEncoding external;
JXL_BOOL cmyk;
JXL_RETURN_IF_ERROR(cms.set_fields_from_icc(cms.set_fields_data, icc_.data(),
icc_.size(), &external, &cmyk));
if (cmyk) {
cmyk_ = true;
return true;
}
PaddedBytes icc = std::move(icc_);
JXL_RETURN_IF_ERROR(ConvertExternalToInternalColorEncoding(external, this));
icc_ = std::move(icc);
return true;
}
void ColorEncoding::DecideIfWantICC(const JxlCmsInterface& cms) {
if (icc_.empty()) return;
JxlColorEncoding c;
JXL_BOOL cmyk;
if (!cms.set_fields_from_icc(cms.set_fields_data, icc_.data(), icc_.size(),
&c, &cmyk)) {
return;
}
if (cmyk) return;
PaddedBytes new_icc;
if (!MaybeCreateProfile(*this, &new_icc)) return;
want_icc_ = false;
}
std::string Description(const ColorEncoding& c_in) {
// Copy required for Implicit*
ColorEncoding c = c_in;

View File

@ -8,6 +8,7 @@
// Metadata for color space conversions.
#include <jxl/cms_interface.h>
#include <jxl/color_encoding.h>
#include <stddef.h>
#include <stdint.h>
@ -251,11 +252,17 @@ struct ColorEncoding : public Fields {
// Returns true if `icc` is assigned and decoded successfully. If so,
// subsequent WantICC() will return true until DecideIfWantICC() changes it.
// Returning false indicates data has been lost.
Status SetICC(PaddedBytes&& icc) {
Status SetICC(PaddedBytes&& icc, const JxlCmsInterface* cms) {
if (icc.empty()) return false;
icc_ = std::move(icc);
if (!SetFieldsFromICC()) {
if (cms == nullptr) {
want_icc_ = true;
have_fields_ = false;
return true;
}
if (!SetFieldsFromICC(*cms)) {
InternalRemoveICC();
return false;
}
@ -286,8 +293,7 @@ struct ColorEncoding : public Fields {
bool HaveFields() const { return have_fields_; }
// Causes WantICC() to return false if ICC() can be reconstructed from fields.
// Defined in color_management.cc.
void DecideIfWantICC();
void DecideIfWantICC(const JxlCmsInterface& cms);
bool IsGray() const { return color_space_ == ColorSpace::kGray; }
bool IsCMYK() const { return cmyk_; }
@ -400,8 +406,7 @@ struct ColorEncoding : public Fields {
private:
// Returns true if all fields have been initialized (possibly to kUnknown).
// Returns false if the ICC profile is invalid or decoding it fails.
// Defined in enc_color_management.cc.
Status SetFieldsFromICC();
Status SetFieldsFromICC(const JxlCmsInterface& cms);
// If true, the codestream contains an ICC profile and we do not serialize
// fields. Otherwise, fields are serialized and we create an ICC profile.
@ -428,11 +433,7 @@ struct ColorEncoding : public Fields {
// Returns whether the two inputs are approximately equal.
static inline bool ApproxEq(const double a, const double b,
#if JPEGXL_ENABLE_SKCMS
double max_l1 = 1E-3) {
#else
double max_l1 = 8E-5) {
#endif
// Threshold should be sufficient for ICC's 15-bit fixed-point numbers.
// We have seen differences of 7.1E-5 with lcms2 and 1E-3 with skcms.
return std::abs(a - b) <= max_l1;

View File

@ -828,8 +828,8 @@ Status MaybeCreateProfile(const ColorEncoding& c,
CreateICCCurvParaTag({2.6, 1.0, 0.0, 1.0, 0.0}, 3, &tags));
break;
default:
JXL_ABORT("Unknown TF %u",
static_cast<unsigned int>(c.tf.GetTransferFunction()));
JXL_UNREACHABLE("Unknown TF %u", static_cast<unsigned int>(
c.tf.GetTransferFunction()));
}
}
FinalizeICCTag(&tags, &tag_offset, &tag_size);

View File

@ -200,7 +200,7 @@ TEST_P(ColorManagementTest, VerifyAllProfiles) {
// Can set an equivalent ColorEncoding from the generated ICC profile.
ColorEncoding c3;
ASSERT_TRUE(c3.SetICC(PaddedBytes(c.ICC())));
ASSERT_TRUE(c3.SetICC(PaddedBytes(c.ICC()), &GetJxlCms()));
EXPECT_THAT(c3, HasSameFieldsAs(c));
VerifyPixelRoundTrip(c);
@ -233,7 +233,7 @@ TEST_F(ColorManagementTest, D2700Chromaticity) {
PaddedBytes icc =
jxl::test::ReadTestData("jxl/color_management/sRGB-D2700.icc");
ColorEncoding sRGB_D2700;
ASSERT_TRUE(sRGB_D2700.SetICC(std::move(icc)));
ASSERT_TRUE(sRGB_D2700.SetICC(std::move(icc), &GetJxlCms()));
EXPECT_THAT(sRGB_D2700.GetWhitePoint(), CIExyIs(0.45986, 0.41060));
// The illuminant-relative chromaticities of this profile's primaries are the
@ -245,12 +245,13 @@ TEST_F(ColorManagementTest, D2700Chromaticity) {
}
TEST_F(ColorManagementTest, D2700ToSRGB) {
const JxlCmsInterface& cms = GetJxlCms();
PaddedBytes icc =
jxl::test::ReadTestData("jxl/color_management/sRGB-D2700.icc");
ColorEncoding sRGB_D2700;
ASSERT_TRUE(sRGB_D2700.SetICC(std::move(icc)));
ASSERT_TRUE(sRGB_D2700.SetICC(std::move(icc), &cms));
ColorSpaceTransform transform(GetJxlCms());
ColorSpaceTransform transform(cms);
ASSERT_TRUE(transform.Init(sRGB_D2700, ColorEncoding::SRGB(),
kDefaultIntensityTarget, 1, 1));
const float sRGB_D2700_values[3] = {0.863, 0.737, 0.490};

View File

@ -276,9 +276,10 @@ class ANSSymbolReader {
}
// Takes a *clustered* idx. Can only use if HuffRleOnly() is true.
void ReadHybridUintClusteredHuffRleOnly(size_t ctx,
BitReader* JXL_RESTRICT br,
uint32_t* value, uint32_t* run) {
JXL_INLINE void ReadHybridUintClusteredHuffRleOnly(size_t ctx,
BitReader* JXL_RESTRICT br,
uint32_t* value,
uint32_t* run) {
JXL_DASSERT(HuffRleOnly());
br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits
size_t token = ReadSymbolHuffWithoutRefill(ctx, br);
@ -300,55 +301,80 @@ class ANSSymbolReader {
if (configs[lz77_ctx_].split_token > 1) return false;
return true;
}
bool UsesLZ77() { return lz77_window_ != nullptr; }
// Takes a *clustered* idx.
size_t ReadHybridUintClustered(size_t ctx, BitReader* JXL_RESTRICT br) {
if (JXL_UNLIKELY(num_to_copy_ > 0)) {
size_t ret = lz77_window_[(copy_pos_++) & kWindowMask];
num_to_copy_--;
lz77_window_[(num_decoded_++) & kWindowMask] = ret;
return ret;
// Takes a *clustered* idx. Inlined, for use in hot paths.
template <bool uses_lz77>
JXL_INLINE size_t ReadHybridUintClustered(size_t ctx,
BitReader* JXL_RESTRICT br) {
if (uses_lz77) {
if (JXL_UNLIKELY(num_to_copy_ > 0)) {
size_t ret = lz77_window_[(copy_pos_++) & kWindowMask];
num_to_copy_--;
lz77_window_[(num_decoded_++) & kWindowMask] = ret;
return ret;
}
}
br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits
size_t token = ReadSymbolWithoutRefill(ctx, br);
if (JXL_UNLIKELY(token >= lz77_threshold_)) {
num_to_copy_ =
ReadHybridUintConfig(lz77_length_uint_, token - lz77_threshold_, br) +
lz77_min_length_;
br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits
// Distance code.
size_t token = ReadSymbolWithoutRefill(lz77_ctx_, br);
size_t distance = ReadHybridUintConfig(configs[lz77_ctx_], token, br);
if (JXL_LIKELY(distance < num_special_distances_)) {
distance = special_distances_[distance];
} else {
distance = distance + 1 - num_special_distances_;
if (uses_lz77) {
if (JXL_UNLIKELY(token >= lz77_threshold_)) {
num_to_copy_ = ReadHybridUintConfig(lz77_length_uint_,
token - lz77_threshold_, br) +
lz77_min_length_;
br->Refill(); // covers ReadSymbolWithoutRefill + PeekBits
// Distance code.
size_t token = ReadSymbolWithoutRefill(lz77_ctx_, br);
size_t distance = ReadHybridUintConfig(configs[lz77_ctx_], token, br);
if (JXL_LIKELY(distance < num_special_distances_)) {
distance = special_distances_[distance];
} else {
distance = distance + 1 - num_special_distances_;
}
if (JXL_UNLIKELY(distance > num_decoded_)) {
distance = num_decoded_;
}
if (JXL_UNLIKELY(distance > kWindowSize)) {
distance = kWindowSize;
}
copy_pos_ = num_decoded_ - distance;
if (JXL_UNLIKELY(distance == 0)) {
JXL_DASSERT(lz77_window_ != nullptr);
// distance 0 -> num_decoded_ == copy_pos_ == 0
size_t to_fill = std::min<size_t>(num_to_copy_, kWindowSize);
memset(lz77_window_, 0, to_fill * sizeof(lz77_window_[0]));
}
// TODO(eustas): overflow; mark BitReader as unhealthy
if (num_to_copy_ < lz77_min_length_) return 0;
// the code below is the same as doing this:
// return ReadHybridUintClustered<uses_lz77>(ctx, br);
// but gcc doesn't like recursive inlining
size_t ret = lz77_window_[(copy_pos_++) & kWindowMask];
num_to_copy_--;
lz77_window_[(num_decoded_++) & kWindowMask] = ret;
return ret;
}
if (JXL_UNLIKELY(distance > num_decoded_)) {
distance = num_decoded_;
}
if (JXL_UNLIKELY(distance > kWindowSize)) {
distance = kWindowSize;
}
copy_pos_ = num_decoded_ - distance;
if (JXL_UNLIKELY(distance == 0)) {
JXL_DASSERT(lz77_window_ != nullptr);
// distance 0 -> num_decoded_ == copy_pos_ == 0
size_t to_fill = std::min<size_t>(num_to_copy_, kWindowSize);
memset(lz77_window_, 0, to_fill * sizeof(lz77_window_[0]));
}
// TODO(eustas): overflow; mark BitReader as unhealthy
if (num_to_copy_ < lz77_min_length_) return 0;
return ReadHybridUintClustered(ctx, br); // will trigger a copy.
}
size_t ret = ReadHybridUintConfig(configs[ctx], token, br);
if (lz77_window_) lz77_window_[(num_decoded_++) & kWindowMask] = ret;
if (uses_lz77 && lz77_window_)
lz77_window_[(num_decoded_++) & kWindowMask] = ret;
return ret;
}
JXL_INLINE size_t ReadHybridUint(size_t ctx, BitReader* JXL_RESTRICT br,
const std::vector<uint8_t>& context_map) {
return ReadHybridUintClustered(context_map[ctx], br);
// inlined, for use in hot paths
template <bool uses_lz77>
JXL_INLINE size_t
ReadHybridUintInlined(size_t ctx, BitReader* JXL_RESTRICT br,
const std::vector<uint8_t>& context_map) {
return ReadHybridUintClustered<uses_lz77>(context_map[ctx], br);
}
// not inlined, for use in non-hot paths
size_t ReadHybridUint(size_t ctx, BitReader* JXL_RESTRICT br,
const std::vector<uint8_t>& context_map) {
return ReadHybridUintClustered</*uses_lz77=*/true>(context_map[ctx], br);
}
// ctx is a *clustered* context!

View File

@ -64,14 +64,17 @@ Status DecodeContextMap(std::vector<uint8_t>* context_map, size_t* num_htrees,
/*disallow_lz77=*/context_map->size() <= 2));
ANSSymbolReader reader(&code, input);
size_t i = 0;
uint32_t maxsym = 0;
while (i < context_map->size()) {
uint32_t sym = reader.ReadHybridUint(0, input, dummy_ctx_map);
if (sym >= kMaxClusters) {
return JXL_FAILURE("Invalid cluster ID");
}
uint32_t sym = reader.ReadHybridUintInlined</*uses_lz77=*/true>(
0, input, dummy_ctx_map);
maxsym = sym > maxsym ? sym : maxsym;
(*context_map)[i] = sym;
i++;
}
if (maxsym >= kMaxClusters) {
return JXL_FAILURE("Invalid cluster ID");
}
if (!reader.CheckANSFinalState()) {
return JXL_FAILURE("Invalid context map");
}

View File

@ -235,18 +235,8 @@ void StoreFloatRow(const float* JXL_RESTRICT* rows_in, size_t num_channels,
void JXL_INLINE Store8(uint32_t value, uint8_t* dest) { *dest = value & 0xff; }
// Maximum number of channels for the ConvertChannelsToExternal function.
const size_t kConvertMaxChannels = 4;
} // namespace
// Converts a list of channels to an interleaved image, applying transformations
// when needed.
// The input channels are given as a (non-const!) array of channel pointers and
// interleaved in that order.
//
// Note: if a pointer in channels[] is nullptr, a 1.0 value will be used
// instead. This is useful for handling when a user requests an alpha channel
// from an image that doesn't have one. The first channel in the list may not
// be nullptr, since it is used to determine the image size.
Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
size_t bits_per_sample, bool float_out,
JxlEndianness endianness, size_t stride,
@ -448,8 +438,6 @@ Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
return true;
}
} // namespace
Status ConvertToExternal(const jxl::ImageBundle& ib, size_t bits_per_sample,
bool float_out, size_t num_channels,
JxlEndianness endianness, size_t stride,

View File

@ -21,6 +21,26 @@
namespace jxl {
// Maximum number of channels for the ConvertChannelsToExternal function.
const size_t kConvertMaxChannels = 4;
// Converts a list of channels to an interleaved image, applying transformations
// when needed.
// The input channels are given as a (non-const!) array of channel pointers and
// interleaved in that order.
//
// Note: if a pointer in channels[] is nullptr, a 1.0 value will be used
// instead. This is useful for handling when a user requests an alpha channel
// from an image that doesn't have one. The first channel in the list may not
// be nullptr, since it is used to determine the image size.
Status ConvertChannelsToExternal(const ImageF* channels[], size_t num_channels,
size_t bits_per_sample, bool float_out,
JxlEndianness endianness, size_t stride,
jxl::ThreadPool* pool, void* out_image,
size_t out_size,
const PixelCallback& out_callback,
jxl::Orientation undo_orientation);
// Converts ib to interleaved void* pixel buffer with the given format.
// bits_per_sample: must be 16 or 32 if float_out is true, and at most 16
// if it is false. No bit packing is done.

View File

@ -431,7 +431,7 @@ namespace jxl {
namespace {
// Decode quantized AC coefficients of DCT blocks.
// LLF components in the output block will not be modified.
template <ACType ac_type>
template <ACType ac_type, bool uses_lz77>
Status DecodeACVarBlock(size_t ctx_offset, size_t log2_covered_blocks,
int32_t* JXL_RESTRICT row_nzeros,
const int32_t* JXL_RESTRICT row_nzeros_top,
@ -458,7 +458,8 @@ Status DecodeACVarBlock(size_t ctx_offset, size_t log2_covered_blocks,
const int32_t nzero_ctx =
block_ctx_map.NonZeroContext(predicted_nzeros, block_ctx) + ctx_offset;
size_t nzeros = decoder->ReadHybridUint(nzero_ctx, br, context_map);
size_t nzeros =
decoder->ReadHybridUintInlined<uses_lz77>(nzero_ctx, br, context_map);
if (nzeros + covered_blocks > size) {
return JXL_FAILURE("Invalid AC: nzeros too large");
}
@ -477,7 +478,8 @@ Status DecodeACVarBlock(size_t ctx_offset, size_t log2_covered_blocks,
const size_t ctx =
histo_offset + ZeroDensityContext(nzeros, k, covered_blocks,
log2_covered_blocks, prev);
const size_t u_coeff = decoder->ReadHybridUint(ctx, br, context_map);
const size_t u_coeff =
decoder->ReadHybridUintInlined<uses_lz77>(ctx, br, context_map);
// Hand-rolled version of UnpackSigned, shifting before the conversion to
// signed integer to avoid undefined behavior of shifting negative numbers.
const size_t magnitude = u_coeff >> 1;
@ -525,9 +527,7 @@ struct GetBlockFromBitstream : public GetBlock {
Status LoadBlock(size_t bx, size_t by, const AcStrategy& acs, size_t size,
size_t log2_covered_blocks, ACPtr block[3],
ACType ac_type) override {
auto decode_ac_varblock = ac_type == ACType::k16
? DecodeACVarBlock<ACType::k16>
: DecodeACVarBlock<ACType::k32>;
;
for (size_t c : {1, 0, 2}) {
size_t sbx = bx >> hshift[c];
size_t sby = by >> vshift[c];
@ -536,6 +536,12 @@ struct GetBlockFromBitstream : public GetBlock {
}
for (size_t pass = 0; JXL_UNLIKELY(pass < num_passes); pass++) {
auto decode_ac_varblock =
decoders[pass].UsesLZ77()
? (ac_type == ACType::k16 ? DecodeACVarBlock<ACType::k16, 1>
: DecodeACVarBlock<ACType::k32, 1>)
: (ac_type == ACType::k16 ? DecodeACVarBlock<ACType::k16, 0>
: DecodeACVarBlock<ACType::k32, 0>);
JXL_RETURN_IF_ERROR(decode_ac_varblock(
ctx_offset[pass], log2_covered_blocks, row_nzeros[pass][c],
row_nzeros_top[pass][c], nzeros_stride, c, sbx, sby, bx, acs,

View File

@ -685,7 +685,7 @@ HWY_MAYBE_UNUSED void TransformToPixels(const AcStrategy::Type strategy,
break;
}
case Type::kNumValidStrategies:
JXL_ABORT("Invalid strategy");
JXL_UNREACHABLE("Invalid strategy");
}
}
@ -813,7 +813,7 @@ HWY_MAYBE_UNUSED void LowestFrequenciesFromDC(const AcStrategy::Type strategy,
llf[0] = dc[0];
break;
case Type::kNumValidStrategies:
JXL_ABORT("Invalid strategy");
JXL_UNREACHABLE("Invalid strategy");
};
}

View File

@ -333,7 +333,7 @@ static inline HWY_MAYBE_UNUSED void FastXYBTosRGB8(const float* input[4],
(void)output;
(void)is_rgba;
(void)xsize;
JXL_ABORT("Unreachable");
JXL_UNREACHABLE("Unreachable");
#endif
}

View File

@ -46,14 +46,6 @@ bool OutOfBounds(size_t a, size_t b, size_t size) {
return false;
}
bool SumOverflows(size_t a, size_t b, size_t c) {
size_t sum = a + b;
if (sum < b) return true;
sum += c;
if (sum < c) return true;
return false;
}
JXL_INLINE size_t InitialBasicInfoSizeHint() {
// Amount of bytes before the start of the codestream in the container format,
// assuming that the codestream is the first box after the signature and
@ -85,12 +77,22 @@ JXL_INLINE size_t InitialBasicInfoSizeHint() {
JXL_DEC_ERROR)
#endif // JXL_CRASH_ON_ERROR
// Error caused by bad input (invalid file) rather than incorrect API usage.
// For now there is no way to distinguish these two types of errors yet.
#define JXL_INPUT_ERROR(format, ...) JXL_API_ERROR(format, ##__VA_ARGS__)
JxlDecoderStatus ConvertStatus(JxlDecoderStatus status) { return status; }
JxlDecoderStatus ConvertStatus(jxl::Status status) {
return status ? JXL_DEC_SUCCESS : JXL_DEC_ERROR;
}
#define JXL_API_RETURN_IF_ERROR(expr) \
{ \
JxlDecoderStatus status_ = ConvertStatus(expr); \
if (status_ != JXL_DEC_SUCCESS) return status_; \
}
JxlSignature ReadSignature(const uint8_t* buf, size_t len, size_t* pos) {
if (*pos >= len) return JXL_SIG_NOT_ENOUGH_BYTES;
@ -165,9 +167,8 @@ uint32_t GetBitDepth(JxlBitDepth bit_depth, const T& metadata,
return metadata.bit_depth.bits_per_sample;
} else if (bit_depth.type == JXL_BIT_DEPTH_CUSTOM) {
return bit_depth.bits_per_sample;
} else {
return 0;
}
return 0;
}
enum class DecoderStage : uint32_t {
@ -842,7 +843,7 @@ void JxlDecoderSkipFrames(JxlDecoder* dec, size_t amount) {
JxlDecoderStatus JxlDecoderSkipCurrentFrame(JxlDecoder* dec) {
if (dec->frame_stage != FrameStage::kFull) {
return JXL_DEC_ERROR;
return JXL_API_ERROR("JxlDecoderSkipCurrentFrame called at the wrong time");
}
JXL_DASSERT(dec->frame_dec);
dec->frame_stage = FrameStage::kHeader;
@ -857,7 +858,8 @@ JXL_EXPORT JxlDecoderStatus
JxlDecoderSetParallelRunner(JxlDecoder* dec, JxlParallelRunner parallel_runner,
void* parallel_runner_opaque) {
if (dec->stage != DecoderStage::kInited) {
return JXL_API_ERROR("parallel_runner must be set before starting");
return JXL_API_ERROR(
"JxlDecoderSetParallelRunner must be called before starting");
}
dec->thread_pool.reset(
new jxl::ThreadPool(parallel_runner, parallel_runner_opaque));
@ -965,12 +967,6 @@ JxlDecoderStatus ReadBundle(JxlDecoder* dec, Span<const uint8_t> data,
return JXL_DEC_SUCCESS;
}
#define JXL_API_RETURN_IF_ERROR(expr) \
{ \
JxlDecoderStatus status_ = ConvertStatus(expr); \
if (status_ != JXL_DEC_SUCCESS) return status_; \
}
std::unique_ptr<BitReader, std::function<void(BitReader*)>> GetBitReader(
Span<const uint8_t> span) {
BitReader* reader = new BitReader(span);
@ -995,7 +991,7 @@ JxlDecoderStatus JxlDecoderReadBasicInfo(JxlDecoder* dec) {
return dec->RequestMoreInput();
}
if (span.data()[0] != 0xff || span.data()[1] != jxl::kCodestreamMarker) {
return JXL_API_ERROR("invalid signature");
return JXL_INPUT_ERROR("invalid signature");
}
dec->got_codestream_signature = true;
dec->AdvanceCodestream(2);
@ -1018,7 +1014,7 @@ JxlDecoderStatus JxlDecoderReadBasicInfo(JxlDecoder* dec) {
if (!CheckSizeLimit(dec, dec->metadata.size.xsize(),
dec->metadata.size.ysize())) {
return JXL_API_ERROR("image is too large");
return JXL_INPUT_ERROR("image is too large");
}
return JXL_DEC_SUCCESS;
@ -1135,17 +1131,17 @@ JxlDecoderStatus JxlDecoderProcessSections(JxlDecoder* dec) {
// If any bit reader indicates out of bounds, it's an error, not just
// needing more input, since we ensure only bit readers containing
// a complete section are provided to the FrameDecoder.
return JXL_API_ERROR("frame out of bounds");
return JXL_INPUT_ERROR("frame out of bounds");
}
if (!status) {
return JXL_API_ERROR("frame processing failed");
return JXL_INPUT_ERROR("frame processing failed");
}
for (size_t i = 0; i < section_status.size(); ++i) {
auto status = section_status[i];
if (status == jxl::FrameDecoder::kDone) {
dec->section_processed[section_info[i].index] = 1;
} else if (status != jxl::FrameDecoder::kSkipped) {
return JXL_API_ERROR("unexpected section status");
return JXL_INPUT_ERROR("unexpected section status");
}
}
size_t completed_prefix_bytes = 0;
@ -1250,14 +1246,14 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec) {
status.code() == StatusCode::kNotEnoughBytes) {
return dec->RequestMoreInput();
} else if (!status) {
return JXL_API_ERROR("invalid frame header");
return JXL_INPUT_ERROR("invalid frame header");
}
dec->AdvanceCodestream(reader->TotalBitsConsumed() / kBitsPerByte);
*dec->frame_header = dec->frame_dec->GetFrameHeader();
jxl::FrameDimensions frame_dim = dec->frame_header->ToFrameDimensions();
if (!CheckSizeLimit(dec, frame_dim.xsize_upsampled_padded,
frame_dim.ysize_upsampled_padded)) {
return JXL_API_ERROR("frame is too large");
return JXL_INPUT_ERROR("frame is too large");
}
bool output_needed =
(dec->preview_frame ? (dec->events_wanted & JXL_DEC_PREVIEW_IMAGE)
@ -1269,11 +1265,11 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec) {
// No overflow, checked in CheckSizeLimit.
size_t num_pixels = frame_dim.xsize * frame_dim.ysize;
if (dec->used_cpu_base + num_pixels < dec->used_cpu_base) {
return JXL_API_ERROR("used too much CPU");
return JXL_INPUT_ERROR("image too large");
}
dec->used_cpu_base += num_pixels;
if (dec->used_cpu_base > dec->cpu_limit_base) {
return JXL_API_ERROR("used too much CPU");
return JXL_INPUT_ERROR("image too large");
}
}
dec->remaining_frame_size = dec->frame_dec->SumSectionSizes();
@ -1472,7 +1468,7 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec) {
}
if (!dec->frame_dec->FinalizeFrame()) {
return JXL_API_ERROR("decoding frame failed");
return JXL_INPUT_ERROR("decoding frame failed");
}
#if JPEGXL_ENABLE_TRANSCODE_JPEG
// If jpeg output was requested, we merely return the JXL_DEC_FULL_IMAGE
@ -1601,10 +1597,10 @@ static JxlDecoderStatus ParseBoxHeader(const uint8_t* in, size_t size,
pos += 4;
*header_size = pos - box_start;
if (*box_size > 0 && *box_size < *header_size) {
return JXL_API_ERROR("invalid box size");
return JXL_INPUT_ERROR("invalid box size");
}
if (SumOverflows(file_pos, pos, *box_size)) {
return JXL_API_ERROR("Box size overflow");
if (file_pos + *box_size < file_pos) {
return JXL_INPUT_ERROR("Box size overflow");
}
return JXL_DEC_SUCCESS;
}
@ -1793,10 +1789,10 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
return JXL_DEC_SUCCESS;
}
if (dec->box_count == 2 && memcmp(dec->box_type, "ftyp", 4) != 0) {
return JXL_API_ERROR("the second box must be the ftyp box");
return JXL_INPUT_ERROR("the second box must be the ftyp box");
}
if (memcmp(dec->box_type, "ftyp", 4) == 0 && dec->box_count != 2) {
return JXL_API_ERROR("the ftyp box must come second");
return JXL_INPUT_ERROR("the ftyp box must come second");
}
dec->box_contents_unbounded = (box_size == 0);
@ -1841,7 +1837,7 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
dec->box_stage = BoxStage::kFtyp;
} else if (memcmp(dec->box_type, "jxlc", 4) == 0) {
if (dec->last_codestream_seen) {
return JXL_API_ERROR("there can only be one jxlc box");
return JXL_INPUT_ERROR("there can only be one jxlc box");
}
dec->last_codestream_seen = true;
dec->box_stage = BoxStage::kCodestream;
@ -1851,7 +1847,7 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
} else if ((dec->orig_events_wanted & JXL_DEC_JPEG_RECONSTRUCTION) &&
memcmp(dec->box_type, "jbrd", 4) == 0) {
if (!(dec->events_wanted & JXL_DEC_JPEG_RECONSTRUCTION)) {
return JXL_API_ERROR(
return JXL_INPUT_ERROR(
"multiple JPEG reconstruction boxes not supported");
}
dec->box_stage = BoxStage::kJpegRecon;
@ -1867,22 +1863,22 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
}
} else if (dec->box_stage == BoxStage::kFtyp) {
if (dec->box_contents_size < 12) {
return JXL_API_ERROR("file type box too small");
return JXL_INPUT_ERROR("file type box too small");
}
if (dec->avail_in < 4) return JXL_DEC_NEED_MORE_INPUT;
if (memcmp(dec->next_in, "jxl ", 4) != 0) {
return JXL_API_ERROR("file type box major brand must be \"jxl \"");
return JXL_INPUT_ERROR("file type box major brand must be \"jxl \"");
}
dec->AdvanceInput(4);
dec->box_stage = BoxStage::kSkip;
} else if (dec->box_stage == BoxStage::kPartialCodestream) {
if (dec->last_codestream_seen) {
return JXL_API_ERROR("cannot have jxlp box after last jxlp box");
return JXL_INPUT_ERROR("cannot have jxlp box after last jxlp box");
}
// TODO(lode): error if box is unbounded but last bit not set
if (dec->avail_in < 4) return JXL_DEC_NEED_MORE_INPUT;
if (!dec->box_contents_unbounded && dec->box_contents_size < 4) {
return JXL_API_ERROR("jxlp box too small to contain index");
return JXL_INPUT_ERROR("jxlp box too small to contain index");
}
size_t jxlp_index = LoadBE32(dec->next_in);
// The high bit of jxlp_index indicates whether this is the last
@ -1946,22 +1942,22 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
size_t num_xmp = jxl::JxlToJpegDecoder::NumXmpMarkers(*jpeg_data);
if (num_exif) {
if (num_exif > 1) {
return JXL_API_ERROR(
return JXL_INPUT_ERROR(
"multiple exif markers for JPEG reconstruction not supported");
}
if (JXL_DEC_SUCCESS != jxl::JxlToJpegDecoder::ExifBoxContentSize(
*jpeg_data, &dec->recon_exif_size)) {
return JXL_API_ERROR("invalid jbrd exif size");
return JXL_INPUT_ERROR("invalid jbrd exif size");
}
}
if (num_xmp) {
if (num_xmp > 1) {
return JXL_API_ERROR(
return JXL_INPUT_ERROR(
"multiple XMP markers for JPEG reconstruction not supported");
}
if (JXL_DEC_SUCCESS != jxl::JxlToJpegDecoder::XmlBoxContentSize(
*jpeg_data, &dec->recon_xmp_size)) {
return JXL_API_ERROR("invalid jbrd XMP size");
return JXL_INPUT_ERROR("invalid jbrd XMP size");
}
}
@ -2027,10 +2023,10 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) {
if (!dec->got_signature) {
JxlSignature sig = JxlSignatureCheck(dec->next_in, dec->avail_in);
if (sig == JXL_SIG_INVALID) return JXL_API_ERROR("invalid signature");
if (sig == JXL_SIG_INVALID) return JXL_INPUT_ERROR("invalid signature");
if (sig == JXL_SIG_NOT_ENOUGH_BYTES) {
if (dec->input_closed) {
return JXL_API_ERROR("file too small for signature");
return JXL_INPUT_ERROR("file too small for signature");
}
return JXL_DEC_NEED_MORE_INPUT;
}
@ -2047,18 +2043,18 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) {
JxlDecoderStatus status = HandleBoxes(dec);
if (status == JXL_DEC_NEED_MORE_INPUT && dec->input_closed) {
return JXL_API_ERROR("missing input");
return JXL_INPUT_ERROR("premature end of input");
}
// Even if the box handling returns success, certain types of
// data may be missing.
if (status == JXL_DEC_SUCCESS) {
if (dec->CanUseMoreCodestreamInput()) {
return JXL_API_ERROR("codestream never finished");
return JXL_INPUT_ERROR("codestream never finished");
}
#if JPEGXL_ENABLE_TRANSCODE_JPEG
if (dec->JbrdNeedMoreBoxes()) {
return JXL_API_ERROR("missing metadata boxes for jpeg reconstruction");
return JXL_INPUT_ERROR("missing metadata boxes for jpeg reconstruction");
}
#endif
}
@ -2769,12 +2765,11 @@ template <typename T>
JxlDecoderStatus VerifyOutputBitDepth(JxlBitDepth bit_depth, const T& metadata,
JxlPixelFormat format) {
uint32_t bits_per_sample = GetBitDepth(bit_depth, metadata, format);
if (format.data_type == JXL_TYPE_UINT8 &&
(bits_per_sample == 0 || bits_per_sample > 8)) {
if (bits_per_sample == 0) return JXL_API_ERROR("Invalid output bit depth");
if (format.data_type == JXL_TYPE_UINT8 && bits_per_sample > 8) {
return JXL_API_ERROR("Invalid bit depth %u for uint8 output",
bits_per_sample);
} else if (format.data_type == JXL_TYPE_UINT16 &&
(bits_per_sample == 0 || bits_per_sample > 16)) {
} else if (format.data_type == JXL_TYPE_UINT16 && bits_per_sample > 16) {
return JXL_API_ERROR("Invalid bit depth %u for uint16 output",
bits_per_sample);
}

View File

@ -242,7 +242,7 @@ PaddedBytes CreateTestJXLCodestream(Span<const uint8_t> pixels, size_t xsize,
// the hardcoded ICC profile we attach requires RGB.
EXPECT_EQ(false, grayscale);
EXPECT_TRUE(params.color_space.empty());
EXPECT_TRUE(color_encoding.SetICC(GetIccTestProfile()));
EXPECT_TRUE(color_encoding.SetICC(GetIccTestProfile(), &GetJxlCms()));
} else if (!params.color_space.empty()) {
JxlColorEncoding c;
EXPECT_TRUE(jxl::ParseDescription(params.color_space, &c));
@ -269,22 +269,22 @@ PaddedBytes CreateTestJXLCodestream(Span<const uint8_t> pixels, size_t xsize,
&io.Main()));
jxl::PaddedBytes jpeg_data;
if (params.jpeg_codestream != nullptr) {
#if JPEGXL_ENABLE_JPEG
std::vector<uint8_t> jpeg_bytes;
io.jpeg_quality = 70;
EXPECT_TRUE(Encode(io, extras::Codec::kJPG, io.metadata.m.color_encoding,
/*bits_per_sample=*/8, &jpeg_bytes, &pool));
params.jpeg_codestream->append(jpeg_bytes.data(),
jpeg_bytes.data() + jpeg_bytes.size());
EXPECT_TRUE(jxl::jpeg::DecodeImageJPG(
jxl::Span<const uint8_t>(jpeg_bytes.data(), jpeg_bytes.size()), &io));
EXPECT_TRUE(
EncodeJPEGData(*io.Main().jpeg_data, &jpeg_data, params.cparams));
io.metadata.m.xyb_encoded = false;
#else // JPEGXL_ENABLE_JPEG
JXL_ABORT(
"unable to create reconstructible JPEG without JPEG support enabled");
#endif // JPEGXL_ENABLE_JPEG
if (jxl::extras::CanDecode(jxl::extras::Codec::kJPG)) {
std::vector<uint8_t> jpeg_bytes;
io.jpeg_quality = 70;
EXPECT_TRUE(Encode(io, extras::Codec::kJPG, io.metadata.m.color_encoding,
/*bits_per_sample=*/8, &jpeg_bytes, &pool));
params.jpeg_codestream->append(jpeg_bytes.data(),
jpeg_bytes.data() + jpeg_bytes.size());
EXPECT_TRUE(jxl::jpeg::DecodeImageJPG(
jxl::Span<const uint8_t>(jpeg_bytes.data(), jpeg_bytes.size()), &io));
EXPECT_TRUE(
EncodeJPEGData(*io.Main().jpeg_data, &jpeg_data, params.cparams));
io.metadata.m.xyb_encoded = false;
} else {
JXL_ABORT(
"unable to create reconstructible JPEG without JPEG support enabled");
}
}
if (params.preview_mode) {
io.preview_frame = io.Main().Copy();
@ -740,7 +740,8 @@ std::vector<uint8_t> GetTestHeader(size_t xsize, size_t ysize,
if (!icc_profile.empty()) {
jxl::PaddedBytes copy = icc_profile;
EXPECT_TRUE(metadata.m.color_encoding.SetICC(std::move(copy)));
EXPECT_TRUE(
metadata.m.color_encoding.SetICC(std::move(copy), &jxl::GetJxlCms()));
}
EXPECT_TRUE(jxl::Bundle::Write(metadata.m, &writer, 0, nullptr));
@ -1644,7 +1645,7 @@ TEST(DecodeTest, PixelTestWithICCProfileLossy) {
// The input pixels use the profile matching GetIccTestProfile, since we set
// add_icc_profile for CreateTestJXLCodestream to true.
jxl::ColorEncoding color_encoding0;
EXPECT_TRUE(color_encoding0.SetICC(GetIccTestProfile()));
EXPECT_TRUE(color_encoding0.SetICC(GetIccTestProfile(), &jxl::GetJxlCms()));
jxl::Span<const uint8_t> span0(pixels.data(), pixels.size());
jxl::CodecInOut io0;
io0.SetSize(xsize, ysize);
@ -1653,7 +1654,7 @@ TEST(DecodeTest, PixelTestWithICCProfileLossy) {
/*pool=*/nullptr, &io0.Main()));
jxl::ColorEncoding color_encoding1;
EXPECT_TRUE(color_encoding1.SetICC(std::move(icc)));
EXPECT_TRUE(color_encoding1.SetICC(std::move(icc), &jxl::GetJxlCms()));
jxl::Span<const uint8_t> span1(pixels2.data(), pixels2.size());
jxl::CodecInOut io1;
io1.SetSize(xsize, ysize);
@ -2302,12 +2303,11 @@ void TestPartialStream(bool reconstructible_jpeg) {
// should return JXL_DEC_NEED_MORE_INPUT, not error.
TEST(DecodeTest, PixelPartialTest) { TestPartialStream(false); }
#if JPEGXL_ENABLE_JPEG
// Tests the return status when trying to decode JPEG bytes on incomplete file.
TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGPartialTest)) {
TEST_LIBJPEG_SUPPORT();
TestPartialStream(true);
}
#endif // JPEGXL_ENABLE_JPEG
// The DC event still exists, but is no longer implemented, it is deprecated.
TEST(DecodeTest, DCNotGettableTest) {
@ -4031,8 +4031,8 @@ TEST(DecodeTest, InputHandlingTestOneShot) {
}
}
#if JPEGXL_ENABLE_JPEG
TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(InputHandlingTestJPEGOneshot)) {
TEST_LIBJPEG_SUPPORT();
size_t xsize = 123;
size_t ysize = 77;
size_t channels = 3;
@ -4122,7 +4122,6 @@ TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(InputHandlingTestJPEGOneshot)) {
}
}
}
#endif // JPEGXL_ENABLE_JPEG
TEST(DecodeTest, InputHandlingTestStreaming) {
size_t xsize = 508, ysize = 470;
@ -4805,8 +4804,8 @@ void VerifyJPEGReconstruction(const jxl::PaddedBytes& container,
EXPECT_EQ(0, memcmp(reconstructed_buffer.data(), jpeg_bytes.data(), used));
}
#if JPEGXL_ENABLE_JPEG
TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructTestCodestream)) {
TEST_LIBJPEG_SUPPORT();
size_t xsize = 123;
size_t ysize = 77;
size_t channels = 3;
@ -4823,7 +4822,6 @@ TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructTestCodestream)) {
channels, params);
VerifyJPEGReconstruction(compressed, jpeg_codestream);
}
#endif // JPEGXL_ENABLE_JPEG
TEST(DecodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) {
const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg";

View File

@ -12,7 +12,7 @@ namespace jxl {
JxlDecoderStatus JxlToJpegDecoder::Process(const uint8_t** next_in,
size_t* avail_in) {
if (!inside_box_) {
JXL_ABORT(
JXL_UNREACHABLE(
"processing of JPEG reconstruction data outside JPEG reconstruction "
"box");
}
@ -38,7 +38,7 @@ JxlDecoderStatus JxlToJpegDecoder::Process(const uint8_t** next_in,
to_decode = Span<const uint8_t>(buffer_.data(), buffer_.size());
}
if (!box_until_eof_ && to_decode.size() > box_size_) {
JXL_ABORT("JPEG reconstruction data to decode larger than expected");
JXL_UNREACHABLE("JPEG reconstruction data to decode larger than expected");
}
if (box_until_eof_ || to_decode.size() == box_size_) {
// If undefined size, or the right size, try to decode.

View File

@ -26,6 +26,7 @@
#include "lib/jxl/convolve.h"
#include "lib/jxl/dct_scales.h"
#include "lib/jxl/enc_aux_out.h"
#include "lib/jxl/enc_debug_image.h"
#include "lib/jxl/enc_params.h"
#include "lib/jxl/enc_transforms-inl.h"
#include "lib/jxl/entropy_coder.h"
@ -44,12 +45,18 @@
// sensitive to some kind of degradation. Unfortunately image quality
// is still more of an art than science.
// Set JXL_DEBUG_AC_STRATEGY to 1 to enable debugging.
#ifndef JXL_DEBUG_AC_STRATEGY
#define JXL_DEBUG_AC_STRATEGY 0
#endif
// This must come before the begin/end_target, but HWY_ONCE is only true
// after that, so use an "include guard".
#ifndef LIB_JXL_ENC_AC_STRATEGY_
#define LIB_JXL_ENC_AC_STRATEGY_
// Parameters of the heuristic are marked with a OPTIMIZE comment.
namespace jxl {
namespace {
// Debugging utilities.
@ -207,7 +214,8 @@ const uint8_t* TypeMask(const uint8_t& raw_strategy) {
}
void DumpAcStrategy(const AcStrategyImage& ac_strategy, size_t xsize,
size_t ysize, const char* tag, AuxOut* aux_out) {
size_t ysize, const char* tag, AuxOut* aux_out,
const CompressParams& cparams) {
Image3F color_acs(xsize, ysize);
for (size_t y = 0; y < ysize; y++) {
float* JXL_RESTRICT rows[3] = {
@ -259,9 +267,10 @@ void DumpAcStrategy(const AcStrategyImage& ac_strategy, size_t xsize,
}
}
}
aux_out->DumpImage(tag, color_acs);
DumpImage(cparams, tag, color_acs);
}
} // namespace
} // namespace jxl
#endif // LIB_JXL_ENC_AC_STRATEGY_
@ -1156,9 +1165,11 @@ void AcStrategyHeuristics::Finalize(AuxOut* aux_out) {
ac_strategy.CountBlocks(AcStrategy::Type::DCT64X64);
}
if (WantDebugOutput(aux_out)) {
// if (JXL_DEBUG_AC_STRATEGY && WantDebugOutput(aux_out)) {
if (JXL_DEBUG_AC_STRATEGY && WantDebugOutput(enc_state->cparams)) {
DumpAcStrategy(ac_strategy, enc_state->shared.frame_dim.xsize,
enc_state->shared.frame_dim.ysize, "ac_strategy", aux_out);
enc_state->shared.frame_dim.ysize, "ac_strategy", aux_out,
enc_state->cparams);
}
}

View File

@ -65,10 +65,6 @@ struct AcStrategyHeuristics {
PassesEncoderState* enc_state;
};
// Debug.
void DumpAcStrategy(const AcStrategyImage& ac_strategy, size_t xsize,
size_t ysize, const char* tag, AuxOut* aux_out);
} // namespace jxl
#endif // LIB_JXL_ENC_AC_STRATEGY_H_

View File

@ -34,6 +34,7 @@
#include "lib/jxl/enc_aux_out.h"
#include "lib/jxl/enc_butteraugli_comparator.h"
#include "lib/jxl/enc_cache.h"
#include "lib/jxl/enc_debug_image.h"
#include "lib/jxl/enc_group.h"
#include "lib/jxl/enc_modular.h"
#include "lib/jxl/enc_params.h"
@ -46,6 +47,12 @@
#include "lib/jxl/image_ops.h"
#include "lib/jxl/opsin_params.h"
#include "lib/jxl/quant_weights.h"
// Set JXL_DEBUG_ADAPTIVE_QUANTIZATION to 1 to enable debugging.
#ifndef JXL_DEBUG_ADAPTIVE_QUANTIZATION
#define JXL_DEBUG_ADAPTIVE_QUANTIZATION 0
#endif
HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
@ -623,38 +630,43 @@ namespace jxl {
HWY_EXPORT(AdaptiveQuantizationMap);
namespace {
// If true, prints the quantization maps at each iteration.
bool FLAGS_dump_quant_state = false;
void DumpHeatmap(const AuxOut* aux_out, const std::string& label,
const ImageF& image, float good_threshold,
float bad_threshold) {
Image3F heatmap = CreateHeatMapImage(image, good_threshold, bad_threshold);
char filename[200];
snprintf(filename, sizeof(filename), "%s%05d", label.c_str(),
aux_out->num_butteraugli_iters);
aux_out->DumpImage(filename, heatmap);
// If true, prints the quantization maps at each iteration.
constexpr bool FLAGS_dump_quant_state = false;
void DumpHeatmap(const CompressParams& cparams, const AuxOut* aux_out,
const std::string& label, const ImageF& image,
float good_threshold, float bad_threshold) {
if (JXL_DEBUG_ADAPTIVE_QUANTIZATION) {
Image3F heatmap = CreateHeatMapImage(image, good_threshold, bad_threshold);
char filename[200];
snprintf(filename, sizeof(filename), "%s%05d", label.c_str(),
aux_out->num_butteraugli_iters);
DumpImage(cparams, filename, heatmap);
}
}
void DumpHeatmaps(const AuxOut* aux_out, float ba_target,
const ImageF& quant_field, const ImageF& tile_heatmap,
const ImageF& bt_diffmap) {
if (!WantDebugOutput(aux_out)) return;
ImageF inv_qmap(quant_field.xsize(), quant_field.ysize());
for (size_t y = 0; y < quant_field.ysize(); ++y) {
const float* JXL_RESTRICT row_q = quant_field.ConstRow(y);
float* JXL_RESTRICT row_inv_q = inv_qmap.Row(y);
for (size_t x = 0; x < quant_field.xsize(); ++x) {
row_inv_q[x] = 1.0f / row_q[x]; // never zero
void DumpHeatmaps(const CompressParams& cparams, const AuxOut* aux_out,
float ba_target, const ImageF& quant_field,
const ImageF& tile_heatmap, const ImageF& bt_diffmap) {
if (JXL_DEBUG_ADAPTIVE_QUANTIZATION) {
if (!WantDebugOutput(cparams)) return;
ImageF inv_qmap(quant_field.xsize(), quant_field.ysize());
for (size_t y = 0; y < quant_field.ysize(); ++y) {
const float* JXL_RESTRICT row_q = quant_field.ConstRow(y);
float* JXL_RESTRICT row_inv_q = inv_qmap.Row(y);
for (size_t x = 0; x < quant_field.xsize(); ++x) {
row_inv_q[x] = 1.0f / row_q[x]; // never zero
}
}
DumpHeatmap(cparams, aux_out, "quant_heatmap", inv_qmap, 4.0f * ba_target,
6.0f * ba_target);
DumpHeatmap(cparams, aux_out, "tile_heatmap", tile_heatmap, ba_target,
1.5f * ba_target);
// matches heat maps produced by the command line tool.
DumpHeatmap(cparams, aux_out, "bt_diffmap", bt_diffmap,
ButteraugliFuzzyInverse(1.5), ButteraugliFuzzyInverse(0.5));
}
DumpHeatmap(aux_out, "quant_heatmap", inv_qmap, 4.0f * ba_target,
6.0f * ba_target);
DumpHeatmap(aux_out, "tile_heatmap", tile_heatmap, ba_target,
1.5f * ba_target);
// matches heat maps produced by the command line tool.
DumpHeatmap(aux_out, "bt_diffmap", bt_diffmap, ButteraugliFuzzyInverse(1.5),
ButteraugliFuzzyInverse(0.5));
}
ImageF TileDistMap(const ImageF& distmap, int tile_size, int margin,
@ -799,6 +811,8 @@ ImageBundle RoundtripImage(const Image3F& opsin, PassesEncoderState* enc_state,
return decoded;
}
constexpr int kMaxButteraugliIters = 4;
void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin,
PassesEncoderState* enc_state,
const JxlCmsInterface& cms, ThreadPool* pool,
@ -838,7 +852,7 @@ void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin,
const float butteraugli_target = cparams.butteraugli_distance;
const float original_butteraugli = cparams.original_butteraugli_distance;
ButteraugliParams params = cparams.ba_params;
ButteraugliParams params;
params.intensity_target = linear.metadata()->IntensityTarget();
// Hack the default intensity target value to be 80.0, the intensity
// target of sRGB images and a more reasonable viewing default than
@ -869,15 +883,12 @@ void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin,
JXL_ASSERT(qf_higher / qf_lower < 253);
constexpr int kOriginalComparisonRound = 1;
int iters = cparams.max_butteraugli_iters;
if (iters > 7) {
iters = 7;
}
int iters = kMaxButteraugliIters;
if (cparams.speed_tier != SpeedTier::kTortoise) {
iters = 2;
}
for (int i = 0; i < iters + 1; ++i) {
if (FLAGS_dump_quant_state) {
if (JXL_DEBUG_ADAPTIVE_QUANTIZATION) {
printf("\nQuantization field:\n");
for (size_t y = 0; y < quant_field.ysize(); ++y) {
for (size_t x = 0; x < quant_field.xsize(); ++x) {
@ -897,16 +908,16 @@ void FindBestQuantization(const ImageBundle& linear, const Image3F& opsin,
}
tile_distmap = TileDistMap(diffmap, 8 * cparams.resampling, 0,
enc_state->shared.ac_strategy);
if (WantDebugOutput(aux_out)) {
aux_out->DumpImage(("dec" + ToString(i)).c_str(), *dec_linear.color());
DumpHeatmaps(aux_out, butteraugli_target, quant_field, tile_distmap,
diffmap);
if (JXL_DEBUG_ADAPTIVE_QUANTIZATION && WantDebugOutput(cparams)) {
DumpImage(cparams, ("dec" + ToString(i)).c_str(), *dec_linear.color());
DumpHeatmaps(cparams, aux_out, butteraugli_target, quant_field,
tile_distmap, diffmap);
}
if (aux_out != nullptr) ++aux_out->num_butteraugli_iters;
if (cparams.log_search_state) {
if (JXL_DEBUG_ADAPTIVE_QUANTIZATION) {
float minval, maxval;
ImageMinMax(quant_field, &minval, &maxval);
printf("\nButteraugli iter: %d/%d\n", i, cparams.max_butteraugli_iters);
printf("\nButteraugli iter: %d/%d\n", i, kMaxButteraugliIters);
printf("Butteraugli distance: %f (target = %f)\n", score,
original_butteraugli);
printf("quant range: %f ... %f DC quant: %f\n", minval, maxval,
@ -1016,16 +1027,15 @@ void FindBestQuantizationMaxError(const Image3F& opsin,
1.0f / enc_state->cparams.max_error[1],
1.0f / enc_state->cparams.max_error[2]};
for (int i = 0; i < cparams.max_butteraugli_iters + 1; ++i) {
for (int i = 0; i < kMaxButteraugliIters + 1; ++i) {
quantizer.SetQuantField(initial_quant_dc, quant_field, &raw_quant_field);
if (aux_out) {
aux_out->DumpXybImage(("ops" + ToString(i)).c_str(), opsin);
if (JXL_DEBUG_ADAPTIVE_QUANTIZATION && aux_out) {
DumpXybImage(cparams, ("ops" + ToString(i)).c_str(), opsin);
}
ImageBundle decoded = RoundtripImage(opsin, enc_state, cms, pool);
if (aux_out) {
aux_out->DumpXybImage(("dec" + ToString(i)).c_str(), *decoded.color());
if (JXL_DEBUG_ADAPTIVE_QUANTIZATION && aux_out) {
DumpXybImage(cparams, ("dec" + ToString(i)).c_str(), *decoded.color());
}
for (size_t by = 0; by < enc_state->shared.frame_dim.ysize_blocks; by++) {
AcStrategyRow ac_strategy_row =
enc_state->shared.ac_strategy.ConstRow(by);

View File

@ -44,7 +44,7 @@ ImageF InitialQuantField(float butteraugli_target, const Image3F& opsin,
float InitialQuantDC(float butteraugli_target);
void AdjustQuantField(const AcStrategyImage& ac_strategy, const Rect& rect,
float original_butteraugli, ImageF* quant_field);
float butteraugli_target, ImageF* quant_field);
// Returns a quantizer that uses an adjusted version of the provided
// quant_field. Also computes the dequant_map corresponding to the given

View File

@ -32,7 +32,10 @@ namespace jxl {
namespace {
bool ans_fuzzer_friendly_ = false;
#if !JXL_IS_DEBUG_BUILD
constexpr
#endif
bool ans_fuzzer_friendly_ = false;
static const int kMaxNumSymbolsForSmallCode = 4;
@ -1463,7 +1466,7 @@ void ApplyLZ77(const HistogramParams& params, size_t num_contexts,
} else if (params.lz77_method == HistogramParams::LZ77Method::kOptimal) {
ApplyLZ77_Optimal(params, num_contexts, tokens, lz77, tokens_lz77);
} else {
JXL_ABORT("Not implemented");
JXL_UNREACHABLE("Not implemented");
}
}
} // namespace

View File

@ -16,9 +16,6 @@
#include "lib/jxl/base/printf_macros.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/dec_xyb.h"
#include "lib/jxl/image_ops.h"
namespace jxl {
@ -55,21 +52,24 @@ const char* LayerName(size_t layer) {
case kLayerModularAcGroup:
return "ModularAcGroup";
default:
JXL_ABORT("Invalid layer %d\n", static_cast<int>(layer));
JXL_UNREACHABLE("Invalid layer %d\n", static_cast<int>(layer));
}
}
void AuxOut::LayerTotals::Print(size_t num_inputs) const {
printf("%10" PRId64, static_cast<int64_t>(total_bits));
if (histogram_bits != 0) {
printf(" [c/i:%6.2f | hst:%8" PRId64 " | ex:%8" PRId64 " | h+c+e:%12.3f",
num_clustered_histograms * 1.0 / num_inputs,
static_cast<int64_t>(histogram_bits >> 3),
static_cast<int64_t>(extra_bits >> 3),
(histogram_bits + clustered_entropy + extra_bits) / 8.0);
printf("]");
if (JXL_DEBUG_V_LEVEL > 0) {
printf("%10" PRId64, static_cast<int64_t>(total_bits));
if (histogram_bits != 0) {
printf(" [c/i:%6.2f | hst:%8" PRId64 " | ex:%8" PRId64
" | h+c+e:%12.3f",
num_clustered_histograms * 1.0 / num_inputs,
static_cast<int64_t>(histogram_bits >> 3),
static_cast<int64_t>(extra_bits >> 3),
(histogram_bits + clustered_entropy + extra_bits) / 8.0);
printf("]");
}
printf("\n");
}
printf("\n");
}
void AuxOut::Assimilate(const AuxOut& victim) {
@ -89,117 +89,39 @@ void AuxOut::Assimilate(const AuxOut& victim) {
num_dct32x64_blocks += victim.num_dct32x64_blocks;
num_dct64_blocks += victim.num_dct64_blocks;
num_butteraugli_iters += victim.num_butteraugli_iters;
for (size_t i = 0; i < dc_pred_usage.size(); ++i) {
dc_pred_usage[i] += victim.dc_pred_usage[i];
dc_pred_usage_xb[i] += victim.dc_pred_usage_xb[i];
}
max_quant_rescale = std::max(max_quant_rescale, victim.max_quant_rescale);
min_quant_rescale = std::min(min_quant_rescale, victim.min_quant_rescale);
max_bitrate_error = std::max(max_bitrate_error, victim.max_bitrate_error);
min_bitrate_error = std::min(min_bitrate_error, victim.min_bitrate_error);
}
void AuxOut::Print(size_t num_inputs) const {
if (num_inputs == 0) return;
if (JXL_DEBUG_V_LEVEL > 0) {
if (num_inputs == 0) return;
LayerTotals all_layers;
for (size_t i = 0; i < layers.size(); ++i) {
all_layers.Assimilate(layers[i]);
}
printf("Average butteraugli iters: %10.2f\n",
num_butteraugli_iters * 1.0 / num_inputs);
if (min_quant_rescale != 1.0 || max_quant_rescale != 1.0) {
printf("quant rescale range: %f .. %f\n", min_quant_rescale,
max_quant_rescale);
printf("bitrate error range: %.3f%% .. %.3f%%\n",
100.0f * min_bitrate_error, 100.0f * max_bitrate_error);
}
for (size_t i = 0; i < layers.size(); ++i) {
if (layers[i].total_bits != 0) {
printf("Total layer bits %-10s\t", LayerName(i));
printf("%10f%%", 100.0 * layers[i].total_bits / all_layers.total_bits);
layers[i].Print(num_inputs);
LayerTotals all_layers;
for (size_t i = 0; i < layers.size(); ++i) {
all_layers.Assimilate(layers[i]);
}
}
printf("Total image size ");
all_layers.Print(num_inputs);
const uint32_t dc_pred_total =
std::accumulate(dc_pred_usage.begin(), dc_pred_usage.end(), 0u);
const uint32_t dc_pred_total_xb =
std::accumulate(dc_pred_usage_xb.begin(), dc_pred_usage_xb.end(), 0u);
if (dc_pred_total + dc_pred_total_xb != 0) {
printf("\nDC pred Y XB:\n");
for (size_t i = 0; i < dc_pred_usage.size(); ++i) {
printf(" %6u (%5.2f%%) %6u (%5.2f%%)\n", dc_pred_usage[i],
100.0 * dc_pred_usage[i] / dc_pred_total, dc_pred_usage_xb[i],
100.0 * dc_pred_usage_xb[i] / dc_pred_total_xb);
}
}
printf("Average butteraugli iters: %10.2f\n",
num_butteraugli_iters * 1.0 / num_inputs);
size_t total_blocks = 0;
size_t total_positions = 0;
if (total_blocks != 0 && total_positions != 0) {
printf("\n\t\t Blocks\t\tPositions\t\t\tBlocks/Position\n");
printf(" Total:\t\t %7" PRIuS "\t\t %7" PRIuS " \t\t\t%10f%%\n\n",
total_blocks, total_positions,
100.0 * total_blocks / total_positions);
}
}
template <typename T>
void AuxOut::DumpImage(const char* label, const Image3<T>& image) const {
if (!dump_image) return;
if (debug_prefix.empty()) return;
std::ostringstream pathname;
pathname << debug_prefix << label << ".png";
(void)dump_image(ConvertToFloat(image), ColorEncoding::SRGB(),
pathname.str());
}
template void AuxOut::DumpImage(const char* label,
const Image3<float>& image) const;
template void AuxOut::DumpImage(const char* label,
const Image3<uint8_t>& image) const;
template <typename T>
void AuxOut::DumpPlaneNormalized(const char* label,
const Plane<T>& image) const {
T min;
T max;
ImageMinMax(image, &min, &max);
Image3B normalized(image.xsize(), image.ysize());
for (size_t c = 0; c < 3; ++c) {
float mul = min == max ? 0 : (255.0f / (max - min));
for (size_t y = 0; y < image.ysize(); ++y) {
const T* JXL_RESTRICT row_in = image.ConstRow(y);
uint8_t* JXL_RESTRICT row_out = normalized.PlaneRow(c, y);
for (size_t x = 0; x < image.xsize(); ++x) {
row_out[x] = static_cast<uint8_t>((row_in[x] - min) * mul);
for (size_t i = 0; i < layers.size(); ++i) {
if (layers[i].total_bits != 0) {
printf("Total layer bits %-10s\t", LayerName(i));
printf("%10f%%", 100.0 * layers[i].total_bits / all_layers.total_bits);
layers[i].Print(num_inputs);
}
}
printf("Total image size ");
all_layers.Print(num_inputs);
size_t total_blocks = 0;
size_t total_positions = 0;
if (total_blocks != 0 && total_positions != 0) {
printf("\n\t\t Blocks\t\tPositions\t\t\tBlocks/Position\n");
printf(" Total:\t\t %7" PRIuS "\t\t %7" PRIuS " \t\t\t%10f%%\n\n",
total_blocks, total_positions,
100.0 * total_blocks / total_positions);
}
}
DumpImage(label, normalized);
}
template void AuxOut::DumpPlaneNormalized(const char* label,
const Plane<float>& image) const;
template void AuxOut::DumpPlaneNormalized(const char* label,
const Plane<uint8_t>& image) const;
void AuxOut::DumpXybImage(const char* label, const Image3F& image) const {
if (!dump_image) return;
if (debug_prefix.empty()) return;
std::ostringstream pathname;
pathname << debug_prefix << label << ".png";
Image3F linear(image.xsize(), image.ysize());
OpsinParams opsin_params;
opsin_params.Init(kDefaultIntensityTarget);
OpsinToLinear(image, Rect(linear), nullptr, &linear, opsin_params);
(void)dump_image(std::move(linear), ColorEncoding::LinearSRGB(),
pathname.str());
}
} // namespace jxl

View File

@ -14,9 +14,6 @@
#include <functional>
#include <string>
#include "lib/jxl/image.h"
#include "lib/jxl/jxl_inspection.h"
namespace jxl {
struct ColorEncoding;
@ -82,27 +79,6 @@ struct AuxOut {
return total;
}
template <typename T>
void DumpImage(const char* label, const Image3<T>& image) const;
void DumpXybImage(const char* label, const Image3F& image) const;
template <typename T>
void DumpPlaneNormalized(const char* label, const Plane<T>& image) const;
void SetInspectorImage3F(const jxl::InspectorImage3F& inspector) {
inspector_image3f_ = inspector;
}
// Allows hooking intermediate data inspection into various places of the
// processing pipeline. Returns true iff processing should proceed.
bool InspectImage3F(const char* label, const Image3F& image) {
if (inspector_image3f_ != nullptr) {
return inspector_image3f_(label, image);
}
return true;
}
std::array<LayerTotals, kNumImageLayers> layers;
size_t num_blocks = 0;
@ -119,45 +95,8 @@ struct AuxOut {
size_t num_dct32x64_blocks = 0;
size_t num_dct64_blocks = 0;
std::array<uint32_t, 8> dc_pred_usage = {{0}};
std::array<uint32_t, 8> dc_pred_usage_xb = {{0}};
int num_butteraugli_iters = 0;
float max_quant_rescale = 1.0f;
float min_quant_rescale = 1.0f;
float min_bitrate_error = 0.0f;
float max_bitrate_error = 0.0f;
// If not empty, additional debugging information (e.g. debug images) is
// saved in files with this prefix.
std::string debug_prefix;
// By how much the decoded image was downsampled relative to the encoded
// image.
size_t downsampling = 1;
jxl::InspectorImage3F inspector_image3f_;
std::function<Status(Image3F&&, const ColorEncoding&, const std::string&)>
dump_image = nullptr;
};
extern template void AuxOut::DumpImage(const char* label,
const Image3<float>& image) const;
extern template void AuxOut::DumpImage(const char* label,
const Image3<uint8_t>& image) const;
extern template void AuxOut::DumpPlaneNormalized(
const char* label, const Plane<float>& image) const;
extern template void AuxOut::DumpPlaneNormalized(
const char* label, const Plane<uint8_t>& image) const;
// Used to skip image creation if they won't be written to debug directory.
static inline bool WantDebugOutput(const AuxOut* aux_out) {
// Need valid pointer and filename.
return aux_out != nullptr && !aux_out->debug_prefix.empty();
}
} // namespace jxl
#endif // LIB_JXL_AUX_OUT_H_

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