// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include <jxl/cms.h>
#include <jxl/codestream_header.h>
#include <jxl/color_encoding.h>
#include <jxl/decode.h>
#include <jxl/decode_cxx.h>
#include <jxl/encode.h>
#include <jxl/encode_cxx.h>
#include <jxl/types.h>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <string>
#include <utility>
#include <vector>
#include "lib/extras/codec.h"
#include "lib/jxl/base/common.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/butteraugli/butteraugli.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/dec_bit_reader.h"
#include "lib/jxl/enc_external_image.h"
#include "lib/jxl/encode_internal.h"
#include "lib/jxl/image.h"
#include "lib/jxl/image_ops.h"
#include "lib/jxl/image_test_utils.h"
#include "lib/jxl/test_memory_manager.h"
#include "lib/jxl/test_utils.h"
#include "lib/jxl/testing.h"
namespace {
using ::jxl::ImageF;
using ::jxl::test::ButteraugliDistance;
// Converts a test image to a CodecInOut.
// icc_profile can be empty to automatically deduce profile from the pixel
// format, or filled in to force this ICC profile
jxl::CodecInOut ConvertTestImage(const std::vector<uint8_t>& buf,
const size_t xsize, const size_t ysize,
const JxlPixelFormat& pixel_format,
const jxl::Bytes& icc_profile) {
jxl::CodecInOut io{jxl::test::MemoryManager()};
jxl::test::Check(io.SetSize(xsize, ysize));
bool is_gray = pixel_format.num_channels < 3 ;
bool has_alpha =
pixel_format.num_channels == 2 || pixel_format.num_channels == 4 ;
io.metadata.m.color_encoding.SetColorSpace(is_gray ? jxl::ColorSpace::kGray
: jxl::ColorSpace::kRGB);
if (has_alpha) {
// Note: alpha > 16 not yet supported by the C++ codec
switch (pixel_format.data_type) {
case JXL_TYPE_UINT8:
io.metadata.m.SetAlphaBits(8 );
break ;
case JXL_TYPE_UINT16:
case JXL_TYPE_FLOAT:
case JXL_TYPE_FLOAT16:
io.metadata.m.SetAlphaBits(16 );
break ;
default :
ADD_FAILURE() << "Roundtrip tests for data type "
<< pixel_format.data_type << " not yet implemented." ;
}
}
size_t bitdepth = 0 ;
switch (pixel_format.data_type) {
case JXL_TYPE_FLOAT:
bitdepth = 32 ;
io.metadata.m.SetFloat32Samples();
break ;
case JXL_TYPE_FLOAT16:
bitdepth = 16 ;
io.metadata.m.SetFloat16Samples();
break ;
case JXL_TYPE_UINT8:
bitdepth = 8 ;
io.metadata.m.SetUintSamples(8 );
break ;
case JXL_TYPE_UINT16:
bitdepth = 16 ;
io.metadata.m.SetUintSamples(16 );
break ;
default :
ADD_FAILURE() << "Roundtrip tests for data type "
<< pixel_format.data_type << " not yet implemented." ;
}
jxl::ColorEncoding color_encoding;
if (!icc_profile.empty()) {
jxl::IccBytes icc_profile_copy;
icc_profile.AppendTo(icc_profile_copy);
EXPECT_TRUE(
color_encoding.SetICC(std::move(icc_profile_copy), JxlGetDefaultCms()));
} else if (pixel_format.data_type == JXL_TYPE_FLOAT) {
color_encoding = jxl::ColorEncoding::LinearSRGB(is_gray);
} else {
color_encoding = jxl::ColorEncoding::SRGB(is_gray);
}
EXPECT_TRUE(ConvertFromExternal(jxl::Bytes(buf), xsize, ysize, color_encoding,
/*bits_per_sample=*/bitdepth, pixel_format,
/*pool=*/nullptr, &io.Main()));
return io;
}
template <typename T>
T ConvertTestPixel(float val);
template <>
float ConvertTestPixel<float >(const float val) {
return val;
}
template <>
uint16_t ConvertTestPixel<uint16_t>(const float val) {
return static_cast <uint16_t>(val * UINT16_MAX);
}
template <>
uint8_t ConvertTestPixel<uint8_t>(const float val) {
return static_cast <uint8_t>(val * UINT8_MAX);
}
// Returns a test image.
template <typename T>
std::vector<uint8_t> GetTestImage(const size_t xsize, const size_t ysize,
const JxlPixelFormat& pixel_format) {
std::vector<T> pixels(xsize * ysize * pixel_format.num_channels);
for (size_t y = 0 ; y < ysize; y++) {
for (size_t x = 0 ; x < xsize; x++) {
for (size_t chan = 0 ; chan < pixel_format.num_channels; chan++) {
float val;
switch (chan % 4 ) {
case 0 :
val = static_cast <float >(y) / static_cast <float >(ysize);
break ;
case 1 :
val = static_cast <float >(x) / static_cast <float >(xsize);
break ;
case 2 :
val = static_cast <float >(x + y) / static_cast <float >(xsize + ysize);
break ;
case 3 :
default :
val = static_cast <float >(x * y) / static_cast <float >(xsize * ysize);
break ;
}
pixels[(y * xsize + x) * pixel_format.num_channels + chan] =
ConvertTestPixel<T>(val);
}
}
}
std::vector<uint8_t> bytes(pixels.size() * sizeof (T));
memcpy(bytes.data(), pixels.data(), sizeof (T) * pixels.size());
return bytes;
}
void EncodeWithEncoder(JxlEncoder* enc, std::vector<uint8_t>* compressed) {
compressed->resize(64 );
uint8_t* next_out = compressed->data();
size_t avail_out = compressed->size() - (next_out - compressed->data());
JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
process_result = JxlEncoderProcessOutput(enc, &next_out, &avail_out);
if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
size_t offset = next_out - compressed->data();
compressed->resize(compressed->size() * 2 );
next_out = compressed->data() + offset;
avail_out = compressed->size() - offset;
}
}
compressed->resize(next_out - compressed->data());
EXPECT_EQ(JXL_ENC_SUCCESS, process_result);
}
// Generates some pixels using some dimensions and pixel_format,
// compresses them, and verifies that the decoded version is similar to the
// original pixels.
// TODO(firsching): change this to be a parameterized test, like in
// decode_test.cc
template <typename T>
void VerifyRoundtripCompression(
const size_t xsize, const size_t ysize,
const JxlPixelFormat& input_pixel_format,
const JxlPixelFormat& output_pixel_format, const bool lossless,
const bool use_container, const uint32_t resampling = 1 ,
const bool already_downsampled = false ,
const std::vector<std::pair<JxlExtraChannelType, std::string>>&
extra_channels = {},
const int upsampling_mode = -1 ) {
size_t orig_xsize = xsize;
size_t orig_ysize = ysize;
if (already_downsampled) {
orig_xsize = jxl::DivCeil(xsize, resampling);
orig_ysize = jxl::DivCeil(ysize, resampling);
}
JxlPixelFormat extra_channel_pixel_format = input_pixel_format;
extra_channel_pixel_format.num_channels = 1 ;
const std::vector<uint8_t> extra_channel_bytes =
GetTestImage<T>(xsize, ysize, extra_channel_pixel_format);
const std::vector<uint8_t> original_bytes =
GetTestImage<T>(orig_xsize, orig_ysize, input_pixel_format);
jxl::CodecInOut original_io = ConvertTestImage(
original_bytes, orig_xsize, orig_ysize, input_pixel_format, {});
JxlEncoder* enc = JxlEncoderCreate(nullptr);
EXPECT_NE(nullptr, enc);
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10 ));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc, use_container));
JxlBasicInfo basic_info;
jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &input_pixel_format);
basic_info.xsize = xsize;
basic_info.ysize = ysize;
basic_info.uses_original_profile = lossless;
uint32_t num_channels = input_pixel_format.num_channels;
size_t has_interleaved_alpha = num_channels == 2 || num_channels == 4 ;
JxlPixelFormat output_pixel_format_with_extra_channel_alpha =
output_pixel_format;
// In the case where we have an alpha channel, but it is provided as an extra
// channel and not interleaved, we do two things here:
// 1. modify the original_io to have the correct alpha channel
// 2. change the output_format_with_extra_alpha to have an alpha channel
bool alpha_in_extra_channels_vector = false ;
for (const auto & extra_channel : extra_channels) {
if (extra_channel.first == JXL_CHANNEL_ALPHA) {
alpha_in_extra_channels_vector = true ;
}
}
if (alpha_in_extra_channels_vector && !has_interleaved_alpha) {
JXL_TEST_ASSIGN_OR_DIE(
ImageF alpha_channel,
ImageF::Create(jxl::test::MemoryManager(), xsize, ysize));
EXPECT_TRUE(jxl::ConvertFromExternal(
extra_channel_bytes.data(), extra_channel_bytes.size(), xsize, ysize,
basic_info.bits_per_sample, extra_channel_pixel_format, 0 ,
/*pool=*/nullptr, &alpha_channel));
original_io.metadata.m.SetAlphaBits(basic_info.bits_per_sample);
ASSERT_TRUE(original_io.Main().SetAlpha(std::move(alpha_channel)));
output_pixel_format_with_extra_channel_alpha.num_channels++;
}
// Those are the num_extra_channels including a potential alpha channel.
basic_info.num_extra_channels = extra_channels.size() + has_interleaved_alpha;
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
EXPECT_EQ(enc->metadata.m.num_extra_channels,
extra_channels.size() + has_interleaved_alpha);
JxlColorEncoding color_encoding;
if (input_pixel_format.data_type == JXL_TYPE_FLOAT) {
JxlColorEncodingSetToLinearSRGB(
&color_encoding,
/*is_gray=*/input_pixel_format.num_channels < 3);
} else {
JxlColorEncodingSetToSRGB(&color_encoding,
/*is_gray=*/input_pixel_format.num_channels < 3);
}
std::vector<JxlExtraChannelInfo> channel_infos;
for (const auto & extra_channel : extra_channels) {
auto channel_type = extra_channel.first;
JxlExtraChannelInfo channel_info;
JxlEncoderInitExtraChannelInfo(channel_type, &channel_info);
channel_info.bits_per_sample = (lossless ? basic_info.bits_per_sample : 8 );
channel_info.exponent_bits_per_sample =
(lossless ? basic_info.exponent_bits_per_sample : 0 );
channel_infos.push_back(channel_info);
}
for (size_t index = 0 ; index < channel_infos.size(); index++) {
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderSetExtraChannelInfo(enc, index + has_interleaved_alpha,
&channel_infos[index]));
std::string name = extra_channels[index].second;
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderSetExtraChannelName(enc, index + has_interleaved_alpha,
name.c_str(), name.length()));
}
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc, &color_encoding));
if (resampling > 1 ) {
EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetUpsamplingMode(enc, 3 , 0 ));
EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetUpsamplingMode(enc, resampling, -2 ));
EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetUpsamplingMode(enc, resampling, 2 ));
}
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderSetUpsamplingMode(enc, resampling, upsampling_mode));
JxlEncoderFrameSettings* frame_settings =
JxlEncoderFrameSettingsCreate(enc, nullptr);
JxlEncoderSetFrameLossless(frame_settings, lossless);
if (resampling > 1 ) {
EXPECT_EQ(
JXL_ENC_SUCCESS,
JxlEncoderFrameSettingsSetOption(
frame_settings, JXL_ENC_FRAME_SETTING_RESAMPLING, resampling));
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderFrameSettingsSetOption(
frame_settings, JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED,
already_downsampled));
}
EXPECT_EQ(
JXL_ENC_SUCCESS,
JxlEncoderAddImageFrame(frame_settings, &input_pixel_format,
static_cast <const void *>(original_bytes.data()),
original_bytes.size()));
EXPECT_EQ(frame_settings->enc->input_queue.empty(), false );
for (size_t index = 0 ; index < channel_infos.size(); index++) {
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderSetExtraChannelBuffer(
frame_settings, &extra_channel_pixel_format,
static_cast <const void *>(extra_channel_bytes.data()),
extra_channel_bytes.size(), index + has_interleaved_alpha));
}
JxlEncoderCloseInput(enc);
std::vector<uint8_t> compressed;
EncodeWithEncoder(enc, &compressed);
JxlEncoderDestroy(enc);
JxlDecoder* dec = JxlDecoderCreate(nullptr);
EXPECT_NE(nullptr, dec);
const uint8_t* next_in = compressed.data();
size_t avail_in = compressed.size();
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO |
JXL_DEC_COLOR_ENCODING |
JXL_DEC_FULL_IMAGE));
JxlDecoderSetInput(dec, next_in, avail_in);
EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
size_t buffer_size;
EXPECT_EQ(
JXL_DEC_SUCCESS,
JxlDecoderImageOutBufferSize(
dec, &output_pixel_format_with_extra_channel_alpha, &buffer_size));
if (&input_pixel_format == &output_pixel_format_with_extra_channel_alpha &&
!already_downsampled) {
EXPECT_EQ(buffer_size, original_bytes.size());
}
JxlBasicInfo info;
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
EXPECT_EQ(xsize, info.xsize);
EXPECT_EQ(ysize, info.ysize);
EXPECT_EQ(extra_channels.size() + has_interleaved_alpha,
info.num_extra_channels);
EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec));
size_t icc_profile_size;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA,
&icc_profile_size));
std::vector<uint8_t> icc_profile(icc_profile_size);
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile(
dec, JXL_COLOR_PROFILE_TARGET_DATA,
icc_profile.data(), icc_profile.size()));
std::vector<uint8_t> decoded_bytes(buffer_size);
EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetImageOutBuffer(
dec, &output_pixel_format_with_extra_channel_alpha,
decoded_bytes.data(), decoded_bytes.size()));
std::vector<std::vector<uint8_t>> extra_channel_decoded_bytes(
info.num_extra_channels - has_interleaved_alpha);
for (size_t index = has_interleaved_alpha; index < info.num_extra_channels;
index++) {
JxlExtraChannelInfo channel_info;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetExtraChannelInfo(dec, index, &channel_info));
EXPECT_EQ(channel_info.type,
extra_channels[index - has_interleaved_alpha].first);
std::string input_name =
extra_channels[index - has_interleaved_alpha].second;
const size_t name_length = channel_info.name_length;
EXPECT_EQ(input_name.size(), name_length);
std::vector<char > output_name(name_length + 1 );
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetExtraChannelName(dec, index, output_name.data(),
output_name.size()));
EXPECT_EQ(0 ,
memcmp(input_name.data(), output_name.data(), input_name.size()));
size_t extra_buffer_size;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderExtraChannelBufferSize(dec, &output_pixel_format,
&extra_buffer_size, index));
std::vector<uint8_t> extra_decoded_bytes(extra_buffer_size);
extra_channel_decoded_bytes[index - has_interleaved_alpha] =
std::move(extra_decoded_bytes);
EXPECT_EQ(
JXL_DEC_SUCCESS,
JxlDecoderSetExtraChannelBuffer(
dec, &output_pixel_format,
extra_channel_decoded_bytes[index - has_interleaved_alpha].data(),
extra_channel_decoded_bytes[index - has_interleaved_alpha].size(),
index));
}
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
// Check if there are no further errors after getting the full image, e.g.
// check that the final codestream box is actually marked as last.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
JxlDecoderDestroy(dec);
jxl::CodecInOut decoded_io = ConvertTestImage(
decoded_bytes, xsize, ysize, output_pixel_format_with_extra_channel_alpha,
jxl::Bytes(icc_profile));
if (already_downsampled) {
jxl::Image3F* color = decoded_io.Main().color();
JXL_TEST_ASSIGN_OR_DIE(*color, jxl::DownsampleImage(*color, resampling));
if (decoded_io.Main().HasAlpha()) {
ImageF* alpha = decoded_io.Main().alpha();
JXL_TEST_ASSIGN_OR_DIE(*alpha, jxl::DownsampleImage(*alpha, resampling));
}
EXPECT_TRUE(decoded_io.SetSize(color->xsize(), color->ysize()));
}
if (lossless && !already_downsampled) {
JXL_EXPECT_OK(jxl::SamePixels(*original_io.Main().color(),
*decoded_io.Main().color(), _));
} else {
jxl::ButteraugliParams butteraugli_params;
float butteraugli_score =
ButteraugliDistance(original_io.frames, decoded_io.frames,
butteraugli_params, *JxlGetDefaultCms(),
/*distmap=*/nullptr, nullptr);
float target_score = 1 .5 f;
// upsampling mode 1 (unlike default and NN) does not downscale back to the
// already downsampled image
if (upsampling_mode == 1 && resampling >= 4 && already_downsampled)
target_score = 15 .f;
EXPECT_LE(butteraugli_score, target_score);
}
JxlPixelFormat extra_channel_output_pixel_format = output_pixel_format;
extra_channel_output_pixel_format.num_channels = 1 ;
for (auto & extra_channel : extra_channel_decoded_bytes) {
EXPECT_EQ(extra_channel.size(), extra_channel_bytes.size());
if (lossless) {
EXPECT_EQ(jxl::test::ComparePixels(extra_channel.data(),
extra_channel_bytes.data(), xsize,
ysize, extra_channel_pixel_format,
extra_channel_output_pixel_format),
0 u);
EXPECT_EQ(extra_channel, extra_channel_bytes);
}
}
}
} // namespace
TEST(RoundtripTest, FloatFrameRoundtripTest) {
std::vector<std::vector<std::pair<JxlExtraChannelType, std::string>>>
extra_channels_cases = {{},
{{JXL_CHANNEL_ALPHA, "my extra alpha channel" }},
{{JXL_CHANNEL_CFA, "my cfa channel" }},
{{JXL_CHANNEL_DEPTH, "depth" },
{JXL_CHANNEL_SELECTION_MASK, "mask" },
{JXL_CHANNEL_BLACK, "black" },
{JXL_CHANNEL_CFA, "my cfa channel" },
{JXL_CHANNEL_OPTIONAL, "optional channel" }},
{{JXL_CHANNEL_DEPTH, "very deep" }}};
for (bool use_container : {false , true }) {
for (bool lossless : {false , true }) {
for (uint32_t num_channels = 1 ; num_channels < 5 ; num_channels++) {
for (auto & extra_channels : extra_channels_cases) {
uint32_t has_alpha = static_cast <uint32_t>(num_channels % 2 == 0 );
uint32_t total_extra_channels = has_alpha + extra_channels.size();
// There's no support (yet) for lossless extra float
// channels, so we don't test it.
if (total_extra_channels == 0 || !lossless) {
JxlPixelFormat pixel_format = JxlPixelFormat{
num_channels, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0 };
VerifyRoundtripCompression<float >(
63 , 129 , pixel_format, pixel_format, lossless, use_container, 1 ,
false , extra_channels);
}
}
}
}
}
}
TEST(RoundtripTest, Uint16FrameRoundtripTest) {
std::vector<std::vector<std::pair<JxlExtraChannelType, std::string>>>
extra_channels_cases = {{},
{{JXL_CHANNEL_ALPHA, "my extra alpha channel" }},
{{JXL_CHANNEL_CFA, "my cfa channel" }},
{{JXL_CHANNEL_CFA, "my cfa channel" },
{JXL_CHANNEL_BLACK, "k_channel" }},
{{JXL_CHANNEL_DEPTH, "very deep" }}};
for (int use_container = 0 ; use_container < 2 ; use_container++) {
for (int lossless = 0 ; lossless < 2 ; lossless++) {
for (uint32_t num_channels = 1 ; num_channels < 5 ; num_channels++) {
for (auto & extra_channels : extra_channels_cases) {
JxlPixelFormat pixel_format = JxlPixelFormat{
num_channels, JXL_TYPE_UINT16, JXL_NATIVE_ENDIAN, 0 };
VerifyRoundtripCompression<uint16_t>(
63 , 129 , pixel_format, pixel_format, static_cast <bool >(lossless),
static_cast <bool >(use_container), 1 , false , extra_channels);
}
}
}
}
}
TEST(RoundtripTest, Uint8FrameRoundtripTest) {
std::vector<std::vector<std::pair<JxlExtraChannelType, std::string>>>
extra_channels_cases = {{},
{{JXL_CHANNEL_THERMAL, "temperature" }},
{{JXL_CHANNEL_ALPHA, "my extra alpha channel" }},
{{JXL_CHANNEL_CFA, "my cfa channel" }},
{{JXL_CHANNEL_CFA, "my cfa channel" },
{JXL_CHANNEL_BLACK, "k_channel" }},
{{JXL_CHANNEL_DEPTH, "very deep" }}};
for (int use_container = 0 ; use_container < 2 ; use_container++) {
for (int lossless = 0 ; lossless < 2 ; lossless++) {
for (uint32_t num_channels = 1 ; num_channels < 5 ; num_channels++) {
for (auto & extra_channels : extra_channels_cases) {
JxlPixelFormat pixel_format = JxlPixelFormat{
num_channels, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 };
VerifyRoundtripCompression<uint8_t>(
63 , 129 , pixel_format, pixel_format, static_cast <bool >(lossless),
static_cast <bool >(use_container), 1 , false , extra_channels);
}
}
}
}
}
TEST(RoundtripTest, TestNonlinearSrgbAsXybEncoded) {
for (int use_container = 0 ; use_container < 2 ; use_container++) {
for (uint32_t num_channels = 1 ; num_channels < 5 ; num_channels++) {
JxlPixelFormat pixel_format_in =
JxlPixelFormat{num_channels, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 };
JxlPixelFormat pixel_format_out =
JxlPixelFormat{num_channels, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0 };
VerifyRoundtripCompression<uint8_t>(
63 , 129 , pixel_format_in, pixel_format_out,
/*lossless=*/false, static_cast<bool>(use_container), 1, false, {});
}
}
}
TEST(RoundtripTest, Resampling) {
JxlPixelFormat pixel_format =
JxlPixelFormat{3 , JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 };
VerifyRoundtripCompression<uint8_t>(63 , 129 , pixel_format, pixel_format,
/*lossless=*/false,
/*use_container=*/false, 2,
/*already_downsampled=*/false);
// TODO(lode): also make this work for odd sizes. This requires a fix in
// enc_frame.cc to not set custom_size_or_origin to true due to even/odd
// mismatch.
for (int factor : {2 , 4 , 8 }) {
for (int upsampling_mode : {-1 , 0 , 1 }) {
VerifyRoundtripCompression<uint8_t>(
64 , 128 , pixel_format, pixel_format,
/*lossless=*/true,
/*use_container=*/false, factor,
/*already_downsampled=*/true, /*extra_channels=*/{}, upsampling_mode);
}
}
}
TEST(RoundtripTest, ExtraBoxesTest) {
JxlPixelFormat pixel_format =
JxlPixelFormat{4 , JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0 };
const size_t xsize = 61 ;
const size_t ysize = 71 ;
const std::vector<uint8_t> original_bytes =
GetTestImage<float >(xsize, ysize, pixel_format);
jxl::CodecInOut original_io =
ConvertTestImage(original_bytes, xsize, ysize, pixel_format, {});
JxlEncoder* enc = JxlEncoderCreate(nullptr);
EXPECT_NE(nullptr, enc);
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc, true ));
JxlBasicInfo basic_info;
jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
basic_info.xsize = xsize;
basic_info.ysize = ysize;
basic_info.uses_original_profile = JXL_FALSE;
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10 ));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
JxlColorEncoding color_encoding;
JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3 );
if (pixel_format.data_type == JXL_TYPE_FLOAT) {
JxlColorEncodingSetToLinearSRGB(&color_encoding, is_gray);
} else {
JxlColorEncodingSetToSRGB(&color_encoding, is_gray);
}
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc, &color_encoding));
JxlEncoderFrameSettings* frame_settings =
JxlEncoderFrameSettingsCreate(enc, nullptr);
JxlEncoderSetFrameLossless(frame_settings, JXL_FALSE);
EXPECT_EQ(
JXL_ENC_SUCCESS,
JxlEncoderAddImageFrame(frame_settings, &pixel_format,
static_cast <const void *>(original_bytes.data()),
original_bytes.size()));
JxlEncoderCloseInput(enc);
std::vector<uint8_t> compressed;
EncodeWithEncoder(enc, &compressed);
JxlEncoderDestroy(enc);
std::vector<uint8_t> extra_data(1023 );
jxl::AppendBoxHeader(jxl::MakeBoxType("crud" ), extra_data.size(), false ,
&compressed);
compressed.insert(compressed.end(), extra_data.begin(), extra_data.end());
JxlDecoder* dec = JxlDecoderCreate(nullptr);
EXPECT_NE(nullptr, dec);
const uint8_t* next_in = compressed.data();
size_t avail_in = compressed.size();
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO |
JXL_DEC_COLOR_ENCODING |
JXL_DEC_FULL_IMAGE));
JxlDecoderSetInput(dec, next_in, avail_in);
EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
size_t buffer_size;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderImageOutBufferSize(dec, &pixel_format, &buffer_size));
EXPECT_EQ(buffer_size, original_bytes.size());
JxlBasicInfo info;
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
EXPECT_EQ(xsize, info.xsize);
EXPECT_EQ(ysize, info.ysize);
EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec));
size_t icc_profile_size;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA,
&icc_profile_size));
std::vector<uint8_t> icc_profile(icc_profile_size);
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile(
dec, JXL_COLOR_PROFILE_TARGET_DATA,
icc_profile.data(), icc_profile.size()));
std::vector<uint8_t> decoded_bytes(buffer_size);
EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(dec, &pixel_format,
decoded_bytes.data(),
decoded_bytes.size()));
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
JxlDecoderDestroy(dec);
jxl::CodecInOut decoded_io = ConvertTestImage(
decoded_bytes, xsize, ysize, pixel_format, jxl::Bytes(icc_profile));
jxl::ButteraugliParams butteraugli_params;
float butteraugli_score =
ButteraugliDistance(original_io.frames, decoded_io.frames,
butteraugli_params, *JxlGetDefaultCms(),
/*distmap=*/nullptr, nullptr);
EXPECT_LE(butteraugli_score, 1 .0 f);
}
TEST(RoundtripTest, MultiFrameTest) {
JxlPixelFormat pixel_format =
JxlPixelFormat{4 , JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0 };
const size_t xsize = 61 ;
const size_t ysize = 71 ;
const size_t nb_frames = 4 ;
size_t compressed_size = 0 ;
for (int index_frames : {0 , 1 }) {
// use a vertical filmstrip of nb_frames frames
const std::vector<uint8_t> original_bytes =
GetTestImage<float >(xsize, ysize * nb_frames, pixel_format);
jxl::CodecInOut original_io = ConvertTestImage(
original_bytes, xsize, ysize * nb_frames, pixel_format, {});
JxlEncoder* enc = JxlEncoderCreate(nullptr);
EXPECT_NE(nullptr, enc);
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc, true ));
JxlBasicInfo basic_info;
jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format);
basic_info.xsize = xsize;
basic_info.ysize = ysize;
basic_info.uses_original_profile = JXL_FALSE;
basic_info.have_animation = JXL_TRUE;
basic_info.animation.tps_numerator = 1 ;
basic_info.animation.tps_denominator = 1 ;
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10 ));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
JxlColorEncoding color_encoding;
JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3 );
if (pixel_format.data_type == JXL_TYPE_FLOAT) {
JxlColorEncodingSetToLinearSRGB(&color_encoding, is_gray);
} else {
JxlColorEncodingSetToSRGB(&color_encoding, is_gray);
}
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderSetColorEncoding(enc, &color_encoding));
JxlEncoderFrameSettings* frame_settings =
JxlEncoderFrameSettingsCreate(enc, nullptr);
JxlEncoderSetFrameLossless(frame_settings, JXL_FALSE);
if (index_frames == 1 ) {
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderFrameSettingsSetOption(frame_settings,
JXL_ENC_FRAME_INDEX_BOX, 1 ));
}
size_t oneframesize = original_bytes.size() / nb_frames;
JxlFrameHeader frame_header;
JxlEncoderInitFrameHeader(&frame_header);
frame_header.duration = 1 ;
frame_header.is_last = JXL_FALSE;
for (size_t i = 0 ; i < nb_frames; i++) {
if (i + 1 == nb_frames) frame_header.is_last = JXL_TRUE;
JxlEncoderSetFrameHeader(frame_settings, &frame_header);
EXPECT_EQ(
JXL_ENC_SUCCESS,
JxlEncoderAddImageFrame(frame_settings, &pixel_format,
static_cast <const void *>(
original_bytes.data() + oneframesize * i),
oneframesize));
}
JxlEncoderCloseInput(enc);
std::vector<uint8_t> compressed;
EncodeWithEncoder(enc, &compressed);
JxlEncoderDestroy(enc);
JxlDecoder* dec = JxlDecoderCreate(nullptr);
EXPECT_NE(nullptr, dec);
const uint8_t* next_in = compressed.data();
size_t avail_in = compressed.size();
if (index_frames == 0 ) {
compressed_size = avail_in;
} else {
// a non-empty jxli box should be added
EXPECT_LE(avail_in, compressed_size + 50 );
EXPECT_GE(avail_in, compressed_size + 10 );
}
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO |
JXL_DEC_COLOR_ENCODING |
JXL_DEC_FULL_IMAGE));
JxlDecoderSetInput(dec, next_in, avail_in);
EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
size_t buffer_size;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderImageOutBufferSize(dec, &pixel_format, &buffer_size));
EXPECT_EQ(buffer_size, oneframesize);
JxlBasicInfo info;
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
EXPECT_EQ(xsize, info.xsize);
EXPECT_EQ(ysize, info.ysize);
EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec));
size_t icc_profile_size;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA,
&icc_profile_size));
std::vector<uint8_t> icc_profile(icc_profile_size);
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile(
dec, JXL_COLOR_PROFILE_TARGET_DATA,
icc_profile.data(), icc_profile.size()));
std::vector<uint8_t> decoded_bytes(buffer_size * nb_frames);
EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
for (size_t i = 0 ; i < nb_frames; i++) {
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetImageOutBuffer(
dec, &pixel_format, decoded_bytes.data() + i * oneframesize,
buffer_size));
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
}
JxlDecoderDestroy(dec);
jxl::CodecInOut decoded_io =
ConvertTestImage(decoded_bytes, xsize, ysize * nb_frames, pixel_format,
jxl::Bytes(icc_profile));
jxl::ButteraugliParams butteraugli_params;
float butteraugli_score =
ButteraugliDistance(original_io.frames, decoded_io.frames,
butteraugli_params, *JxlGetDefaultCms(),
/*distmap=*/nullptr, nullptr);
EXPECT_LE(butteraugli_score, 1 .0 f);
}
}
static const unsigned char kEncodedTestProfile[] = {
0 x1f, 0 x8b, 0 x1, 0 x13, 0 x10, 0 x0, 0 x0, 0 x0, 0 x20, 0 x4c, 0 xcc, 0 x3,
0 xe7, 0 xa0, 0 xa5, 0 xa2, 0 x90, 0 xa4, 0 x27, 0 xe8, 0 x79, 0 x1d, 0 xe3, 0 x26,
0 x57, 0 x54, 0 xef, 0 x0, 0 xe8, 0 x97, 0 x2, 0 xce, 0 xa1, 0 xd7, 0 x85, 0 x16,
0 xb4, 0 x29, 0 x94, 0 x58, 0 xf2, 0 x56, 0 xc0, 0 x76, 0 xea, 0 x23, 0 xec, 0 x7c,
0 x73, 0 x51, 0 x41, 0 x40, 0 x23, 0 x21, 0 x95, 0 x4, 0 x75, 0 x12, 0 xc9, 0 xcc,
0 x16, 0 xbd, 0 xb6, 0 x99, 0 xad, 0 xf8, 0 x75, 0 x35, 0 xb6, 0 x42, 0 xae, 0 xae,
0 xae, 0 x86, 0 x56, 0 xf8, 0 xcc, 0 x16, 0 x30, 0 xb3, 0 x45, 0 xad, 0 xd, 0 x40,
0 xd6, 0 xd1, 0 xd6, 0 x99, 0 x40, 0 xbe, 0 xe2, 0 xdc, 0 x31, 0 x7, 0 xa6, 0 xb9,
0 x27, 0 x92, 0 x38, 0 x0, 0 x3, 0 x5e, 0 x2c, 0 xbe, 0 xe6, 0 xfb, 0 x19, 0 xbf,
0 xf3, 0 x6d, 0 xbc, 0 x4d, 0 x64, 0 xe5, 0 xba, 0 x76, 0 xde, 0 x31, 0 x65, 0 x66,
0 x14, 0 xa6, 0 x3a, 0 xc5, 0 x8f, 0 xb1, 0 xb4, 0 xba, 0 x1f, 0 xb1, 0 xb8, 0 xd4,
0 x75, 0 xba, 0 x18, 0 x86, 0 x95, 0 x3c, 0 x26, 0 xf6, 0 x25, 0 x62, 0 x53, 0 xfd,
0 x9c, 0 x94, 0 x76, 0 xf6, 0 x95, 0 x2c, 0 xb1, 0 xfd, 0 xdc, 0 xc0, 0 xe4, 0 x3f,
0 xb3, 0 xff, 0 x67, 0 xde, 0 xd5, 0 x94, 0 xcc, 0 xb0, 0 x83, 0 x2f, 0 x28, 0 x93,
0 x92, 0 x3, 0 xa1, 0 x41, 0 x64, 0 x60, 0 x62, 0 x70, 0 x80, 0 x87, 0 xaf, 0 xe7,
0 x60, 0 x4a, 0 x20, 0 x23, 0 xb3, 0 x11, 0 x7, 0 x38, 0 x38, 0 xd4, 0 xa, 0 x66,
0 xb5, 0 x93, 0 x41, 0 x90, 0 x19, 0 x17, 0 x18, 0 x60, 0 xa5, 0 xb, 0 x7a, 0 x24,
0 xaa, 0 x20, 0 x81, 0 xac, 0 xa9, 0 xa1, 0 x70, 0 xa6, 0 x12, 0 x8a, 0 x4a, 0 xa3,
0 xa0, 0 xf9, 0 x9a, 0 x97, 0 xe7, 0 xa8, 0 xac, 0 x8, 0 xa8, 0 xc4, 0 x2a, 0 x86,
0 xa7, 0 x69, 0 x1e, 0 x67, 0 xe6, 0 xbe, 0 xa4, 0 xd3, 0 xff, 0 x91, 0 x61, 0 xf6,
0 x8a, 0 xe6, 0 xb5, 0 xb3, 0 x61, 0 x9f, 0 x19, 0 x17, 0 x98, 0 x27, 0 x6b, 0 xe9,
0 x8, 0 x98, 0 xe1, 0 x21, 0 x4a, 0 x9, 0 xb5, 0 xd7, 0 xca, 0 xfa, 0 x94, 0 xd0,
0 x69, 0 x1a, 0 xeb, 0 x52, 0 x1, 0 x4e, 0 xf5, 0 xf6, 0 xdf, 0 x7f, 0 xe7, 0 x29,
0 x70, 0 xee, 0 x4, 0 xda, 0 x2f, 0 xa4, 0 xff, 0 xfe, 0 xbb, 0 x6f, 0 xa8, 0 xff,
0 xfe, 0 xdb, 0 xaf, 0 x8, 0 xf6, 0 x72, 0 xa1, 0 x40, 0 x5d, 0 xf0, 0 x2d, 0 x8,
0 x82, 0 x5b, 0 x87, 0 xbd, 0 x10, 0 x8, 0 xe9, 0 x7, 0 xee, 0 x4b, 0 x80, 0 xda,
0 x4a, 0 x4, 0 xc5, 0 x5e, 0 xa0, 0 xb7, 0 x1e, 0 x60, 0 xb0, 0 x59, 0 x76, 0 x60,
0 xb, 0 x2e, 0 x19, 0 x8a, 0 x2e, 0 x1c, 0 xe6, 0 x6, 0 x20, 0 xb8, 0 x64, 0 x18,
0 x2a, 0 xcf, 0 x51, 0 x94, 0 xd4, 0 xee, 0 xc3, 0 xfe, 0 x39, 0 x74, 0 xd4, 0 x2b,
0 x48, 0 xc9, 0 x83, 0 x4c, 0 x9b, 0 xd0, 0 x4c, 0 x35, 0 x10, 0 xe3, 0 x9, 0 xf7,
0 x72, 0 xf0, 0 x7a, 0 xe, 0 xbf, 0 x7d, 0 x36, 0 x2e, 0 x19, 0 x7e, 0 x3f, 0 xc,
0 xf7, 0 x93, 0 xe7, 0 xf4, 0 x1d, 0 x32, 0 xc6, 0 xb0, 0 x89, 0 xad, 0 xe0, 0 x28,
0 xc1, 0 xa7, 0 x59, 0 xe3, 0 x0,
};
TEST(RoundtripTest, TestICCProfile) {
// JxlEncoderSetICCProfile parses the ICC profile, so a valid profile is
// needed. The profile should be passed correctly through the roundtrip.
jxl::BitReader reader(
jxl::Bytes(kEncodedTestProfile, sizeof (kEncodedTestProfile)));
std::vector<uint8_t> icc;
ASSERT_TRUE(jxl::test::ReadICC(&reader, &icc));
ASSERT_TRUE(reader.Close());
JxlPixelFormat format =
JxlPixelFormat{3 , JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 };
size_t xsize = 25 ;
size_t ysize = 37 ;
const std::vector<uint8_t> original_bytes =
GetTestImage<uint8_t>(xsize, ysize, format);
JxlEncoder* enc = JxlEncoderCreate(nullptr);
EXPECT_NE(nullptr, enc);
JxlBasicInfo basic_info;
jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &format);
basic_info.xsize = xsize;
basic_info.ysize = ysize;
basic_info.uses_original_profile = JXL_TRUE;
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info));
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderSetICCProfile(enc, icc.data(), icc.size()));
JxlEncoderFrameSettings* frame_settings =
JxlEncoderFrameSettingsCreate(enc, nullptr);
EXPECT_EQ(
JXL_ENC_SUCCESS,
JxlEncoderAddImageFrame(frame_settings, &format,
static_cast <const void *>(original_bytes.data()),
original_bytes.size()));
JxlEncoderCloseInput(enc);
std::vector<uint8_t> compressed;
EncodeWithEncoder(enc, &compressed);
JxlEncoderDestroy(enc);
JxlDecoder* dec = JxlDecoderCreate(nullptr);
EXPECT_NE(nullptr, dec);
const uint8_t* next_in = compressed.data();
size_t avail_in = compressed.size();
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO |
JXL_DEC_COLOR_ENCODING |
JXL_DEC_FULL_IMAGE));
JxlDecoderSetInput(dec, next_in, avail_in);
EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
size_t buffer_size;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderImageOutBufferSize(dec, &format, &buffer_size));
EXPECT_EQ(buffer_size, original_bytes.size());
JxlBasicInfo info;
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
EXPECT_EQ(xsize, info.xsize);
EXPECT_EQ(ysize, info.ysize);
EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec));
size_t dec_icc_size;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL,
&dec_icc_size));
EXPECT_EQ(icc.size(), dec_icc_size);
std::vector<uint8_t> dec_icc(dec_icc_size);
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile(
dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL,
dec_icc.data(), dec_icc.size()));
std::vector<uint8_t> decoded_bytes(buffer_size);
EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetImageOutBuffer(dec, &format, decoded_bytes.data(),
decoded_bytes.size()));
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
EXPECT_EQ(icc, dec_icc);
JxlDecoderDestroy(dec);
}
JXL_TRANSCODE_JPEG_TEST(RoundtripTest, TestJPEGReconstruction) {
TEST_LIBJPEG_SUPPORT();
const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg" ;
const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path);
JxlEncoderPtr enc = JxlEncoderMake(nullptr);
JxlEncoderFrameSettings* frame_settings =
JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc.get(), JXL_TRUE));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderStoreJPEGMetadata(enc.get(), JXL_TRUE));
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderAddJPEGFrame(frame_settings, orig.data(), orig.size()));
JxlEncoderCloseInput(enc.get());
std::vector<uint8_t> compressed;
EncodeWithEncoder(enc.get(), &compressed);
JxlDecoderPtr dec = JxlDecoderMake(nullptr);
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSubscribeEvents(
dec.get(), JXL_DEC_JPEG_RECONSTRUCTION | JXL_DEC_FULL_IMAGE));
JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
EXPECT_EQ(JXL_DEC_JPEG_RECONSTRUCTION, JxlDecoderProcessInput(dec.get()));
std::vector<uint8_t> reconstructed_buffer(128 );
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetJPEGBuffer(dec.get(), reconstructed_buffer.data(),
reconstructed_buffer.size()));
size_t used = 0 ;
JxlDecoderStatus dec_process_result = JXL_DEC_JPEG_NEED_MORE_OUTPUT;
while (dec_process_result == JXL_DEC_JPEG_NEED_MORE_OUTPUT) {
used = reconstructed_buffer.size() - JxlDecoderReleaseJPEGBuffer(dec.get());
reconstructed_buffer.resize(reconstructed_buffer.size() * 2 );
EXPECT_EQ(
JXL_DEC_SUCCESS,
JxlDecoderSetJPEGBuffer(dec.get(), reconstructed_buffer.data() + used,
reconstructed_buffer.size() - used));
dec_process_result = JxlDecoderProcessInput(dec.get());
}
ASSERT_EQ(JXL_DEC_FULL_IMAGE, dec_process_result);
used = reconstructed_buffer.size() - JxlDecoderReleaseJPEGBuffer(dec.get());
ASSERT_EQ(used, orig.size());
EXPECT_EQ(0 , memcmp(reconstructed_buffer.data(), orig.data(), used));
}
JXL_TRANSCODE_JPEG_TEST(RoundtripTest,
TestJPEGReconstructionWithIncompleteHuffmanCode) {
TEST_LIBJPEG_SUPPORT();
static constexpr uint8_t kJPEGBytes[] = {
// SOI
0 xff, 0 xd8, //
// SOF
0 xff, 0 xc0, 0 x00, 0 x0b, 0 x08, 0 x00, 0 x01, 0 x00, 0 x01, 0 x01, //
0 x01, 0 x11, 0 x00, //
// DQT
0 xff, 0 xdb, 0 x00, 0 x43, 0 x00, 0 x03, 0 x02, 0 x02, 0 x03, 0 x02, //
0 x02, 0 x03, 0 x03, 0 x03, 0 x03, 0 x04, 0 x03, 0 x03, 0 x04, 0 x05, //
0 x08, 0 x05, 0 x05, 0 x04, 0 x04, 0 x05, 0 x0a, 0 x07, 0 x07, 0 x06, //
0 x08, 0 x0c, 0 x0a, 0 x0c, 0 x0c, 0 x0b, 0 x0a, 0 x0b, 0 x0b, 0 x0d, //
0 x0e, 0 x12, 0 x10, 0 x0d, 0 x0e, 0 x11, 0 x0e, 0 x0b, 0 x0b, 0 x10, //
0 x16, 0 x10, 0 x11, 0 x13, 0 x14, 0 x15, 0 x15, 0 x15, 0 x0c, 0 x0f, //
0 x17, 0 x18, 0 x16, 0 x14, 0 x18, 0 x12, 0 x14, 0 x15, 0 x14, //
// DHT
0 xff, 0 xc4, 0 x01, 0 x30, // marker, len
0 x00, // slot id DC0
// counts for lengths 1 - 16 (total_count: 12)
0 x00, 0 x01, 0 x05, 0 x01, 0 x01, 0 x01, 0 x01, 0 x01, 0 x01, 0 x00, //
0 x00, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00,
// symbols: 0 - 11
0 x00, 0 x01, 0 x02, 0 x03, 0 x04, 0 x05, 0 x06, 0 x07, 0 x08, 0 x09, //
0 x0a, 0 x0b, //
0 x13, // slot id AC3
// counts for lengths 1 - 16 (total_count: 256)
0 x00, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00, 0 x00, 0 x01, 0 x01, //
0 x01, 0 x01, 0 x01, 0 x01, 0 x01, 0 xf9, //
// symbols: 0 - 255
0 x00, 0 x01, 0 x02, 0 x03, 0 x04, 0 x05, 0 x06, 0 x07, 0 x08, 0 x09, //
0 x0a, 0 x0b, 0 x0c, 0 x0d, 0 x0e, 0 x0f, 0 x10, 0 x11, 0 x12, 0 x13, //
0 x14, 0 x15, 0 x16, 0 x17, 0 x18, 0 x19, 0 x1a, 0 x1b, 0 x1c, 0 x1d, //
0 x1e, 0 x1f, 0 x20, 0 x21, 0 x22, 0 x23, 0 x24, 0 x25, 0 x26, 0 x27, //
0 x28, 0 x29, 0 x2a, 0 x2b, 0 x2c, 0 x2d, 0 x2e, 0 x2f, 0 x30, 0 x31, //
0 x32, 0 x33, 0 x34, 0 x35, 0 x36, 0 x37, 0 x38, 0 x39, 0 x3a, 0 x3b, //
0 x3c, 0 x3d, 0 x3e, 0 x3f, 0 x40, 0 x41, 0 x42, 0 x43, 0 x44, 0 x45, //
0 x46, 0 x47, 0 x48, 0 x49, 0 x4a, 0 x4b, 0 x4c, 0 x4d, 0 x4e, 0 x4f, //
0 x50, 0 x51, 0 x52, 0 x53, 0 x54, 0 x55, 0 x56, 0 x57, 0 x58, 0 x59, //
0 x5a, 0 x5b, 0 x5c, 0 x5d, 0 x5e, 0 x5f, 0 x60, 0 x61, 0 x62, 0 x63, //
0 x64, 0 x65, 0 x66, 0 x67, 0 x68, 0 x69, 0 x6a, 0 x6b, 0 x6c, 0 x6d, //
0 x6e, 0 x6f, 0 x70, 0 x71, 0 x72, 0 x73, 0 x74, 0 x75, 0 x76, 0 x77, //
0 x78, 0 x79, 0 x7a, 0 x7b, 0 x7c, 0 x7d, 0 x7e, 0 x7f, 0 x80, 0 x81, //
0 x82, 0 x83, 0 x84, 0 x85, 0 x86, 0 x87, 0 x88, 0 x89, 0 x8a, 0 x8b, //
0 x8c, 0 x8d, 0 x8e, 0 x8f, 0 x90, 0 x91, 0 x92, 0 x93, 0 x94, 0 x95, //
0 x96, 0 x97, 0 x98, 0 x99, 0 x9a, 0 x9b, 0 x9c, 0 x9d, 0 x9e, 0 x9f, //
0 xa0, 0 xa1, 0 xa2, 0 xa3, 0 xa4, 0 xa5, 0 xa6, 0 xa7, 0 xa8, 0 xa9, //
0 xaa, 0 xab, 0 xac, 0 xad, 0 xae, 0 xaf, 0 xb0, 0 xb1, 0 xb2, 0 xb3, //
0 xb4, 0 xb5, 0 xb6, 0 xb7, 0 xb8, 0 xb9, 0 xba, 0 xbb, 0 xbc, 0 xbd, //
0 xbe, 0 xbf, 0 xc0, 0 xc1, 0 xc2, 0 xc3, 0 xc4, 0 xc5, 0 xc6, 0 xc7, //
0 xc8, 0 xc9, 0 xca, 0 xcb, 0 xcc, 0 xcd, 0 xce, 0 xcf, 0 xd0, 0 xd1, //
0 xd2, 0 xd3, 0 xd4, 0 xd5, 0 xd6, 0 xd7, 0 xd8, 0 xd9, 0 xda, 0 xdb, //
0 xdc, 0 xdd, 0 xde, 0 xdf, 0 xe0, 0 xe1, 0 xe2, 0 xe3, 0 xe4, 0 xe5, //
0 xe6, 0 xe7, 0 xe8, 0 xe9, 0 xea, 0 xeb, 0 xec, 0 xed, 0 xee, 0 xef, //
0 xf0, 0 xf1, 0 xf2, 0 xf3, 0 xf4, 0 xf5, 0 xf6, 0 xf7, 0 xf8, 0 xf9, //
0 xfa, 0 xfb, 0 xfc, 0 xfd, 0 xfe, 0 xff,
// SOS
0 xff, 0 xda, 0 x00, 0 x08, 0 x01, 0 x01, 0 x03, 0 x00, 0 x3f, 0 x00, //
// entropy coded data (first 16 bit: DC 0, next 9 bits: AC eob)
0 xfc, 0 xaa, 0 x00, 0 x00, //
// EOI
0 xff, 0 xd9, //
};
static constexpr size_t kJPEGSize = sizeof (kJPEGBytes);
JxlEncoderPtr enc = JxlEncoderMake(nullptr);
JxlEncoderFrameSettings* frame_settings =
JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc.get(), JXL_TRUE));
EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderStoreJPEGMetadata(enc.get(), JXL_TRUE));
EXPECT_EQ(JXL_ENC_SUCCESS,
JxlEncoderAddJPEGFrame(frame_settings, kJPEGBytes, kJPEGSize));
JxlEncoderCloseInput(enc.get());
std::vector<uint8_t> compressed;
EncodeWithEncoder(enc.get(), &compressed);
JxlDecoderPtr dec = JxlDecoderMake(nullptr);
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSubscribeEvents(
dec.get(), JXL_DEC_JPEG_RECONSTRUCTION | JXL_DEC_FULL_IMAGE));
JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
EXPECT_EQ(JXL_DEC_JPEG_RECONSTRUCTION, JxlDecoderProcessInput(dec.get()));
std::vector<uint8_t> reconstructed_buffer(128 );
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetJPEGBuffer(dec.get(), reconstructed_buffer.data(),
reconstructed_buffer.size()));
size_t used = 0 ;
JxlDecoderStatus dec_process_result = JXL_DEC_JPEG_NEED_MORE_OUTPUT;
while (dec_process_result == JXL_DEC_JPEG_NEED_MORE_OUTPUT) {
used = reconstructed_buffer.size() - JxlDecoderReleaseJPEGBuffer(dec.get());
reconstructed_buffer.resize(reconstructed_buffer.size() * 2 );
EXPECT_EQ(
JXL_DEC_SUCCESS,
JxlDecoderSetJPEGBuffer(dec.get(), reconstructed_buffer.data() + used,
reconstructed_buffer.size() - used));
dec_process_result = JxlDecoderProcessInput(dec.get());
}
ASSERT_EQ(JXL_DEC_FULL_IMAGE, dec_process_result);
used = reconstructed_buffer.size() - JxlDecoderReleaseJPEGBuffer(dec.get());
ASSERT_EQ(used, kJPEGSize);
EXPECT_EQ(0 , memcmp(reconstructed_buffer.data(), kJPEGBytes, used));
}
Messung V0.5 in Prozent C=89 H=87 G=87
¤ Dauer der Verarbeitung: 0.16 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland