Bug 1800284 - Update libjxl r=tnikkel

Differential Revision: https://phabricator.services.mozilla.com/D162008
This commit is contained in:
Kagami Sascha Rosylight 2022-11-15 00:34:56 +00:00
parent 2707c9dabc
commit 26ed19454c
35 changed files with 2946 additions and 448 deletions

View File

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

View File

@ -20,7 +20,6 @@ SOURCES += [
"/third_party/jpeg-xl/lib/jxl/base/padded_bytes.cc",
"/third_party/jpeg-xl/lib/jxl/base/random.cc",
"/third_party/jpeg-xl/lib/jxl/blending.cc",
"/third_party/jpeg-xl/lib/jxl/box_content_decoder.cc",
"/third_party/jpeg-xl/lib/jxl/chroma_from_luma.cc",
"/third_party/jpeg-xl/lib/jxl/coeff_order.cc",
"/third_party/jpeg-xl/lib/jxl/color_encoding_internal.cc",
@ -45,7 +44,6 @@ 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/decode_to_jpeg.cc",
"/third_party/jpeg-xl/lib/jxl/enc_bit_writer.cc",
"/third_party/jpeg-xl/lib/jxl/entropy_coder.cc",
"/third_party/jpeg-xl/lib/jxl/epf.cc",
@ -60,9 +58,6 @@ SOURCES += [
"/third_party/jpeg-xl/lib/jxl/image.cc",
"/third_party/jpeg-xl/lib/jxl/image_bundle.cc",
"/third_party/jpeg-xl/lib/jxl/image_metadata.cc",
"/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data.cc",
"/third_party/jpeg-xl/lib/jxl/jpeg/dec_jpeg_data_writer.cc",
"/third_party/jpeg-xl/lib/jxl/jpeg/jpeg_data.cc",
"/third_party/jpeg-xl/lib/jxl/loop_filter.cc",
"/third_party/jpeg-xl/lib/jxl/luminance.cc",
"/third_party/jpeg-xl/lib/jxl/memory_manager_internal.cc",
@ -123,6 +118,9 @@ EXPORTS.jxl += [
"/third_party/jpeg-xl/lib/include/jxl/types.h",
]
DEFINES["JPEGXL_ENABLE_BOXES"] = "0"
DEFINES["JPEGXL_ENABLE_TRANSCODE_JPEG"] = "0"
FINAL_LIBRARY = "gkmedias"
# We allow warnings for third-party code that can be updated from upstream.

View File

@ -10,9 +10,9 @@ origin:
url: https://github.com/libjxl/libjxl
release: 99b0721251b51028927c56a81657cf76de7ea320 (2022-10-20T11:29:24Z).
release: afa493d9c7c8b47b6ce709180a74a49085291776 (2022-11-12T22:27:21Z).
revision: 99b0721251b51028927c56a81657cf76de7ea320
revision: afa493d9c7c8b47b6ce709180a74a49085291776
license: Apache-2.0

View File

@ -68,6 +68,16 @@ jobs:
-DJPEGXL_ENABLE_TRANSCODE_JPEG=OFF
-DJPEGXL_ENABLE_PLUGINS=OFF
-DJPEGXL_ENABLE_VIEWERS=OFF
# Build optimized for binary size, all features not needed for
# reconstructing pixels is disabled.
- name: release:minimal
mode: release
cxxflags: -DJXL_DEBUG_ON_ABORT=0
cmake_args: >-
-DJPEGXL_ENABLE_TRANSCODE_JPEG=OFF
-DJPEGXL_ENABLE_BOXES=OFF
-DJPEGXL_ENABLE_PLUGINS=OFF
-DJPEGXL_ENABLE_VIEWERS=OFF
# Builds with gcc in release mode
- name: release:gcc8
mode: release
@ -394,7 +404,7 @@ jobs:
- name: Set EMSDK node version
run: |
echo "NODE_JS='$(cat $HOME/.base_node_path)'" >> $EM_CONFIG
echo "NODE_JS='$(cat $HOME/.base_node_path)'" >> $EMSDK/.emscripten
emsdk construct_env
# TODO(deymo): Build and install other dependencies like libpng, libjpeg,

View File

@ -29,6 +29,7 @@ David Burnett <vargolsoft@gmail.com>
Dirk Lemstra <dirk@lemstra.org>
Don Olmstead <don.j.olmstead@gmail.com>
Even Rouault <even.rouault@spatialys.com>
Fred Brennan <copypaste@kittens.ph>
Heiko Becker <heirecka@exherbo.org>
Jon Sneyers <jon@cloudinary.com>
Kai Hollberg <Schweinepriester@users.noreply.github.com>
@ -43,6 +44,7 @@ Mathieu Malaterre <mathieu.malaterre@gmail.com>
Mikk Leini <mikk.leini@krakul.eu>
Misaki Kasumi <misakikasumi@outlook.com>
Nicholas Hayes <0xC0000054@users.noreply.github.com>
Nigel Tao <nigeltao@golang.org>
Petr Diblík
Pieter Wuille
roland-rollo

View File

@ -136,6 +136,10 @@ set(JPEGXL_ENABLE_TRANSCODE_JPEG true CACHE BOOL
"Builds in support for decoding transcoded JXL files back to JPEG,\
disabling it makes the decoder reject JXL_DEC_JPEG_RECONSTRUCTION events,\
(default enabled)")
set(JPEGXL_ENABLE_BOXES true CACHE BOOL
"Builds in support for decoding boxes in JXL files,\
disabling it makes the decoder reject JXL_DEC_BOX events,\
(default enabled)")
set(JPEGXL_STATIC false CACHE BOOL
"Build tools as static binaries.")
set(JPEGXL_WARNINGS_AS_ERRORS ${WARNINGS_AS_ERRORS_DEFAULT} CACHE BOOL

View File

@ -576,7 +576,7 @@ cmd_test() {
(cd "${BUILD_DIR}"
export UBSAN_OPTIONS=print_stacktrace=1
[[ "${TEST_STACK_LIMIT}" == "none" ]] || ulimit -s "${TEST_STACK_LIMIT}"
ctest -j $(nproc --all || echo 1) --output-on-failure "$@")
ctest -j $(nproc --all || echo 1) ${TEST_SELECTOR} --output-on-failure "$@")
}
cmd_gbench() {

View File

@ -15,7 +15,7 @@ MYDIR=$(dirname $(realpath "$0"))
# update a git submodule.
THIRD_PARTY_BROTLI="35ef5c554d888bef217d449346067de05e269b30"
THIRD_PARTY_HIGHWAY="22e3d7276f4157d4a47586ba9fd91dd6303f441a"
THIRD_PARTY_SKCMS="64374756e03700d649f897dbd98c95e78c30c7da"
THIRD_PARTY_SKCMS="b25b07b4b07990811de121c0356155b2ba0f4318"
THIRD_PARTY_SJPEG="868ab558fad70fcbe8863ba4e85179eeb81cc840"
THIRD_PARTY_ZLIB="cacf7f1d4e3d44d871b605da3b647f07d718623f"
THIRD_PARTY_LIBPNG="a40189cf881e9f0db80511c382292a5604c3c3d1"

View File

@ -20,6 +20,8 @@
#error "system not known to be little endian"
#endif
namespace {
struct BitWriter {
void Allocate(size_t maximum_bit_size) {
assert(data == nullptr);
@ -143,10 +145,10 @@ struct PrefixCode {
static void ComputeCodeLengths(uint64_t* freqs, size_t n, size_t limit,
uint8_t* nbits) {
if (n <= 1) return;
assert(n <= (1 << limit));
assert(n <= (1u << limit));
assert(n <= 32);
int parent[64] = {};
int height[64] = {};
unsigned int parent[64] = {};
unsigned int height[64] = {};
using QElem = std::pair<uint64_t, size_t>;
std::priority_queue<QElem, std::vector<QElem>, std::greater<QElem>> q;
// Standard Huffman code construction. On failure (i.e. if going beyond the
@ -1088,8 +1090,8 @@ void PrepareDCGlobalPalette(bool is_single_group, size_t width, size_t height,
encoder.code = &code;
int16_t p[4][32 + 1024] = {};
uint8_t prgba[4];
int i = 0;
int have_zero = 0;
size_t i = 0;
size_t have_zero = 0;
if (palette[pcolors_real - 1] == 0) have_zero = 1;
for (; i < pcolors; i++) {
if (i < pcolors_real) {
@ -1184,7 +1186,7 @@ size_t LLEnc(const unsigned char* rgba, size_t width, size_t stride,
if (palette[0] == 1) palette[0] = 0;
bool have_color = false;
uint8_t minG = 255, maxG = 0;
for (int k = 0; k < kHashSize; k++) {
for (uint32_t k = 0; k < kHashSize; k++) {
if (palette[k] == 0) continue;
uint8_t p[4];
memcpy(p, &palette[k], 4);
@ -1297,7 +1299,9 @@ size_t LLEnc(const unsigned char* rgba, size_t width, size_t stride,
PrepareDCGlobalPalette(onegroup, width, height, hcode, palette, pcolors,
&group_data[0][0]);
}
#ifdef _OPENMP
#pragma omp parallel for
#endif
for (size_t g = 0; g < num_groups_y * num_groups_x; g++) {
size_t xg = g % num_groups_x;
size_t yg = g / num_groups_x;
@ -1324,9 +1328,16 @@ size_t LLEnc(const unsigned char* rgba, size_t width, size_t stride,
return writer.bytes_written;
}
size_t FastLosslessEncode(const unsigned char* rgba, size_t width,
size_t stride, size_t height, size_t nb_chans,
size_t bitdepth, int effort, unsigned char** output) {
} // namespace
#ifdef __cplusplus
extern "C" {
#endif
size_t JxlFastLosslessEncode(const unsigned char* rgba, size_t width,
size_t stride, size_t height, size_t nb_chans,
size_t bitdepth, int effort,
unsigned char** output) {
assert(bitdepth <= 12);
assert(bitdepth > 0);
assert(nb_chans <= 4);
@ -1360,3 +1371,7 @@ size_t FastLosslessEncode(const unsigned char* rgba, size_t width,
}
return 0;
}
#ifdef __cplusplus
} // extern "C"
#endif

View File

@ -7,8 +7,17 @@
#define FAST_LOSSLESS_H
#include <stdlib.h>
size_t FastLosslessEncode(const unsigned char* rgba, size_t width,
size_t row_stride, size_t height, size_t nb_chans,
size_t bitdepth, int effort, unsigned char** output);
#ifdef __cplusplus
extern "C" {
#endif
size_t JxlFastLosslessEncode(const unsigned char* rgba, size_t width,
size_t row_stride, size_t height, size_t nb_chans,
size_t bitdepth, int effort,
unsigned char** output);
#ifdef __cplusplus
} // extern "C"
#endif
#endif

View File

@ -51,8 +51,8 @@ int main(int argc, char** argv) {
auto start = std::chrono::high_resolution_clock::now();
for (size_t _ = 0; _ < num_reps; _++) {
free(encoded);
encoded_size = FastLosslessEncode(png, width, stride, height, nb_chans,
bitdepth, effort, &encoded);
encoded_size = JxlFastLosslessEncode(png, width, stride, height, nb_chans,
bitdepth, effort, &encoded);
}
auto stop = std::chrono::high_resolution_clock::now();
if (num_reps > 1) {

View File

@ -102,7 +102,7 @@ Status Encode(const CodecInOut& io, const extras::Codec codec,
encoder = extras::GetPPMEncoder();
} else {
format.data_type = JXL_TYPE_FLOAT;
format.endianness = JXL_NATIVE_ENDIAN;
format.endianness = JXL_LITTLE_ENDIAN;
encoder = extras::GetPFMEncoder();
}
break;
@ -131,6 +131,9 @@ Status Encode(const CodecInOut& io, const extras::Codec codec,
JXL_RETURN_IF_ERROR(
ConvertCodecInOutToPackedPixelFile(io, format, c_desired, pool, &ppf));
ppf.info.bits_per_sample = bits_per_sample;
if (format.data_type == JXL_TYPE_FLOAT) {
ppf.info.exponent_bits_per_sample = 8;
}
extras::EncodedImage encoded_image;
JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded_image, pool));
JXL_ASSERT(encoded_image.bitstreams.size() == 1);

View File

@ -283,6 +283,10 @@ bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
// color space.
ppf->info.num_color_channels = num_color_channels;
}
if (dparams.output_bitdepth.type == JXL_BIT_DEPTH_CUSTOM) {
// Select format based on custom bits per sample.
ppf->info.bits_per_sample = dparams.output_bitdepth.bits_per_sample;
}
// Select format according to accepted formats.
if (!jxl::extras::SelectFormat(accepted_formats, ppf->info, &format)) {
fprintf(stderr, "SelectFormat failed\n");

View File

@ -12,28 +12,43 @@
#include <memory>
#include <utility>
#ifdef MEMORY_SANITIZER
#define JXL_MEMORY_SANITIZER 1
#elif defined(__has_feature)
#if __has_feature(memory_sanitizer)
#define JXL_MEMORY_SANITIZER 1
#else
#define JXL_MEMORY_SANITIZER 0
#endif
#else
#define JXL_MEMORY_SANITIZER 0
#endif
#if JXL_MEMORY_SANITIZER
#include "sanitizer/msan_interface.h"
#endif
#undef HWY_TARGET_INCLUDE
#define HWY_TARGET_INCLUDE "lib/extras/dec_group_jpeg.cc"
#include <hwy/foreach_target.h>
#include <hwy/highway.h>
#include "lib/jxl/base/status.h"
#include "lib/jxl/dct_scales.h"
#include "lib/jxl/dec_transforms-inl.h"
#include "lib/jxl/sanitizers.h"
#include "lib/jxl/simd_util-inl.h"
HWY_BEFORE_NAMESPACE();
namespace jxl {
namespace HWY_NAMESPACE {
// These templates are not found via ADL.
using hwy::HWY_NAMESPACE::Abs;
using hwy::HWY_NAMESPACE::Add;
using hwy::HWY_NAMESPACE::Clamp;
using hwy::HWY_NAMESPACE::Gt;
using hwy::HWY_NAMESPACE::IfThenElseZero;
using hwy::HWY_NAMESPACE::Mul;
using hwy::HWY_NAMESPACE::MulAdd;
using hwy::HWY_NAMESPACE::NearestInt;
using hwy::HWY_NAMESPACE::NegMulAdd;
using hwy::HWY_NAMESPACE::Rebind;
using hwy::HWY_NAMESPACE::Sub;
using hwy::HWY_NAMESPACE::Vec;
using hwy::HWY_NAMESPACE::Xor;
@ -42,8 +57,11 @@ using DI = HWY_FULL(int32_t);
constexpr D d;
constexpr DI di;
void GatherBlockStats(const int16_t* coeffs, const size_t coeffs_size,
int32_t* JXL_RESTRICT nonzeros,
using D8 = HWY_CAPPED(float, 8);
constexpr D8 d8;
void GatherBlockStats(const int16_t* JXL_RESTRICT coeffs,
const size_t coeffs_size, int32_t* JXL_RESTRICT nonzeros,
int32_t* JXL_RESTRICT sumabs) {
for (size_t i = 0; i < coeffs_size; i += Lanes(d)) {
size_t k = i % kDCTBlockSize;
@ -76,16 +94,274 @@ void DequantBlock(const int16_t* JXL_RESTRICT qblock,
}
}
void DecodeJpegBlock(const int16_t* qblock, const float* JXL_RESTRICT dequant,
#if HWY_CAP_GE256
JXL_INLINE void Transpose8x8Block(const float* JXL_RESTRICT from,
float* JXL_RESTRICT to) {
const D8 d;
auto i0 = Load(d, from);
auto i1 = Load(d, from + 1 * 8);
auto i2 = Load(d, from + 2 * 8);
auto i3 = Load(d, from + 3 * 8);
auto i4 = Load(d, from + 4 * 8);
auto i5 = Load(d, from + 5 * 8);
auto i6 = Load(d, from + 6 * 8);
auto i7 = Load(d, from + 7 * 8);
const auto q0 = InterleaveLower(d, i0, i2);
const auto q1 = InterleaveLower(d, i1, i3);
const auto q2 = InterleaveUpper(d, i0, i2);
const auto q3 = InterleaveUpper(d, i1, i3);
const auto q4 = InterleaveLower(d, i4, i6);
const auto q5 = InterleaveLower(d, i5, i7);
const auto q6 = InterleaveUpper(d, i4, i6);
const auto q7 = InterleaveUpper(d, i5, i7);
const auto r0 = InterleaveLower(d, q0, q1);
const auto r1 = InterleaveUpper(d, q0, q1);
const auto r2 = InterleaveLower(d, q2, q3);
const auto r3 = InterleaveUpper(d, q2, q3);
const auto r4 = InterleaveLower(d, q4, q5);
const auto r5 = InterleaveUpper(d, q4, q5);
const auto r6 = InterleaveLower(d, q6, q7);
const auto r7 = InterleaveUpper(d, q6, q7);
i0 = ConcatLowerLower(d, r4, r0);
i1 = ConcatLowerLower(d, r5, r1);
i2 = ConcatLowerLower(d, r6, r2);
i3 = ConcatLowerLower(d, r7, r3);
i4 = ConcatUpperUpper(d, r4, r0);
i5 = ConcatUpperUpper(d, r5, r1);
i6 = ConcatUpperUpper(d, r6, r2);
i7 = ConcatUpperUpper(d, r7, r3);
Store(i0, d, to);
Store(i1, d, to + 1 * 8);
Store(i2, d, to + 2 * 8);
Store(i3, d, to + 3 * 8);
Store(i4, d, to + 4 * 8);
Store(i5, d, to + 5 * 8);
Store(i6, d, to + 6 * 8);
Store(i7, d, to + 7 * 8);
}
#elif HWY_TARGET != HWY_SCALAR
JXL_INLINE void Transpose8x8Block(const float* JXL_RESTRICT from,
float* JXL_RESTRICT to) {
const HWY_CAPPED(float, 4) d;
for (size_t n = 0; n < 8; n += 4) {
for (size_t m = 0; m < 8; m += 4) {
auto p0 = Load(d, from + n * 8 + m);
auto p1 = Load(d, from + (n + 1) * 8 + m);
auto p2 = Load(d, from + (n + 2) * 8 + m);
auto p3 = Load(d, from + (n + 3) * 8 + m);
const auto q0 = InterleaveLower(d, p0, p2);
const auto q1 = InterleaveLower(d, p1, p3);
const auto q2 = InterleaveUpper(d, p0, p2);
const auto q3 = InterleaveUpper(d, p1, p3);
const auto r0 = InterleaveLower(d, q0, q1);
const auto r1 = InterleaveUpper(d, q0, q1);
const auto r2 = InterleaveLower(d, q2, q3);
const auto r3 = InterleaveUpper(d, q2, q3);
Store(r0, d, to + m * 8 + n);
Store(r1, d, to + (1 + m) * 8 + n);
Store(r2, d, to + (2 + m) * 8 + n);
Store(r3, d, to + (3 + m) * 8 + n);
}
}
}
#else
JXL_INLINE void Transpose8x8Block(const float* JXL_RESTRICT from,
float* JXL_RESTRICT to) {
for (size_t n = 0; n < 8; ++n) {
for (size_t m = 0; m < 8; ++m) {
to[8 * n + m] = from[8 * m + n];
}
}
}
#endif
template <size_t N>
void ForwardEvenOdd(const float* JXL_RESTRICT ain, size_t ain_stride,
float* JXL_RESTRICT aout) {
for (size_t i = 0; i < N / 2; i++) {
auto in1 = LoadU(d8, ain + 2 * i * ain_stride);
Store(in1, d8, aout + i * 8);
}
for (size_t i = N / 2; i < N; i++) {
auto in1 = LoadU(d8, ain + (2 * (i - N / 2) + 1) * ain_stride);
Store(in1, d8, aout + i * 8);
}
}
template <size_t N>
void BTranspose(float* JXL_RESTRICT coeff) {
for (size_t i = N - 1; i > 0; i--) {
auto in1 = Load(d8, coeff + i * 8);
auto in2 = Load(d8, coeff + (i - 1) * 8);
Store(Add(in1, in2), d8, coeff + i * 8);
}
constexpr float kSqrt2 = 1.41421356237f;
auto sqrt2 = Set(d8, kSqrt2);
auto in1 = Load(d8, coeff);
Store(Mul(in1, sqrt2), d8, coeff);
}
// Constants for DCT implementation. Generated by the following snippet:
// for i in range(N // 2):
// print(1.0 / (2 * math.cos((i + 0.5) * math.pi / N)), end=", ")
template <size_t N>
struct WcMultipliers;
template <>
struct WcMultipliers<4> {
static constexpr float kMultipliers[] = {
0.541196100146197,
1.3065629648763764,
};
};
template <>
struct WcMultipliers<8> {
static constexpr float kMultipliers[] = {
0.5097955791041592,
0.6013448869350453,
0.8999762231364156,
2.5629154477415055,
};
};
constexpr float WcMultipliers<4>::kMultipliers[];
constexpr float WcMultipliers<8>::kMultipliers[];
template <size_t N>
void MultiplyAndAdd(const float* JXL_RESTRICT coeff, float* JXL_RESTRICT out,
size_t out_stride) {
for (size_t i = 0; i < N / 2; i++) {
auto mul = Set(d8, WcMultipliers<N>::kMultipliers[i]);
auto in1 = Load(d8, coeff + i * 8);
auto in2 = Load(d8, coeff + (N / 2 + i) * 8);
auto out1 = MulAdd(mul, in2, in1);
auto out2 = NegMulAdd(mul, in2, in1);
StoreU(out1, d8, out + i * out_stride);
StoreU(out2, d8, out + (N - i - 1) * out_stride);
}
}
template <size_t N>
struct IDCT1DImpl;
template <>
struct IDCT1DImpl<1> {
JXL_INLINE void operator()(const float* from, size_t from_stride, float* to,
size_t to_stride) {
StoreU(LoadU(d8, from), d8, to);
}
};
template <>
struct IDCT1DImpl<2> {
JXL_INLINE void operator()(const float* from, size_t from_stride, float* to,
size_t to_stride) {
JXL_DASSERT(from_stride >= 8);
JXL_DASSERT(to_stride >= 8);
auto in1 = LoadU(d8, from);
auto in2 = LoadU(d8, from + from_stride);
StoreU(Add(in1, in2), d8, to);
StoreU(Sub(in1, in2), d8, to + to_stride);
}
};
template <size_t N>
struct IDCT1DImpl {
void operator()(const float* from, size_t from_stride, float* to,
size_t to_stride) {
JXL_DASSERT(from_stride >= 8);
JXL_DASSERT(to_stride >= 8);
HWY_ALIGN float tmp[64];
ForwardEvenOdd<N>(from, from_stride, tmp);
IDCT1DImpl<N / 2>()(tmp, 8, tmp, 8);
BTranspose<N / 2>(tmp + N * 4);
IDCT1DImpl<N / 2>()(tmp + N * 4, 8, tmp + N * 4, 8);
MultiplyAndAdd<N>(tmp, to, to_stride);
}
};
template <size_t N>
void IDCT1D(float* JXL_RESTRICT from, float* JXL_RESTRICT output,
size_t output_stride) {
for (size_t i = 0; i < 8; i += Lanes(d8)) {
IDCT1DImpl<N>()(from + i, 8, output + i, output_stride);
}
}
void ComputeScaledIDCT(float* JXL_RESTRICT block0, float* JXL_RESTRICT block1,
float* JXL_RESTRICT output, size_t output_stride) {
Transpose8x8Block(block0, block1);
IDCT1D<8>(block1, block0, 8);
Transpose8x8Block(block0, block1);
IDCT1D<8>(block1, output, output_stride);
}
void DecodeJpegBlock(const int16_t* JXL_RESTRICT qblock,
const float* JXL_RESTRICT dequant,
const float* JXL_RESTRICT biases,
float* JXL_RESTRICT scratch_space,
float* JXL_RESTRICT output, size_t output_stride) {
HWY_ALIGN float* const block = scratch_space + kDCTBlockSize;
DequantBlock(qblock, dequant, biases, scratch_space);
// JPEG XL transposes the DCT, JPEG doesn't.
Transpose<8, 8>::Run(DCTFrom(scratch_space, 8), DCTTo(block, 8));
TransformToPixels(AcStrategy::DCT, block, output, output_stride,
scratch_space);
float* JXL_RESTRICT block0 = scratch_space;
float* JXL_RESTRICT block1 = scratch_space + kDCTBlockSize;
DequantBlock(qblock, dequant, biases, block0);
ComputeScaledIDCT(block0, block1, output, output_stride);
}
#if HWY_CAP_GE512
using hwy::HWY_NAMESPACE::Half;
using hwy::HWY_NAMESPACE::Vec;
template <size_t i, class DF, class V>
HWY_INLINE Vec<Half<Half<DF>>> Quarter(const DF df, V v) {
using HF = Half<DF>;
using HHF = Half<HF>;
auto half = i >= 2 ? UpperHalf(HF(), v) : LowerHalf(HF(), v);
return i & 1 ? UpperHalf(HHF(), half) : LowerHalf(HHF(), half);
}
template <class DF, class V>
HWY_INLINE Vec<DF> Concat4(const DF df, V v0, V v1, V v2, V v3) {
using HF = Half<DF>;
return Combine(DF(), Combine(HF(), v3, v2), Combine(HF(), v1, v0));
}
#endif
// Stores v0[0], v1[0], v0[1], v1[1], ... to mem, in this order. Mem must be
// aligned.
template <class DF, class V, typename T>
void StoreInterleaved(const DF df, V v0, V v1, T* mem) {
static_assert(sizeof(T) == 4, "only use StoreInterleaved for 4-byte types");
#if HWY_TARGET == HWY_SCALAR
Store(v0, df, mem);
Store(v1, df, mem + 1);
#elif !HWY_CAP_GE256
Store(InterleaveLower(df, v0, v1), df, mem);
Store(InterleaveUpper(df, v0, v1), df, mem + Lanes(df));
#else
if (!HWY_CAP_GE512 || Lanes(df) == 8) {
auto t0 = InterleaveLower(df, v0, v1);
auto t1 = InterleaveUpper(df, v0, v1);
Store(ConcatLowerLower(df, t1, t0), df, mem);
Store(ConcatUpperUpper(df, t1, t0), df, mem + Lanes(df));
} else {
#if HWY_CAP_GE512
auto t0 = InterleaveLower(df, v0, v1);
auto t1 = InterleaveUpper(df, v0, v1);
Store(Concat4(df, Quarter<0>(df, t0), Quarter<0>(df, t1),
Quarter<1>(df, t0), Quarter<1>(df, t1)),
df, mem);
Store(Concat4(df, Quarter<2>(df, t0), Quarter<2>(df, t1),
Quarter<3>(df, t0), Quarter<3>(df, t1)),
df, mem + Lanes(df));
#endif
}
#endif
}
void Upsample2Horizontal(float* JXL_RESTRICT row_in,
@ -165,10 +441,12 @@ void StoreUnsignedRow(float* JXL_RESTRICT input[3], size_t x0, size_t len,
auto one = Set(d, 1.0f);
auto mul = Set(d, (1u << (sizeof(T) * 8)) - 1);
const Rebind<T, decltype(d)> du;
#if JXL_MEMORY_SANITIZER
const size_t padding = RoundUpTo(len, Lanes(d)) - len;
for (size_t c = 0; c < num_channels; ++c) {
msan::UnpoisonMemory(input[c] + x0 + len, sizeof(input[c][0]) * padding);
__msan_unpoison(input[c] + x0 + len, sizeof(input[c][0]) * padding);
}
#endif
if (num_channels == 1) {
for (size_t i = 0; i < len; i += Lanes(d)) {
auto v0 = Mul(Clamp(zero, Load(d, &input[0][x0 + i]), one), mul);
@ -184,8 +462,10 @@ void StoreUnsignedRow(float* JXL_RESTRICT input[3], size_t x0, size_t len,
DemoteTo(du, NearestInt(v2)), du, &output[3 * i]);
}
}
msan::PoisonMemory(output + num_channels * len,
sizeof(output[0]) * num_channels * padding);
#if JXL_MEMORY_SANITIZER
__msan_poison(output + num_channels * len,
sizeof(output[0]) * num_channels * padding);
#endif
}
void WriteToPackedImage(float* JXL_RESTRICT rows[3], size_t x0, size_t y0,
@ -228,14 +508,14 @@ HWY_EXPORT(WriteToPackedImage);
namespace extras {
void GatherBlockStats(const int16_t* coeffs, const size_t coeffs_size,
int32_t* JXL_RESTRICT nonzeros,
void GatherBlockStats(const int16_t* JXL_RESTRICT coeffs,
const size_t coeffs_size, int32_t* JXL_RESTRICT nonzeros,
int32_t* JXL_RESTRICT sumabs) {
return HWY_DYNAMIC_DISPATCH(GatherBlockStats)(coeffs, coeffs_size, nonzeros,
sumabs);
}
void DecodeJpegBlock(const int16_t* qblock,
void DecodeJpegBlock(const int16_t* JXL_RESTRICT qblock,
const float* JXL_RESTRICT dequant_matrices,
const float* JXL_RESTRICT biases,
float* JXL_RESTRICT scratch_space,

View File

@ -12,17 +12,16 @@
#include <vector>
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/image.h"
#include "lib/jxl/base/compiler_specific.h"
namespace jxl {
namespace extras {
void GatherBlockStats(const int16_t* coeffs, const size_t coeffs_size,
int32_t* JXL_RESTRICT nonzeros,
void GatherBlockStats(const int16_t* JXL_RESTRICT coeffs,
const size_t coeffs_size, int32_t* JXL_RESTRICT nonzeros,
int32_t* JXL_RESTRICT sumabs);
void DecodeJpegBlock(const int16_t* qblock,
void DecodeJpegBlock(const int16_t* JXL_RESTRICT qblock,
const float* JXL_RESTRICT dequant_matrices,
const float* JXL_RESTRICT biases,
float* JXL_RESTRICT scratch_space,

File diff suppressed because it is too large Load Diff

View File

@ -8,14 +8,264 @@
#include <stdint.h>
#include <array>
#include <vector>
#include "hwy/aligned_allocator.h"
#include "lib/extras/packed_image.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/image.h"
namespace jxl {
namespace extras {
constexpr int kMaxComponents = 4;
typedef int16_t coeff_t;
// Represents one component of a jpeg file.
struct JPEGComponent {
JPEGComponent()
: id(0),
h_samp_factor(1),
v_samp_factor(1),
quant_idx(0),
width_in_blocks(0),
height_in_blocks(0) {}
// One-byte id of the component.
uint32_t id;
// Horizontal and vertical sampling factors.
// In interleaved mode, each minimal coded unit (MCU) has
// h_samp_factor x v_samp_factor DCT blocks from this component.
int h_samp_factor;
int v_samp_factor;
// The index of the quantization table used for this component.
uint32_t quant_idx;
// The dimensions of the component measured in 8x8 blocks.
uint32_t width_in_blocks;
uint32_t height_in_blocks;
// The DCT coefficients of this component, laid out block-by-block, divided
// through the quantization matrix values.
hwy::AlignedFreeUniquePtr<coeff_t[]> coeffs;
};
struct HuffmanTableEntry {
// Initialize the value to an invalid symbol so that we can recognize it
// when reading the bit stream using a Huffman code with space > 0.
HuffmanTableEntry() : bits(0), value(0xffff) {}
uint8_t bits; // number of bits used for this symbol
uint16_t value; // symbol value or table offset
};
// Quantization values for an 8x8 pixel block.
struct JPEGQuantTable {
std::array<int32_t, kDCTBlockSize> values;
// The index of this quantization table as it was parsed from the input JPEG.
// Each DQT marker segment contains an 'index' field, and we save this index
// here. Valid values are 0 to 3.
uint32_t index = 0;
};
// Huffman table indexes and MCU dimensions used for one component of one scan.
struct JPEGComponentScanInfo {
uint32_t comp_idx;
uint32_t dc_tbl_idx;
uint32_t ac_tbl_idx;
uint32_t mcu_ysize_blocks;
uint32_t mcu_xsize_blocks;
};
// Contains information that is used in one scan.
struct JPEGScanInfo {
// Parameters used for progressive scans (named the same way as in the spec):
// Ss : Start of spectral band in zig-zag sequence.
// Se : End of spectral band in zig-zag sequence.
// Ah : Successive approximation bit position, high.
// Al : Successive approximation bit position, low.
uint32_t Ss;
uint32_t Se;
uint32_t Ah;
uint32_t Al;
uint32_t num_components = 0;
std::array<JPEGComponentScanInfo, kMaxComponents> components;
size_t MCU_rows;
size_t MCU_cols;
};
// State of the decoder that has to be saved before decoding one MCU in case
// we run out of the bitstream.
struct MCUCodingState {
coeff_t last_dc_coeff[kMaxComponents];
int eobrun;
std::vector<coeff_t> coeffs;
};
// Streaming JPEG decoding object.
class JpegDecoder {
public:
enum class Status {
kSuccess,
kNeedMoreInput,
kError,
};
// Sets the next chunk of input. It must be called before the first call to
// ReadHeaders() and every time a reder function returns
// Status::kNeedMoreInput.
Status SetInput(const uint8_t* data, size_t len);
// Sets the output image. Must be called between ReadHeaders() and
// ReadScanLines(). The provided image must have the dimensions and number of
// channels as the underlying JPEG bitstream.
Status SetOutput(PackedImage* image);
// Reads the header markers up to and including SOF marker. After this returns
// kSuccess, the image attribute accessors can be called.
Status ReadHeaders();
// Reads the bitstream after the SOF marker, and fills in at most
// max_output_rows scan lines of the provided image. Set *num_output_rows to
// the actual number of lines produced.
Status ReadScanLines(size_t* num_output_rows, size_t max_output_rows);
// Image attribute accessors, can be called after ReadHeaders() returns
// kSuccess.
size_t xsize() const { return xsize_; }
size_t ysize() const { return ysize_; }
size_t num_channels() const { return components_.size(); }
const std::vector<uint8_t>& icc_profile() const { return icc_profile_; }
private:
enum class State {
kStart,
kProcessMarkers,
kScan,
kRender,
kEnd,
};
State state_ = State::kStart;
//
// Input handling state.
//
const uint8_t* next_in_ = nullptr;
size_t avail_in_ = 0;
// Codestream input data is copied here temporarily when the decoder needs
// more input bytes to process the next part of the stream.
std::vector<uint8_t> codestream_copy_;
// Number of bytes at the end of codestream_copy_ that were not yet consumed
// by calling AdvanceInput().
size_t codestream_unconsumed_ = 0;
// Position in the codestream_copy_ vector that the decoder already finished
// processing.
size_t codestream_pos_ = 0;
// Number of bits after codestream_pos_ that were already processed.
size_t codestream_bits_ahead_ = 0;
//
// Marker data processing state.
//
bool found_soi_ = false;
bool found_app0_ = false;
bool found_dri_ = false;
bool found_sof_ = false;
bool found_eoi_ = false;
size_t xsize_ = 0;
size_t ysize_ = 0;
bool is_ycbcr_ = true;
size_t icc_index_ = 0;
size_t icc_total_ = 0;
std::vector<uint8_t> icc_profile_;
size_t restart_interval_ = 0;
std::vector<JPEGQuantTable> quant_;
std::vector<JPEGComponent> components_;
std::vector<HuffmanTableEntry> dc_huff_lut_;
std::vector<HuffmanTableEntry> ac_huff_lut_;
uint8_t huff_slot_defined_[256] = {};
// Fields defined by SOF marker.
bool is_progressive_;
int max_h_samp_;
int max_v_samp_;
size_t iMCU_rows_;
size_t iMCU_cols_;
size_t iMCU_width_;
size_t iMCU_height_;
// Initialized at strat of frame.
uint16_t scan_progression_[kMaxComponents][kDCTBlockSize];
//
// Per scan state.
//
JPEGScanInfo scan_info_;
size_t scan_mcu_row_;
size_t scan_mcu_col_;
coeff_t last_dc_coeff_[kMaxComponents];
int eobrun_;
int restarts_to_go_;
int next_restart_marker_;
MCUCodingState mcu_;
//
// Rendering state.
//
PackedImage* output_;
Image3F MCU_row_buf_;
size_t MCU_buf_current_row_;
size_t MCU_buf_ready_rows_;
size_t output_row_;
size_t output_mcu_row_;
size_t output_ci_;
// Temporary buffers for vertically upsampled chroma components. We keep a
// ringbuffer of 3 * kBlockDim rows so that we have access for previous and
// next rows.
std::vector<ImageF> chroma_;
// In the rendering order, vertically upsampled chroma components come first.
std::vector<size_t> component_order_;
hwy::AlignedFreeUniquePtr<float[]> idct_scratch_;
hwy::AlignedFreeUniquePtr<float[]> upsample_scratch_;
hwy::AlignedFreeUniquePtr<uint8_t[]> output_scratch_;
hwy::AlignedFreeUniquePtr<float[]> dequant_;
// Per channel and per frequency statistics about the number of nonzeros and
// the sum of coefficient absolute values, used in dequantization bias
// computation.
hwy::AlignedFreeUniquePtr<int[]> nonzeros_;
hwy::AlignedFreeUniquePtr<int[]> sumabs_;
std::vector<size_t> num_processed_blocks_;
hwy::AlignedFreeUniquePtr<float[]> biases_;
void AdvanceInput(size_t size);
void AdvanceCodestream(size_t size);
Status RequestMoreInput();
Status GetCodestreamInput(const uint8_t** data, size_t* len);
Status ProcessMarker(const uint8_t* data, size_t len, size_t* pos);
Status ProcessSOF(const uint8_t* data, size_t len);
Status ProcessSOS(const uint8_t* data, size_t len);
Status ProcessDHT(const uint8_t* data, size_t len);
Status ProcessDQT(const uint8_t* data, size_t len);
Status ProcessDRI(const uint8_t* data, size_t len);
Status ProcessAPP(const uint8_t* data, size_t len);
Status ProcessCOM(const uint8_t* data, size_t len);
Status ProcessScan(const uint8_t* data, size_t len, size_t* pos);
void SaveMCUCodingState();
void RestoreMCUCodingState();
void PrepareForOutput();
void ProcessOutput(size_t* num_output_rows, size_t max_output_rows);
};
Status DecodeJpeg(const std::vector<uint8_t>& compressed,
JxlDataType output_data_type, ThreadPool* pool,
PackedPixelFile* ppf);

View File

@ -0,0 +1,190 @@
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "lib/extras/decode_jpeg.h"
#include <stddef.h>
#include <stdio.h>
#if JPEGXL_ENABLE_JPEG
#include "lib/extras/dec/jpg.h"
#endif
#include "lib/jxl/test_utils.h"
#include "lib/jxl/testdata.h"
namespace jxl {
namespace extras {
namespace {
using test::DistanceRMS;
struct TestConfig {
std::string fn;
std::string fn_desc;
size_t chunk_size;
size_t max_output_lines;
};
class DecodeJpegTestParam : public ::testing::TestWithParam<TestConfig> {};
TEST_P(DecodeJpegTestParam, Streaming) {
TestConfig config = GetParam();
const PaddedBytes compressed = ReadTestData(config.fn.c_str());
#if JPEGXL_ENABLE_JPEG
PackedPixelFile ppf_libjpeg;
EXPECT_TRUE(
DecodeImageJPG(Span<const uint8_t>(compressed.data(), compressed.size()),
ColorHints(), SizeConstraints(), &ppf_libjpeg));
ASSERT_EQ(1, ppf_libjpeg.frames.size());
#endif
JpegDecoder dec;
size_t chunk_size = config.chunk_size;
if (chunk_size == 0) chunk_size = compressed.size();
size_t pos = std::min(chunk_size, compressed.size());
ASSERT_EQ(JpegDecoder::Status::kSuccess,
dec.SetInput(compressed.data(), pos));
JpegDecoder::Status status;
for (;;) {
status = dec.ReadHeaders();
if (status == JpegDecoder::Status::kNeedMoreInput) {
ASSERT_LT(pos, compressed.size());
size_t len = std::min(chunk_size, compressed.size() - pos);
ASSERT_EQ(JpegDecoder::Status::kSuccess,
dec.SetInput(compressed.data() + pos, len));
pos += len;
continue;
}
ASSERT_EQ(status, JpegDecoder::Status::kSuccess);
break;
}
#if JPEGXL_ENABLE_JPEG
EXPECT_EQ(ppf_libjpeg.info.xsize, dec.xsize());
EXPECT_EQ(ppf_libjpeg.info.ysize, dec.ysize());
EXPECT_EQ(ppf_libjpeg.info.num_color_channels, dec.num_channels());
#endif
JxlPixelFormat format = {static_cast<uint32_t>(dec.num_channels()),
JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0};
PackedImage output(dec.xsize(), dec.ysize(), format);
ASSERT_EQ(JpegDecoder::Status::kSuccess, dec.SetOutput(&output));
size_t max_output_lines = config.max_output_lines;
if (max_output_lines == 0) max_output_lines = dec.ysize();
size_t total_output_lines = 0;
while (total_output_lines < dec.ysize()) {
size_t num_output_lines = 0;
status = dec.ReadScanLines(&num_output_lines, max_output_lines);
total_output_lines += num_output_lines;
if (status == JpegDecoder::Status::kNeedMoreInput) {
ASSERT_LT(pos, compressed.size());
size_t len = std::min(chunk_size, compressed.size() - pos);
ASSERT_EQ(JpegDecoder::Status::kSuccess,
dec.SetInput(compressed.data() + pos, len));
pos += len;
continue;
}
ASSERT_EQ(status, JpegDecoder::Status::kSuccess);
if (total_output_lines < dec.ysize()) {
EXPECT_EQ(num_output_lines, max_output_lines);
}
}
#if JPEGXL_ENABLE_JPEG
const PackedImage& output_libjpeg = ppf_libjpeg.frames[0].color;
ASSERT_EQ(output.xsize, output_libjpeg.xsize);
ASSERT_EQ(output.ysize, output_libjpeg.ysize);
EXPECT_LE(
DistanceRMS(reinterpret_cast<const uint8_t*>(output.pixels()),
reinterpret_cast<const uint8_t*>(output_libjpeg.pixels()),
output.xsize, output.ysize, output.format),
0.0075);
#endif
}
std::vector<TestConfig> GenerateTests() {
std::vector<TestConfig> all_tests;
{
std::vector<std::pair<std::string, std::string>> testfiles({
{"jxl/flower/flower.png.im_q85_444.jpg", "Q85YUV444"},
{"jxl/flower/flower.png.im_q85_420.jpg", "Q85YUV420"},
{"jxl/flower/flower.png.im_q85_420_progr.jpg", "Q85YUV420PROGR"},
{"jxl/flower/flower.png.im_q85_420_R13B.jpg", "Q85YUV420R13B"},
});
for (const auto& it : testfiles) {
for (size_t chunk_size : {0, 1, 64, 65536}) {
for (size_t max_output_lines : {0, 1, 8, 16}) {
TestConfig config;
config.fn = it.first;
config.fn_desc = it.second;
config.chunk_size = chunk_size;
config.max_output_lines = max_output_lines;
all_tests.push_back(config);
}
}
}
}
{
std::vector<std::pair<std::string, std::string>> testfiles({
{"jxl/flower/flower.png.im_q85_422.jpg", "Q85YUV422"},
{"jxl/flower/flower.png.im_q85_440.jpg", "Q85YUV440"},
{"jxl/flower/flower.png.im_q85_444_1x2.jpg", "Q85YUV444_1x2"},
{"jxl/flower/flower.png.im_q85_asymmetric.jpg", "Q85Asymmetric"},
{"jxl/flower/flower.png.im_q85_gray.jpg", "Q85Gray"},
{"jxl/flower/flower.png.im_q85_luma_subsample.jpg", "Q85LumaSubsample"},
{"jxl/flower/flower.png.im_q85_rgb.jpg", "Q85RGB"},
{"jxl/flower/flower.png.im_q85_rgb_subsample_blue.jpg",
"Q85RGBSubsampleBlue"},
});
for (const auto& it : testfiles) {
for (size_t chunk_size : {0, 64}) {
for (size_t max_output_lines : {0, 16}) {
TestConfig config;
config.fn = it.first;
config.fn_desc = it.second;
config.chunk_size = chunk_size;
config.max_output_lines = max_output_lines;
all_tests.push_back(config);
}
}
}
}
return all_tests;
}
std::ostream& operator<<(std::ostream& os, const TestConfig& c) {
os << c.fn_desc;
if (c.chunk_size == 0) {
os << "CompleteInput";
} else {
os << "InputChunks" << c.chunk_size;
}
if (c.max_output_lines == 0) {
os << "CompleteOutput";
} else {
os << "OutputLines" << c.max_output_lines;
}
return os;
}
std::string TestDescription(
const testing::TestParamInfo<DecodeJpegTestParam::ParamType>& info) {
std::stringstream name;
name << info.param;
return name.str();
}
JXL_GTEST_INSTANTIATE_TEST_SUITE_P(DecodeJpegTest, DecodeJpegTestParam,
testing::ValuesIn(GenerateTests()),
TestDescription);
} // namespace
} // namespace extras
} // namespace jxl

View File

@ -15,7 +15,6 @@
#include "lib/jxl/enc_adaptive_quantization.h"
#include "lib/jxl/enc_cluster.h"
#include "lib/jxl/enc_color_management.h"
#include "lib/jxl/enc_quant_weights.h"
#include "lib/jxl/enc_transforms.h"
#include "lib/jxl/enc_xyb.h"
#include "lib/jxl/huffman_tree.h"
@ -117,36 +116,218 @@ std::vector<uint8_t> CreateXybICCAppMarker() {
return icc_marker;
}
static constexpr float kBaseQuantMatrix[] = {
// c = 0
0.010745695802f,
0.014724285860f,
0.016765073259f,
0.015352546818f,
0.016849715608f,
0.017505664513f,
0.019171796023f,
0.026983627671f,
0.014724285860f,
0.016005879113f,
0.014807802023f,
0.015257294568f,
0.016239266522f,
0.017754112611f,
0.021007430943f,
0.024258001854f,
0.016765073259f,
0.014807802023f,
0.016266879484f,
0.014202573480f,
0.016155362246f,
0.018324768181f,
0.018883664957f,
0.024261275157f,
0.015352546818f,
0.015257294568f,
0.014202573480f,
0.014974020066f,
0.018844302744f,
0.019286162437f,
0.023009874591f,
0.023277331489f,
0.016849715608f,
0.016239266522f,
0.016155362246f,
0.018844302744f,
0.019491371738f,
0.030153905190f,
0.032131952026f,
0.047015070993f,
0.017505664513f,
0.017754112611f,
0.018324768181f,
0.019286162437f,
0.030153905190f,
0.035875428738f,
0.025324149774f,
0.046037739693f,
0.019171796023f,
0.021007430943f,
0.018883664957f,
0.023009874591f,
0.032131952026f,
0.025324149774f,
0.025619236945f,
0.049740249957f,
0.026983627671f,
0.024258001854f,
0.024261275157f,
0.023277331489f,
0.047015070993f,
0.046037739693f,
0.049740249957f,
0.029683058303f,
// c = 1
0.002310547025f,
0.002391506241f,
0.002592377991f,
0.002907631930f,
0.003590107614f,
0.003647418579f,
0.003946607583f,
0.004580867024f,
0.002391506241f,
0.002565945978f,
0.002676532241f,
0.003167916799f,
0.003592423110f,
0.003581864537f,
0.004168190188f,
0.004711190832f,
0.002592377991f,
0.002676532241f,
0.002899922325f,
0.003221508920f,
0.003597377805f,
0.004015001363f,
0.004164168214f,
0.004536180462f,
0.002907631930f,
0.003167916799f,
0.003221508920f,
0.003421333194f,
0.003843692347f,
0.004011729362f,
0.004486022354f,
0.005037524314f,
0.003590107614f,
0.003592423110f,
0.003597377805f,
0.003843692347f,
0.003991982168f,
0.004561113887f,
0.005683994831f,
0.005587879717f,
0.003647418579f,
0.003581864537f,
0.004015001363f,
0.004011729362f,
0.004561113887f,
0.004711190832f,
0.005279489671f,
0.005645298559f,
0.003946607583f,
0.004168190188f,
0.004164168214f,
0.004486022354f,
0.005683994831f,
0.005279489671f,
0.005269099460f,
0.005234577959f,
0.004580867024f,
0.004711190832f,
0.004536180462f,
0.005037524314f,
0.005587879717f,
0.005645298559f,
0.005234577959f,
0.005138602544f,
// c = 2
0.004694191704f,
0.007478405841f,
0.009119519544f,
0.010846788859f,
0.012040055008f,
0.014283609506f,
0.020805819128f,
0.041346026531f,
0.007478405841f,
0.008473337032f,
0.008457467755f,
0.011507290737f,
0.012282006381f,
0.011077942494f,
0.019589180487f,
0.030348661601f,
0.009119519544f,
0.008457467755f,
0.012692131754f,
0.010360988009f,
0.011883779193f,
0.021216622915f,
0.019468523508f,
0.022375231013f,
0.010846788859f,
0.011507290737f,
0.010360988009f,
0.015688875916f,
0.019428087454f,
0.018982414995f,
0.030218311113f,
0.025108166811f,
0.012040055008f,
0.012282006381f,
0.011883779193f,
0.019428087454f,
0.019908111501f,
0.019428676375f,
0.026540699320f,
0.032446303017f,
0.014283609506f,
0.011077942494f,
0.021216622915f,
0.018982414995f,
0.019428676375f,
0.025654942665f,
0.030689090332f,
0.036234971093f,
0.020805819128f,
0.019589180487f,
0.019468523508f,
0.030218311113f,
0.026540699320f,
0.030689090332f,
0.035378000966f,
0.041109510150f,
0.041346026531f,
0.030348661601f,
0.022375231013f,
0.025108166811f,
0.032446303017f,
0.036234971093f,
0.041109510150f,
0.047241950370f,
};
void AddJpegQuantMatrices(const ImageF& qf, float dc_quant, float global_scale,
std::vector<jpeg::JPEGQuantTable>* quant_tables,
float* qm) {
// Create a custom JPEG XL dequant matrix. The quantization weight parameters
// were determined with manual tweaking.
DequantMatrices dequant;
std::vector<QuantEncoding> encodings(DequantMatrices::kNum,
QuantEncoding::Library(0));
encodings[0] = QuantEncoding::DCT(
DctQuantWeightParams({{{{2000.0, -0.5, -0.5, -0.5, -0.5, -0.5}},
{{500.0, -0.1, -0.1, -0.2, -0.2, -0.2}},
{{200.0, -1.0, -0.5, -0.5, -0.5, -0.5}}}},
6));
dequant.SetEncodings(encodings);
JXL_CHECK(dequant.EnsureComputed(1));
memcpy(qm, dequant.Matrix(0, 0), 3 * kDCTBlockSize * sizeof(qm[0]));
// Set custom DC quant weights.
const float inv_dc_quant[3] = {3200.0f, 512.0f, 320.0f};
for (size_t c = 0; c < 3; c++) {
qm[c * kDCTBlockSize] = 1.0f / (inv_dc_quant[c] * dc_quant);
}
// Scale the quant matrix based on the scaled XYB scales and the quant field.
// Scale the base quant matrix based on the scaled XYB scales and the quant
// field.
float qfmin, qfmax;
ImageMinMax(qf, &qfmin, &qfmax);
for (size_t c = 0; c < 3; c++) {
const float scale = kScaledXYBScale[c] * global_scale;
qm[c * kDCTBlockSize] *= scale;
for (size_t j = 1; j < kDCTBlockSize; j++) {
qm[c * kDCTBlockSize + j] *= scale / qfmax;
const float dc_scale = global_scale / dc_quant;
const float ac_scale = global_scale / qfmax;
for (size_t c = 0, ix = 0; c < 3; c++) {
qm[ix] = dc_scale * kBaseQuantMatrix[ix];
ix++;
for (size_t j = 1; j < kDCTBlockSize; j++, ix++) {
qm[ix] = ac_scale * kBaseQuantMatrix[ix];
}
}
@ -193,9 +374,34 @@ void AddJpegScanInfos(const std::vector<ProgressiveScan>& scans,
}
}
float HistogramCost(const Histogram& histo) {
std::vector<uint32_t> counts(jpeg::kJpegHuffmanAlphabetSize + 1);
std::vector<uint8_t> depths(jpeg::kJpegHuffmanAlphabetSize + 1);
for (size_t i = 0; i < jpeg::kJpegHuffmanAlphabetSize; ++i) {
counts[i] = histo.data_[i];
}
counts[jpeg::kJpegHuffmanAlphabetSize] = 1;
CreateHuffmanTree(counts.data(), counts.size(),
jpeg::kJpegHuffmanMaxBitLength, &depths[0]);
size_t header_bits = (1 + jpeg::kJpegHuffmanMaxBitLength) * 8;
size_t data_bits = 0;
for (size_t i = 0; i < jpeg::kJpegHuffmanAlphabetSize; ++i) {
if (depths[i] > 0) {
header_bits += 8;
data_bits += counts[i] * depths[i];
}
}
return header_bits + data_bits;
}
struct JpegClusteredHistograms {
std::vector<Histogram> histograms;
std::vector<uint32_t> histogram_indexes;
std::vector<uint32_t> slot_ids;
};
void ClusterJpegHistograms(const std::vector<jpeg::HuffmanCodeTable>& jpeg_in,
size_t max_histograms, std::vector<Histogram>* out,
std::vector<uint32_t>* histogram_indexes) {
JpegClusteredHistograms* clusters) {
std::vector<Histogram> histograms;
for (const auto& t : jpeg_in) {
Histogram histo;
@ -206,17 +412,70 @@ void ClusterJpegHistograms(const std::vector<jpeg::HuffmanCodeTable>& jpeg_in,
}
histograms.push_back(histo);
}
// TODO(szabadka): Use a JPEG-specific version of the clustering algorithm.
HistogramParams params;
ClusterHistograms(params, histograms, max_histograms, out, histogram_indexes);
clusters->histogram_indexes.resize(histograms.size());
std::vector<uint32_t> slot_histograms;
std::vector<float> slot_costs;
for (size_t i = 0; i < histograms.size(); ++i) {
const Histogram& cur = histograms[i];
if (cur.total_count_ == 0) {
continue;
}
float best_cost = HistogramCost(cur);
size_t best_slot = slot_histograms.size();
for (size_t j = 0; j < slot_histograms.size(); ++j) {
size_t prev_idx = slot_histograms[j];
const Histogram& prev = clusters->histograms[prev_idx];
Histogram combined;
combined.AddHistogram(prev);
combined.AddHistogram(cur);
float combined_cost = HistogramCost(combined);
float cost = combined_cost - slot_costs[j];
if (cost < best_cost) {
best_cost = cost;
best_slot = j;
}
}
if (best_slot == slot_histograms.size()) {
// Create new histogram.
size_t histogram_index = clusters->histograms.size();
clusters->histograms.push_back(cur);
clusters->histogram_indexes[i] = histogram_index;
if (best_slot < 4) {
// We have a free slot, so we put the new histogram there.
slot_histograms.push_back(histogram_index);
slot_costs.push_back(best_cost);
} else {
// TODO(szabadka) Find the best histogram to replce.
best_slot = (clusters->slot_ids.back() + 1) % 4;
}
slot_histograms[best_slot] = histogram_index;
slot_costs[best_slot] = best_cost;
clusters->slot_ids.push_back(best_slot);
} else {
// Merge this histogram with a previous one.
size_t histogram_index = slot_histograms[best_slot];
clusters->histograms[histogram_index].AddHistogram(cur);
clusters->histogram_indexes[i] = histogram_index;
JXL_ASSERT(clusters->slot_ids[histogram_index] == best_slot);
slot_costs[best_slot] += best_cost;
}
}
}
void BuildJpegHuffmanCode(const uint8_t* depth, jpeg::JPEGHuffmanCode* huff) {
void BuildJpegHuffmanCode(const Histogram& histo, jpeg::JPEGHuffmanCode* huff) {
std::vector<uint32_t> counts(jpeg::kJpegHuffmanAlphabetSize + 1);
std::vector<uint8_t> depths(jpeg::kJpegHuffmanAlphabetSize + 1);
for (size_t j = 0; j < jpeg::kJpegHuffmanAlphabetSize; ++j) {
counts[j] = histo.data_[j];
}
counts[jpeg::kJpegHuffmanAlphabetSize] = 1;
CreateHuffmanTree(counts.data(), counts.size(),
jpeg::kJpegHuffmanMaxBitLength, &depths[0]);
std::fill(std::begin(huff->counts), std::end(huff->counts), 0);
std::fill(std::begin(huff->values), std::end(huff->values), 0);
for (size_t i = 0; i <= jpeg::kJpegHuffmanAlphabetSize; ++i) {
if (depth[i] > 0) {
++huff->counts[depth[i]];
if (depths[i] > 0) {
++huff->counts[depths[i]];
}
}
int offset[jpeg::kJpegHuffmanMaxBitLength + 1] = {0};
@ -224,29 +483,33 @@ void BuildJpegHuffmanCode(const uint8_t* depth, jpeg::JPEGHuffmanCode* huff) {
offset[i] = offset[i - 1] + huff->counts[i - 1];
}
for (size_t i = 0; i <= jpeg::kJpegHuffmanAlphabetSize; ++i) {
if (depth[i] > 0) {
huff->values[offset[depth[i]]++] = i;
if (depths[i] > 0) {
huff->values[offset[depths[i]]++] = i;
}
}
huff->is_last = false;
}
void AddJpegHuffmanCodes(std::vector<Histogram>& histograms,
size_t slot_id_offset,
std::vector<jpeg::JPEGHuffmanCode>* huff_codes) {
for (size_t i = 0; i < histograms.size(); ++i) {
jpeg::JPEGHuffmanCode huff_code;
huff_code.slot_id = slot_id_offset + i;
std::vector<uint32_t> counts(jpeg::kJpegHuffmanAlphabetSize + 1);
std::vector<uint8_t> depths(jpeg::kJpegHuffmanAlphabetSize + 1);
for (size_t j = 0; j < jpeg::kJpegHuffmanAlphabetSize; ++j) {
counts[j] = histograms[i].data_[j];
}
counts[jpeg::kJpegHuffmanAlphabetSize] = 1;
CreateHuffmanTree(counts.data(), counts.size(),
jpeg::kJpegHuffmanMaxBitLength, &depths[0]);
BuildJpegHuffmanCode(&depths[0], &huff_code);
huff_codes->emplace_back(std::move(huff_code));
void AddJpegHuffmanCode(const Histogram& histogram, size_t slot_id,
std::vector<jpeg::JPEGHuffmanCode>* huff_codes) {
jpeg::JPEGHuffmanCode huff_code;
huff_code.slot_id = slot_id;
BuildJpegHuffmanCode(histogram, &huff_code);
huff_codes->emplace_back(std::move(huff_code));
}
void SetJpegHuffmanCode(const JpegClusteredHistograms& clusters,
size_t histogram_id, size_t slot_id_offset,
std::vector<uint32_t>& slot_histograms,
uint32_t* slot_id,
std::vector<jpeg::JPEGHuffmanCode>* huff_codes) {
JXL_ASSERT(histogram_id < clusters.histogram_indexes.size());
uint32_t histogram_index = clusters.histogram_indexes[histogram_id];
*slot_id = clusters.slot_ids[histogram_index];
if (slot_histograms[*slot_id] != histogram_index) {
AddJpegHuffmanCode(clusters.histograms[histogram_index],
slot_id_offset + *slot_id, huff_codes);
slot_histograms[*slot_id] = histogram_index;
}
}
@ -291,11 +554,8 @@ void FillJPEGData(const Image3F& opsin, const ImageF& qf, float dc_quant,
// SOS
std::vector<ProgressiveScan> progressive_mode = {
// DC
{0, 0, 0, 0, !subsample_blue},
// AC 1 - highest bits
{1, 63, 0, 1, false},
// AC 2 - lowest bit
{1, 63, 1, 0, false},
{0, 0, 0, 0, !subsample_blue}, {1, 2, 0, 0, false}, {3, 63, 0, 2, false},
{3, 63, 2, 1, false}, {3, 63, 1, 0, false},
};
AddJpegScanInfos(progressive_mode, &out->scan_info);
for (size_t i = 0; i < out->scan_info.size(); i++) {
@ -309,30 +569,58 @@ void FillJPEGData(const Image3F& opsin, const ImageF& qf, float dc_quant,
jpeg::SerializationState ss;
JXL_CHECK(jpeg::ProcessJpeg(*out, &ss));
// Build DC Huffman codes and add them to DHT segment.
std::vector<Histogram> dc_histo;
std::vector<uint32_t> dc_tbl_indexes;
ClusterJpegHistograms(ss.dc_huff_table, 4, &dc_histo, &dc_tbl_indexes);
AddJpegHuffmanCodes(dc_histo, 0, &out->huffman_code);
// Cluster DC histograms.
JpegClusteredHistograms dc_clusters;
ClusterJpegHistograms(ss.dc_huff_table, &dc_clusters);
// Build AC Huffman codes and add them to DHT segment.
std::vector<Histogram> ac_histo;
std::vector<uint32_t> ac_tbl_indexes;
ClusterJpegHistograms(ss.ac_huff_table, 4, &ac_histo, &ac_tbl_indexes);
AddJpegHuffmanCodes(ac_histo, 0x10, &out->huffman_code);
// Cluster AC histograms.
JpegClusteredHistograms ac_clusters;
ClusterJpegHistograms(ss.ac_huff_table, &ac_clusters);
// Rebuild marker_order because we may want to emit Huffman codes in between
// scans.
out->marker_order.resize(out->marker_order.size() - 2 -
out->scan_info.size());
// Add the first 4 DC and AC histograms in the first DHT segment.
out->marker_order.emplace_back(0xc4); // DHT
std::vector<uint32_t> dc_slot_histograms;
std::vector<uint32_t> ac_slot_histograms;
for (size_t i = 0; i < dc_clusters.histograms.size(); ++i) {
if (i >= 4) break;
JXL_ASSERT(dc_clusters.slot_ids[i] == i);
AddJpegHuffmanCode(dc_clusters.histograms[i], i, &out->huffman_code);
dc_slot_histograms.push_back(i);
}
for (size_t i = 0; i < ac_clusters.histograms.size(); ++i) {
if (i >= 4) break;
JXL_ASSERT(ac_clusters.slot_ids[i] == i);
AddJpegHuffmanCode(ac_clusters.histograms[i], 0x10 + i, &out->huffman_code);
ac_slot_histograms.push_back(i);
}
out->huffman_code.back().is_last = true;
// Set the Huffman table indexes in the scan_infos.
size_t histo_idx = 0;
// Set the Huffman table indexes in the scan_infos and emit additional DHT
// segments if necessary.
size_t histogram_id = 0;
for (auto& si : out->scan_info) {
size_t num_huff_codes = out->huffman_code.size();
for (size_t c = 0; c < si.num_components; ++c) {
JXL_ASSERT(histo_idx < dc_tbl_indexes.size());
JXL_ASSERT(histo_idx < ac_tbl_indexes.size());
si.components[c].dc_tbl_idx = dc_tbl_indexes[histo_idx];
si.components[c].ac_tbl_idx = ac_tbl_indexes[histo_idx];
++histo_idx;
SetJpegHuffmanCode(dc_clusters, histogram_id, 0, dc_slot_histograms,
&si.components[c].dc_tbl_idx, &out->huffman_code);
SetJpegHuffmanCode(ac_clusters, histogram_id, 0x10, ac_slot_histograms,
&si.components[c].ac_tbl_idx, &out->huffman_code);
++histogram_id;
}
// If we updated a slot histogram in this scan, create an additional DHT
// segment.
if (out->huffman_code.size() > num_huff_codes) {
out->marker_order.emplace_back(0xc4); // DHT
out->huffman_code.back().is_last = true;
}
out->marker_order.emplace_back(0xda); // SOS
}
out->marker_order.emplace_back(0xd9); // EOI
}
size_t JpegSize(const jpeg::JPEGData& jpeg_data) {

View File

@ -71,15 +71,6 @@ typedef struct {
uint32_t ysize;
} JxlPreviewHeader;
/** The intrinsic size header */
typedef struct {
/** Intrinsic width in pixels */
uint32_t xsize;
/** Intrinsic height in pixels */
uint32_t ysize;
} JxlIntrinsicSizeHeader;
/** The codestream animation header, optionally present in the beginning of
* the codestream, and if it is it applies to all animation frames, unlike
* JxlFrameHeader which applies to an individual frame.

View File

@ -81,7 +81,6 @@ typedef enum {
* for pixels. This is not necessarily the same as the data type encoded in the
* codestream. The channels are interleaved per pixel. The pixels are
* organized row by row, left to right, top to bottom.
* TODO(lode): implement padding / alignment (row stride)
* TODO(lode): support different channel orders if needed (RGB, BGR, ...)
*/
typedef struct {

View File

@ -46,8 +46,6 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/base/thread_pool_internal.h
jxl/blending.cc
jxl/blending.h
jxl/box_content_decoder.cc
jxl/box_content_decoder.h
jxl/chroma_from_luma.cc
jxl/chroma_from_luma.h
jxl/codec_in_out.h
@ -102,8 +100,6 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/dec_xyb.cc
jxl/dec_xyb.h
jxl/decode.cc
jxl/decode_to_jpeg.cc
jxl/decode_to_jpeg.h
jxl/enc_bit_writer.cc
jxl/enc_bit_writer.h
jxl/entropy_coder.cc
@ -143,14 +139,6 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/image_metadata.cc
jxl/image_metadata.h
jxl/image_ops.h
jxl/jpeg/dec_jpeg_data.cc
jxl/jpeg/dec_jpeg_data.h
jxl/jpeg/dec_jpeg_data_writer.cc
jxl/jpeg/dec_jpeg_data_writer.h
jxl/jpeg/dec_jpeg_output_chunk.h
jxl/jpeg/dec_jpeg_serialization_state.h
jxl/jpeg/jpeg_data.cc
jxl/jpeg/jpeg_data.h
jxl/jxl_inspection.h
jxl/lehmer_code.h
jxl/linalg.h
@ -238,6 +226,30 @@ set(JPEGXL_INTERNAL_SOURCES_DEC
jxl/xorshift128plus-inl.h
)
if (JPEGXL_ENABLE_TRANSCODE_JPEG OR JPEGXL_ENABLE_TOOLS OR JPEGXL_ENABLE_DEVTOOLS OR JPEGXL_ENABLE_BOXES)
list(APPEND JPEGXL_INTERNAL_SOURCES_DEC
jxl/decode_to_jpeg.cc
jxl/decode_to_jpeg.h
jxl/box_content_decoder.cc
jxl/box_content_decoder.h
)
endif()
if (JPEGXL_ENABLE_TRANSCODE_JPEG OR JPEGXL_ENABLE_TOOLS OR JPEGXL_ENABLE_DEVTOOLS)
list(APPEND JPEGXL_INTERNAL_SOURCES_DEC
jxl/decode_to_jpeg.cc
jxl/decode_to_jpeg.h
jxl/jpeg/dec_jpeg_data.cc
jxl/jpeg/dec_jpeg_data.h
jxl/jpeg/dec_jpeg_data_writer.cc
jxl/jpeg/dec_jpeg_data_writer.h
jxl/jpeg/dec_jpeg_output_chunk.h
jxl/jpeg/dec_jpeg_serialization_state.h
jxl/jpeg/jpeg_data.cc
jxl/jpeg/jpeg_data.h
)
endif()
# List of source files only needed by the encoder or by tools (including
# decoding tools), but not by the decoder library.
set(JPEGXL_INTERNAL_SOURCES_ENC
@ -349,13 +361,15 @@ set(JPEGXL_INTERNAL_SOURCES_ENC
)
set(JPEGXL_DEC_INTERNAL_LIBS
brotlidec-static
brotlicommon-static
hwy
Threads::Threads
${ATOMICS_LIBRARIES}
)
if (JPEGXL_ENABLE_TRANSCODE_JPEG OR JPEGXL_ENABLE_BOXES)
list(APPEND JPEGXL_DEC_INTERNAL_LIBS brotlidec-static brotlicommon-static)
endif()
if(JPEGXL_ENABLE_PROFILER)
list(APPEND JPEGXL_DEC_INTERNAL_LIBS jxl_profiler)
endif()
@ -386,10 +400,18 @@ else ()
list(APPEND JPEGXL_INTERNAL_LIBS lcms2)
endif ()
if (NOT JPEGXL_ENABLE_TRANSCODE_JPEG)
if (JPEGXL_ENABLE_TRANSCODE_JPEG)
list(APPEND JPEGXL_INTERNAL_FLAGS -DJPEGXL_ENABLE_TRANSCODE_JPEG=1)
else()
list(APPEND JPEGXL_INTERNAL_FLAGS -DJPEGXL_ENABLE_TRANSCODE_JPEG=0)
endif ()
if (JPEGXL_ENABLE_BOXES)
list(APPEND JPEGXL_INTERNAL_FLAGS -DJPEGXL_ENABLE_BOXES=1)
else()
list(APPEND JPEGXL_INTERNAL_FLAGS -DJPEGXL_ENABLE_BOXES=0)
endif ()
set(OBJ_COMPILE_DEFINITIONS
JPEGXL_MAJOR_VERSION=${JPEGXL_MAJOR_VERSION}
JPEGXL_MINOR_VERSION=${JPEGXL_MINOR_VERSION}

View File

@ -28,6 +28,11 @@
#define JPEGXL_ENABLE_TRANSCODE_JPEG 1
#endif // JPEGXL_ENABLE_TRANSCODE_JPEG
// Macro that defines whether support for decoding boxes is enabled.
#ifndef JPEGXL_ENABLE_BOXES
#define JPEGXL_ENABLE_BOXES 1
#endif // JPEGXL_ENABLE_BOXES
namespace jxl {
// Some enums and typedefs used by more than one header file.

View File

@ -9,11 +9,15 @@
#include "lib/jxl/base/byte_order.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#if JPEGXL_ENABLE_BOXES || JPEGXL_ENABLE_TRANSCODE_JPEG
#include "lib/jxl/box_content_decoder.h"
#endif
#include "lib/jxl/dec_external_image.h"
#include "lib/jxl/dec_frame.h"
#include "lib/jxl/dec_modular.h"
#if JPEGXL_ENABLE_TRANSCODE_JPEG
#include "lib/jxl/decode_to_jpeg.h"
#endif
#include "lib/jxl/fields.h"
#include "lib/jxl/frame_header.h"
#include "lib/jxl/headers.h"
@ -507,8 +511,11 @@ struct JxlDecoderStruct {
BoxStage box_stage;
jxl::JxlToJpegDecoder jpeg_decoder;
#if JPEGXL_ENABLE_BOXES
jxl::JxlBoxContentDecoder box_content_decoder;
#endif
#if JPEGXL_ENABLE_TRANSCODE_JPEG
jxl::JxlToJpegDecoder jpeg_decoder;
// Decodes Exif or XMP metadata for JPEG reconstruction
jxl::JxlBoxContentDecoder metadata_decoder;
std::vector<uint8_t> exif_metadata;
@ -529,6 +536,7 @@ struct JxlDecoderStruct {
if (store_xmp < 2 && recon_xmp_size > 0) return true;
return false;
}
#endif
// Statistics which CodecInOut can keep
uint64_t dec_pixels;
@ -695,6 +703,8 @@ void JxlDecoderRewindDecodingState(JxlDecoder* dec) {
dec->box_out_buffer_size = 0;
dec->box_out_buffer_begin = 0;
dec->box_out_buffer_pos = 0;
#if JPEGXL_ENABLE_TRANSCODE_JPEG
dec->exif_metadata.clear();
dec->xmp_metadata.clear();
dec->store_exif = 0;
@ -703,6 +713,7 @@ void JxlDecoderRewindDecodingState(JxlDecoder* dec) {
dec->recon_exif_size = 0;
dec->recon_xmp_size = 0;
dec->recon_output_jpeg = JpegReconStage::kNone;
#endif
dec->events_wanted = 0;
dec->basic_info_size_hint = InitialBasicInfoSizeHint();
@ -1210,6 +1221,7 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec) {
break;
}
if (dec->frame_stage == FrameStage::kHeader) {
#if JPEGXL_ENABLE_TRANSCODE_JPEG
if (dec->recon_output_jpeg == JpegReconStage::kSettingMetadata ||
dec->recon_output_jpeg == JpegReconStage::kOutputting) {
// The image bundle contains the JPEG reconstruction frame, but the
@ -1219,14 +1231,16 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec) {
return JXL_API_ERROR(
"cannot decode a next frame after JPEG reconstruction frame");
}
#endif
if (!dec->ib) {
dec->ib.reset(new jxl::ImageBundle(&dec->image_metadata));
}
#if JPEGXL_ENABLE_TRANSCODE_JPEG
// If JPEG reconstruction is wanted and possible, set the jpeg_data of
// the ImageBundle.
if (!dec->jpeg_decoder.SetImageBundleJpegData(dec->ib.get()))
return JXL_DEC_ERROR;
#endif
dec->frame_dec.reset(new FrameDecoder(
dec->passes_state.get(), dec->metadata, dec->thread_pool.get(),
/*use_slow_rendering_pipeline=*/false));
@ -1382,8 +1396,11 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec) {
if (dec->preview_frame) {
return JXL_DEC_NEED_PREVIEW_OUT_BUFFER;
}
if ((!dec->jpeg_decoder.IsOutputSet() ||
if (
#if JPEGXL_ENABLE_TRANSCODE_JPEG
(!dec->jpeg_decoder.IsOutputSet() ||
dec->ib->jpeg_data == nullptr) &&
#endif
dec->is_last_of_still && !dec->skipping_frame) {
// TODO(lode): remove the dec->is_last_of_still condition if the
// frame decoder needs the image buffer as working space for decoding
@ -1458,7 +1475,7 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec) {
if (!dec->frame_dec->FinalizeFrame()) {
return JXL_API_ERROR("decoding frame failed");
}
#if JPEGXL_ENABLE_TRANSCODE_JPEG
// If jpeg output was requested, we merely return the JXL_DEC_FULL_IMAGE
// status without outputting pixels.
if (dec->jpeg_decoder.IsOutputSet() && dec->ib->jpeg_data != nullptr) {
@ -1466,7 +1483,7 @@ JxlDecoderStatus JxlDecoderProcessCodestream(JxlDecoder* dec) {
dec->recon_output_jpeg = JpegReconStage::kSettingMetadata;
return JXL_DEC_FULL_IMAGE;
}
#endif
if (dec->preview_frame || dec->is_last_of_still) {
dec->image_out_buffer_set = false;
dec->extra_channel_output.clear();
@ -1523,6 +1540,7 @@ void JxlDecoderCloseInput(JxlDecoder* dec) { dec->input_closed = true; }
JxlDecoderStatus JxlDecoderSetJPEGBuffer(JxlDecoder* dec, uint8_t* data,
size_t size) {
#if JPEGXL_ENABLE_TRANSCODE_JPEG
// JPEG reconstruction buffer can only set and updated before or during the
// first frame, the reconstruction box refers to the first frame and in
// theory multi-frame images should not be used with a jbrd box.
@ -1533,10 +1551,17 @@ JxlDecoderStatus JxlDecoderSetJPEGBuffer(JxlDecoder* dec, uint8_t* data,
return JXL_API_ERROR("Already set JPEG buffer");
}
return dec->jpeg_decoder.SetOutputBuffer(data, size);
#else
return JXL_API_ERROR("JPEG reconstruction is not supported.");
#endif
}
size_t JxlDecoderReleaseJPEGBuffer(JxlDecoder* dec) {
#if JPEGXL_ENABLE_TRANSCODE_JPEG
return dec->jpeg_decoder.ReleaseOutputBuffer();
#else
return JXL_API_ERROR("JPEG reconstruction is not supported.");
#endif
}
// Parses the header of the box, outputting the 4-character type and the box
@ -1592,6 +1617,7 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
if (dec->box_stage != BoxStage::kHeader) {
dec->AdvanceInput(dec->header_size);
dec->header_size = 0;
#if JPEGXL_ENABLE_BOXES
if ((dec->events_wanted & JXL_DEC_BOX) &&
dec->box_out_buffer_set_current_box) {
uint8_t* next_out = dec->box_out_buffer + dec->box_out_buffer_pos;
@ -1612,7 +1638,8 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
return box_result;
}
}
#endif
#if JPEGXL_ENABLE_TRANSCODE_JPEG
if (dec->store_exif == 1 || dec->store_xmp == 1) {
std::vector<uint8_t>& metadata =
(dec->store_exif == 1) ? dec->exif_metadata : dec->xmp_metadata;
@ -1650,8 +1677,9 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
}
}
}
#endif
}
#if JPEGXL_ENABLE_TRANSCODE_JPEG
if (dec->recon_output_jpeg == JpegReconStage::kSettingMetadata &&
!dec->JbrdNeedMoreBoxes()) {
jxl::jpeg::JPEGData* jpeg_data = dec->ib->jpeg_data.get();
@ -1682,6 +1710,7 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
return JXL_DEC_FULL_IMAGE;
}
}
#endif
if (dec->box_stage == BoxStage::kHeader) {
if (!dec->have_container) {
@ -1696,9 +1725,11 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
// Not yet seen (all) codestream boxes.
return JXL_DEC_NEED_MORE_INPUT;
}
#if JPEGXL_ENABLE_TRANSCODE_JPEG
if (dec->JbrdNeedMoreBoxes()) {
return JXL_DEC_NEED_MORE_INPUT;
}
#endif
if (dec->input_closed) {
return JXL_DEC_SUCCESS;
}
@ -1719,7 +1750,10 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
bool boxed_codestream_done =
((dec->events_wanted & JXL_DEC_BOX) &&
dec->stage == DecoderStage::kCodestreamFinished &&
dec->last_codestream_seen && !dec->JbrdNeedMoreBoxes());
#if JPEGXL_ENABLE_TRANSCODE_JPEG
!dec->JbrdNeedMoreBoxes() &&
#endif
dec->last_codestream_seen);
if (boxed_codestream_done && dec->avail_in >= 2 &&
dec->next_in[0] == 0xff &&
dec->next_in[1] == jxl::kCodestreamMarker) {
@ -1774,7 +1808,7 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
dec->box_contents_unbounded ? 0 : (box_size - header_size);
dec->box_size = box_size;
dec->header_size = header_size;
#if JPEGXL_ENABLE_TRANSCODE_JPEG
if (dec->orig_events_wanted & JXL_DEC_JPEG_RECONSTRUCTION) {
// Initiate storing of Exif or XMP data for JPEG reconstruction
if (dec->store_exif == 0 &&
@ -1788,19 +1822,22 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
dec->recon_out_buffer_pos = 0;
}
}
#endif
#if JPEGXL_ENABLE_BOXES
if (dec->events_wanted & JXL_DEC_BOX) {
bool decompress =
dec->decompress_boxes && memcmp(dec->box_type, "brob", 4) == 0;
dec->box_content_decoder.StartBox(
decompress, dec->box_contents_unbounded, dec->box_contents_size);
}
#endif
#if JPEGXL_ENABLE_TRANSCODE_JPEG
if (dec->store_exif == 1 || dec->store_xmp == 1) {
bool brob = memcmp(dec->box_type, "brob", 4) == 0;
dec->metadata_decoder.StartBox(brob, dec->box_contents_unbounded,
dec->box_contents_size);
}
#endif
if (memcmp(dec->box_type, "ftyp", 4) == 0) {
dec->box_stage = BoxStage::kFtyp;
} else if (memcmp(dec->box_type, "jxlc", 4) == 0) {
@ -1811,6 +1848,7 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
dec->box_stage = BoxStage::kCodestream;
} else if (memcmp(dec->box_type, "jxlp", 4) == 0) {
dec->box_stage = BoxStage::kPartialCodestream;
#if JPEGXL_ENABLE_TRANSCODE_JPEG
} 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)) {
@ -1818,6 +1856,7 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
"multiple JPEG reconstruction boxes not supported");
}
dec->box_stage = BoxStage::kJpegRecon;
#endif
} else {
dec->box_stage = BoxStage::kSkip;
}
@ -1856,11 +1895,13 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
dec->box_stage = BoxStage::kCodestream;
} else if (dec->box_stage == BoxStage::kCodestream) {
JxlDecoderStatus status = jxl::JxlDecoderProcessCodestream(dec);
#if JPEGXL_ENABLE_TRANSCODE_JPEG
if (status == JXL_DEC_FULL_IMAGE) {
if (dec->recon_output_jpeg != JpegReconStage::kNone) {
continue;
}
}
#endif
if (status == JXL_DEC_NEED_MORE_INPUT) {
if (dec->file_pos == dec->box_contents_end &&
!dec->box_contents_unbounded) {
@ -1870,10 +1911,12 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
}
if (status == JXL_DEC_SUCCESS) {
#if JPEGXL_ENABLE_TRANSCODE_JPEG
if (dec->JbrdNeedMoreBoxes()) {
dec->box_stage = BoxStage::kSkip;
continue;
}
#endif
if (dec->box_contents_unbounded) {
// Last box reached and codestream done, nothing more to do.
break;
@ -1885,6 +1928,7 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
}
}
return status;
#if JPEGXL_ENABLE_TRANSCODE_JPEG
} else if (dec->box_stage == BoxStage::kJpegRecon) {
if (!dec->jpeg_decoder.IsParsingBox()) {
// This is a new JPEG reconstruction metadata box.
@ -1933,6 +1977,7 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
// If anything else, return the result.
return recon_result;
}
#endif
} else if (dec->box_stage == BoxStage::kSkip) {
if (dec->box_contents_unbounded) {
if (dec->input_closed) {
@ -1968,7 +2013,6 @@ static JxlDecoderStatus HandleBoxes(JxlDecoder* dec) {
JXL_DASSERT(false); // unknown box stage
}
}
return JXL_DEC_SUCCESS;
}
@ -2013,9 +2057,11 @@ JxlDecoderStatus JxlDecoderProcessInput(JxlDecoder* dec) {
if (dec->CanUseMoreCodestreamInput()) {
return JXL_API_ERROR("codestream never finished");
}
#if JPEGXL_ENABLE_TRANSCODE_JPEG
if (dec->JbrdNeedMoreBoxes()) {
return JXL_API_ERROR("missing metadata boxes for jpeg reconstruction");
}
#endif
}
return status;

View File

@ -4967,7 +4967,7 @@ TEST(DecodeTest, ExtentedBoxSizeTest) {
JxlDecoderDestroy(dec);
}
TEST(DecodeTest, BoxTest) {
TEST(DecodeTest, JXL_BOXES_TEST(BoxTest)) {
size_t xsize = 1, ysize = 1;
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
jxl::TestCodestreamParams params;
@ -5040,7 +5040,7 @@ TEST(DecodeTest, BoxTest) {
JxlDecoderDestroy(dec);
}
TEST(DecodeTest, ExifBrobBoxTest) {
TEST(DecodeTest, JXL_BOXES_TEST(ExifBrobBoxTest)) {
size_t xsize = 1, ysize = 1;
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
jxl::TestCodestreamParams params;
@ -5222,7 +5222,7 @@ TEST(DecodeTest, ExifBrobBoxTest) {
}
}
TEST(DecodeTest, PartialCodestreamBoxTest) {
TEST(DecodeTest, JXL_BOXES_TEST(PartialCodestreamBoxTest)) {
size_t xsize = 23, ysize = 81;
std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0);
JxlPixelFormat format_orig = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0};

View File

@ -840,6 +840,35 @@ Status ApplyHlgOotf(JxlCms* t, float* JXL_RESTRICT buf, size_t xsize,
return true;
}
bool ApplyCICP(const uint8_t color_primaries,
const uint8_t transfer_characteristics,
const uint8_t matrix_coefficients, const uint8_t full_range,
ColorEncoding* JXL_RESTRICT c) {
if (matrix_coefficients != 0) return false;
if (full_range != 1) return false;
const auto primaries = static_cast<Primaries>(color_primaries);
const auto tf = static_cast<TransferFunction>(transfer_characteristics);
if (!EnumValid(tf) || tf == TransferFunction::kUnknown) return false;
if (!(EnumValid(primaries) || color_primaries == 12) ||
primaries == Primaries::kCustom) {
return false;
}
c->SetColorSpace(ColorSpace::kRGB);
c->tf.SetTransferFunction(tf);
if (primaries == Primaries::kP3) {
c->white_point = WhitePoint::kDCI;
c->primaries = Primaries::kP3;
} else if (color_primaries == 12) {
c->white_point = WhitePoint::kD65;
c->primaries = Primaries::kP3;
} else {
c->white_point = WhitePoint::kD65;
c->primaries = primaries;
}
return true;
}
} // namespace
Status ColorEncoding::SetFieldsFromICC() {
@ -864,6 +893,15 @@ Status ColorEncoding::SetFieldsFromICC() {
icc_[66] != 0) {
return JXL_FAILURE("Invalid rendering intent %u\n", rendering_intent32);
}
// ICC and RenderingIntent have the same values (0..3).
rendering_intent = static_cast<RenderingIntent>(rendering_intent32);
if (profile.has_CICP && ApplyCICP(profile.CICP.color_primaries,
profile.CICP.transfer_characteristics,
profile.CICP.matrix_coefficients,
profile.CICP.video_full_range_flag, this)) {
return true;
}
SetColorSpace(ColorSpaceFromProfile(profile));
cmyk_ = (profile.data_color_space == skcms_Signature_CMYK);
@ -877,8 +915,6 @@ Status ColorEncoding::SetFieldsFromICC() {
// Relies on color_space/white point/primaries being set already.
DetectTransferFunction(profile, this);
// ICC and RenderingIntent have the same values (0..3).
rendering_intent = static_cast<RenderingIntent>(rendering_intent32);
#else // JPEGXL_ENABLE_SKCMS
const cmsContext context = GetContext();
@ -886,6 +922,17 @@ Status ColorEncoding::SetFieldsFromICC() {
Profile profile;
JXL_RETURN_IF_ERROR(DecodeProfile(context, icc_, &profile));
static constexpr size_t kCICPSize = 12;
static constexpr auto kCICPSignature =
static_cast<cmsTagSignature>(0x63696370);
uint8_t cicp_buffer[kCICPSize];
if (cmsReadRawTag(profile.get(), kCICPSignature, cicp_buffer, kCICPSize) ==
kCICPSize &&
ApplyCICP(cicp_buffer[8], cicp_buffer[9], cicp_buffer[10],
cicp_buffer[11], this)) {
return true;
}
const cmsUInt32Number rendering_intent32 =
cmsGetHeaderRenderingIntent(profile.get());
if (rendering_intent32 > 3) {
@ -916,23 +963,18 @@ Status ColorEncoding::SetFieldsFromICC() {
void ColorEncoding::DecideIfWantICC() {
PaddedBytes icc_new;
bool equivalent;
#if JPEGXL_ENABLE_SKCMS
skcms_ICCProfile profile;
if (!DecodeProfile(ICC().data(), ICC().size(), &profile)) return;
if (!MaybeCreateProfile(*this, &icc_new)) return;
equivalent = ProfileEquivalentToICC(profile, icc_new);
#else // JPEGXL_ENABLE_SKCMS
const cmsContext context = GetContext();
Profile profile;
if (!DecodeProfile(context, ICC(), &profile)) return;
if (cmsGetColorSpace(profile.get()) == cmsSigCmykData) return;
if (!MaybeCreateProfile(*this, &icc_new)) return;
equivalent = ProfileEquivalentToICC(context, profile, icc_new, *this);
#endif // JPEGXL_ENABLE_SKCMS
// Successfully created a profile => reconstruction should be equivalent.
JXL_ASSERT(equivalent);
want_icc_ = false;
}

View File

@ -42,6 +42,8 @@
namespace jxl {
namespace {
constexpr bool kPrintTree = false;
// Squeeze default quantization factors
// these quantization factors are for -Q 50 (other qualities simply scale the
// factors; things are rounded down and obviously cannot get below 1)
@ -1090,7 +1092,7 @@ Status ModularFrameEncoder::PrepareEncoding(const FrameHeader& frame_header,
JXL_ASSERT(tree_.size() == decoded_tree.size());
tree_ = std::move(decoded_tree);
if (WantDebugOutput(aux_out)) {
if (kPrintTree && WantDebugOutput(aux_out)) {
if (frame_header.dc_level > 0) {
PrintTree(tree_, aux_out->debug_prefix + "/dc_frame_level" +
std::to_string(frame_header.dc_level) + "_tree");

View File

@ -762,6 +762,18 @@ JxlEncoderStatus JxlEncoderSetBasicInfo(JxlEncoder* enc,
enc->metadata.m.modular_16_bit_buffer_sufficient =
(!info->uses_original_profile || info->bits_per_sample <= 12) &&
info->alpha_bits <= 12;
if ((info->intrinsic_xsize > 0 || info->intrinsic_ysize > 0) &&
(info->intrinsic_xsize != info->xsize ||
info->intrinsic_ysize != info->ysize)) {
if (info->intrinsic_xsize > (1ull << 30ull) ||
info->intrinsic_ysize > (1ull << 30ull) ||
!enc->metadata.m.intrinsic_size.Set(info->intrinsic_xsize,
info->intrinsic_ysize)) {
return JXL_API_ERROR(enc, JXL_ENC_ERR_API_USAGE,
"Invalid intrinsic dimensions");
}
enc->metadata.m.have_intrinsic_size = true;
}
// The number of extra channels includes the alpha channel, so for example and
// RGBA with no other extra channels, has exactly num_extra_channels == 1
@ -939,8 +951,9 @@ JxlEncoderStatus JxlEncoderSetFrameLossless(
JxlEncoderFrameSettings* frame_settings, const JXL_BOOL lossless) {
if (lossless && frame_settings->enc->basic_info_set &&
frame_settings->enc->metadata.m.xyb_encoded) {
return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
"Set use_original_profile=true for lossless encoding");
return JXL_API_ERROR(
frame_settings->enc, JXL_ENC_ERR_API_USAGE,
"Set uses_original_profile=true for lossless encoding");
}
frame_settings->values.lossless = lossless;
return JXL_ENC_SUCCESS;
@ -1691,8 +1704,9 @@ JxlEncoderStatus JxlEncoderAddImageFrame(
}
if (frame_settings->values.lossless &&
frame_settings->enc->metadata.m.xyb_encoded) {
return JXL_API_ERROR(frame_settings->enc, JXL_ENC_ERR_API_USAGE,
"Set use_original_profile=true for lossless encoding");
return JXL_API_ERROR(
frame_settings->enc, JXL_ENC_ERR_API_USAGE,
"Set uses_original_profile=true for lossless encoding");
}
queued_frame->option_values.cparams.level =
frame_settings->enc->codestream_level;

View File

@ -890,6 +890,8 @@ TEST(EncodeTest, BasicInfoTest) {
basic_info.min_nits = 5.0;
basic_info.linear_below = 12.7;
basic_info.orientation = JXL_ORIENT_ROTATE_90_CW;
basic_info.intrinsic_xsize = 88;
basic_info.intrinsic_ysize = 99;
basic_info.animation.tps_numerator = 55;
basic_info.animation.tps_denominator = 77;
basic_info.animation.num_loops = 10;
@ -945,6 +947,8 @@ TEST(EncodeTest, BasicInfoTest) {
EXPECT_EQ(basic_info.uses_original_profile,
basic_info2.uses_original_profile);
EXPECT_EQ(basic_info.orientation, basic_info2.orientation);
EXPECT_EQ(basic_info.intrinsic_xsize, basic_info2.intrinsic_xsize);
EXPECT_EQ(basic_info.intrinsic_ysize, basic_info2.intrinsic_ysize);
EXPECT_EQ(basic_info.num_color_channels, basic_info2.num_color_channels);
// TODO(lode): also test num_extra_channels, but currently there may be a
// mismatch between 0 and 1 if there is alpha, until encoder support for
@ -1167,7 +1171,7 @@ TEST(EncodeTest, CroppedFrameTest) {
EXPECT_EQ(true, seen_frame);
}
TEST(EncodeTest, BoxTest) {
TEST(EncodeTest, JXL_BOXES_TEST(BoxTest)) {
// Test with uncompressed boxes and with brob boxes
for (int compress_box = 0; compress_box <= 1; ++compress_box) {
// Tests adding two metadata boxes with the encoder: an exif box before the

View File

@ -10,6 +10,8 @@
#define LIB_JXL_FAST_DCT_INL_H_
#endif
#include <cmath>
#include <hwy/aligned_allocator.h>
#include <hwy/highway.h>

View File

@ -1022,6 +1022,9 @@ Status WriteJpegInternal(const JPEGData& jpg, const JPEGOutput& out,
case SerializationState::DONE:
JXL_ASSERT(ss->output_queue.empty());
if (ss->pad_bits != nullptr && ss->pad_bits != ss->pad_bits_end) {
return JXL_FAILURE("Invalid number of padding bits.");
}
return true;
case SerializationState::ERROR:

View File

@ -288,8 +288,6 @@ Status JPEGData::VisitFields(Visitor* visitor) {
JXL_RETURN_IF_ERROR(visitor->Bits(16, 0, &restart_interval));
}
uint64_t padding_spot_limit = scan_info.size();
for (auto& scan : scan_info) {
uint32_t num_reset_points = scan.reset_points.size();
JXL_RETURN_IF_ERROR(visitor->U32(Val(0), BitsOffset(2, 1), BitsOffset(4, 4),
@ -349,13 +347,6 @@ Status JPEGData::VisitFields(Visitor* visitor) {
}
last_block_idx = block_idx;
}
if (restart_interval > 0) {
int MCUs_per_row = 0;
int MCU_rows = 0;
CalculateMcuSize(scan, &MCUs_per_row, &MCU_rows);
padding_spot_limit += DivCeil(MCU_rows * MCUs_per_row, restart_interval);
}
}
std::vector<uint32_t> inter_marker_data_sizes;
inter_marker_data_sizes.reserve(info.num_intermarker);
@ -377,18 +368,19 @@ Status JPEGData::VisitFields(Visitor* visitor) {
if (has_zero_padding_bit) {
uint32_t nbit = padding_bits.size();
JXL_RETURN_IF_ERROR(visitor->Bits(24, 0, &nbit));
if (nbit > 7 * padding_spot_limit) {
return JXL_FAILURE("Number of padding bits does not correspond to image");
}
// TODO(eustas): check that that much bits of input are available.
if (visitor->IsReading()) {
padding_bits.resize(nbit);
}
// TODO(eustas): read in (8-64?) bit groups to reduce overhead.
for (uint8_t& bit : padding_bits) {
bool bbit = bit;
JXL_RETURN_IF_ERROR(visitor->Bool(false, &bbit));
bit = bbit;
padding_bits.reserve(std::min<uint32_t>(1024u, nbit));
for (uint32_t i = 0; i < nbit; i++) {
bool bbit = false;
JXL_RETURN_IF_ERROR(visitor->Bool(false, &bbit));
padding_bits.push_back(bbit);
}
} else {
for (uint8_t& bit : padding_bits) {
bool bbit = bit;
JXL_RETURN_IF_ERROR(visitor->Bool(false, &bbit));
bit = bbit;
}
}
}

View File

@ -50,6 +50,12 @@
#define JXL_TRANSCODE_JPEG_TEST(X) DISABLED_##X
#endif // JPEGXL_ENABLE_TRANSCODE_JPEG
#if JPEGXL_ENABLE_BOXES
#define JXL_BOXES_TEST(X) X
#else
#define JXL_BOXES_TEST(X) DISABLED_##X
#endif // JPEGXL_ENABLE_BOXES
#ifdef THREAD_SANITIZER
#define JXL_TSAN_SLOW_TEST(X) DISABLED_##X
#else

View File

@ -5,6 +5,7 @@ set(TEST_FILES
extras/codec_test.cc
extras/dec/color_description_test.cc
extras/dec/pgx_test.cc
extras/decode_jpeg_test.cc
jxl/ac_strategy_test.cc
jxl/alpha_test.cc
jxl/ans_common_test.cc

View File

@ -16,6 +16,11 @@ bool LoadJpegXlImage(const gchar *const filename, gint32 *const image_id) {
GimpColorProfile *profile_icc = nullptr;
GimpColorProfile *profile_int = nullptr;
bool is_linear = false;
unsigned long xsize = 0, ysize = 0;
long crop_x0 = 0, crop_y0 = 0;
size_t layer_idx = 0;
uint32_t frame_duration = 0;
double tps_denom = 1.f, tps_numer = 1.f;
gint32 layer;
@ -28,6 +33,10 @@ bool LoadJpegXlImage(const gchar *const filename, gint32 *const image_id) {
GimpPrecision precision = GIMP_PRECISION_U16_GAMMA;
JxlBasicInfo info = {};
JxlPixelFormat format = {};
JxlAnimationHeader animation = {};
JxlBlendMode blend_mode = JXL_BLEND_BLEND;
char *frame_name = nullptr; // will be realloced
size_t frame_name_len = 0;
format.num_channels = 4;
format.data_type = JXL_TYPE_FLOAT;
@ -53,9 +62,9 @@ bool LoadJpegXlImage(const gchar *const filename, gint32 *const image_id) {
auto dec = JxlDecoderMake(nullptr);
if (JXL_DEC_SUCCESS !=
JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO |
JXL_DEC_COLOR_ENCODING |
JXL_DEC_FULL_IMAGE)) {
JxlDecoderSubscribeEvents(dec.get(),
JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING |
JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME)) {
g_printerr(LOAD_PROC " Error: JxlDecoderSubscribeEvents failed\n");
return false;
}
@ -66,6 +75,12 @@ bool LoadJpegXlImage(const gchar *const filename, gint32 *const image_id) {
g_printerr(LOAD_PROC " Error: JxlDecoderSetParallelRunner failed\n");
return false;
}
// TODO: make this work with coalescing set to false, while handling frames
// with duration 0 and references to earlier frames correctly.
if (JXL_DEC_SUCCESS != JxlDecoderSetCoalescing(dec.get(), JXL_TRUE)) {
g_printerr(LOAD_PROC " Error: JxlDecoderSetCoalescing failed\n");
return false;
}
// grand decode loop...
JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
@ -81,9 +96,16 @@ bool LoadJpegXlImage(const gchar *const filename, gint32 *const image_id) {
return false;
}
xsize = info.xsize;
ysize = info.ysize;
if (info.have_animation) {
animation = info.animation;
tps_denom = animation.tps_denominator;
tps_numer = animation.tps_numerator;
}
JxlResizableParallelRunnerSetThreads(
runner.get(),
JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize));
runner.get(), JxlResizableParallelRunnerSuggestThreads(xsize, ysize));
} else if (status == JXL_DEC_COLOR_ENCODING) {
// check for ICC profile
size_t icc_size = 0;
@ -279,11 +301,11 @@ bool LoadJpegXlImage(const gchar *const filename, gint32 *const image_id) {
// create new image
if (is_linear) {
*image_id = gimp_image_new_with_precision(
info.xsize, info.ysize, image_type, GIMP_PRECISION_FLOAT_LINEAR);
*image_id = gimp_image_new_with_precision(xsize, ysize, image_type,
GIMP_PRECISION_FLOAT_LINEAR);
} else {
*image_id = gimp_image_new_with_precision(
info.xsize, info.ysize, image_type, GIMP_PRECISION_FLOAT_GAMMA);
*image_id = gimp_image_new_with_precision(xsize, ysize, image_type,
GIMP_PRECISION_FLOAT_GAMMA);
}
if (profile_int) {
@ -306,10 +328,38 @@ bool LoadJpegXlImage(const gchar *const filename, gint32 *const image_id) {
g_printerr(LOAD_PROC " Error: JxlDecoderSetImageOutBuffer failed\n");
return false;
}
} else if (status == JXL_DEC_FULL_IMAGE || status == JXL_DEC_FRAME) {
} else if (status == JXL_DEC_FULL_IMAGE) {
// create and insert layer
layer = gimp_layer_new(*image_id, "Background", info.xsize, info.ysize,
layer_type, /*opacity=*/100,
gchar *layer_name;
if (layer_idx == 0 && !info.have_animation) {
layer_name = g_strdup_printf("Background");
} else {
const GString *blend_null_flag = g_string_new("");
const GString *blend_replace_flag = g_string_new(" (replace)");
const GString *blend_combine_flag = g_string_new(" (combine)");
GString *blend;
if (blend_mode == JXL_BLEND_REPLACE) {
blend = (GString *)blend_replace_flag;
} else if (blend_mode == JXL_BLEND_BLEND) {
blend = (GString *)blend_combine_flag;
} else {
blend = (GString *)blend_null_flag;
}
char *temp_frame_name = nullptr;
bool must_free_frame_name = false;
if (frame_name_len == 0) {
temp_frame_name = g_strdup_printf("Frame %lu", layer_idx + 1);
must_free_frame_name = true;
} else {
temp_frame_name = frame_name;
}
double fduration = frame_duration * 1000.f * tps_denom / tps_numer;
layer_name = g_strdup_printf("%s (%.15gms)%s", temp_frame_name,
fduration, blend->str);
if (must_free_frame_name) free(temp_frame_name);
}
layer = gimp_layer_new(*image_id, layer_name, xsize, ysize, layer_type,
/*opacity=*/100,
gimp_image_get_default_new_layer_mode(*image_id));
gimp_image_insert_layer(*image_id, layer, /*parent_id=*/-1,
@ -333,12 +383,42 @@ bool LoadJpegXlImage(const gchar *const filename, gint32 *const image_id) {
const Babl *source_format = babl_format(babl_format_str.c_str());
babl_process(babl_fish(source_format, destination_format),
pixels_buffer_1, pixels_buffer_2, info.xsize * info.ysize);
pixels_buffer_1, pixels_buffer_2, xsize * ysize);
gegl_buffer_set(buffer, GEGL_RECTANGLE(0, 0, info.xsize, info.ysize), 0,
nullptr, pixels_buffer_2, GEGL_AUTO_ROWSTRIDE);
gegl_buffer_set(buffer, GEGL_RECTANGLE(0, 0, xsize, ysize), 0, nullptr,
pixels_buffer_2, GEGL_AUTO_ROWSTRIDE);
gimp_item_transform_translate(layer, crop_x0, crop_y0);
g_clear_object(&buffer);
g_free(layer_name);
layer_idx++;
} else if (status == JXL_DEC_FRAME) {
JxlFrameHeader frame_header;
if (JxlDecoderGetFrameHeader(dec.get(), &frame_header) !=
JXL_DEC_SUCCESS) {
g_printerr(LOAD_PROC " Error: JxlDecoderSetImageOutBuffer failed\n");
return false;
}
xsize = frame_header.layer_info.xsize;
ysize = frame_header.layer_info.ysize;
crop_x0 = frame_header.layer_info.crop_x0;
crop_y0 = frame_header.layer_info.crop_y0;
frame_duration = frame_header.duration;
blend_mode = frame_header.layer_info.blend_info.blendmode;
if (blend_mode != JXL_BLEND_BLEND && blend_mode != JXL_BLEND_REPLACE) {
g_printerr(
LOAD_PROC
" Warning: JxlDecoderGetFrameHeader: Unhandled blend mode: %d\n",
blend_mode);
}
if ((frame_name_len = frame_header.name_length) > 0) {
frame_name = (char *)realloc(frame_name, frame_name_len);
if (JXL_DEC_SUCCESS !=
JxlDecoderGetFrameName(dec.get(), frame_name, frame_name_len)) {
g_printerr(LOAD_PROC "Error: JxlDecoderGetFrameName failed");
return false;
};
}
} else if (status == JXL_DEC_SUCCESS) {
// All decoding successfully finished.
// It's not required to call JxlDecoderReleaseInput(dec.get())