// 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.
// What type of codestream format in the boxes to use for testing enum CodeStreamBoxFormat { // Do not use box format at all, only pure codestream
kCSBF_None, // Have a single codestream box, with its actual size given in the box
kCSBF_Single, // Have a single codestream box, with box size 0 (final box running to end)
kCSBF_Single_Zero_Terminated, // Single codestream box, with another unknown box behind it
kCSBF_Single_Other, // Have multiple partial codestream boxes
kCSBF_Multi, // Have multiple partial codestream boxes, with final box size 0 (running // to end)
kCSBF_Multi_Zero_Terminated, // Have multiple partial codestream boxes, terminated by non-codestream box
kCSBF_Multi_Other_Terminated, // Have multiple partial codestream boxes, terminated by non-codestream box // that has its size set to 0 (running to end)
kCSBF_Multi_Other_Zero_Terminated, // Have multiple partial codestream boxes, and the first one has a content // of zero length
kCSBF_Multi_First_Empty, // Have multiple partial codestream boxes, and the last one has a content // of zero length and there is an unknown empty box at the end
kCSBF_Multi_Last_Empty_Other, // Have a compressed exif box before a regular codestream box
kCSBF_Brob_Exif, // Not a value but used for counting amount of enum entries
kCSBF_NUM_ENTRIES,
};
// After the full image was output, JxlDecoderProcessInput should return // success to indicate all is done, unless we requested boxes and the last // box was not a terminal unbounded box, in which case it should ask for // more input.
JxlDecoderStatus expected_status =
expect_success ? JXL_DEC_SUCCESS : JXL_DEC_NEED_MORE_INPUT;
EXPECT_EQ(expected_status, process_input(dec));
return pixels;
}
// Decodes one-shot with the API for non-streaming decoding tests.
std::vector<uint8_t> DecodeWithAPI(Span<const uint8_t> compressed, const JxlPixelFormat& format, bool use_callback, bool set_buffer_early, bool use_resizable_runner, bool require_boxes, bool expect_success) {
JxlDecoder* dec = JxlDecoderCreate(nullptr);
std::vector<uint8_t> pixels =
DecodeWithAPI(dec, compressed, format, use_callback, set_buffer_early,
use_resizable_runner, require_boxes, expect_success);
JxlDecoderDestroy(dec); return pixels;
}
// TODO(lode): add multi-threaded test when multithreaded pixel decoding from // API is implemented.
TEST(DecodeTest, DefaultParallelRunnerTest) {
JxlDecoder* dec = JxlDecoderCreate(nullptr);
EXPECT_NE(nullptr, dec);
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetParallelRunner(dec, nullptr, nullptr));
JxlDecoderDestroy(dec);
}
// Creates the header of a JPEG XL file with various custom parameters for // testing. // xsize, ysize: image dimensions to store in the SizeHeader, max 512. // bits_per_sample, orientation: a selection of header parameters to test with. // orientation: image orientation to set in the metadata // alpha_bits: if non-0, alpha extra channel bits to set in the metadata. Also // gives the alpha channel the name "alpha_test" // have_container: add box container format around the codestream. // metadata_default: if true, ImageMetadata is set to default and // bits_per_sample, orientation and alpha_bits are ignored. // insert_box: insert an extra box before the codestream box, making the header // farther away from the front than is ideal. Only used if have_container.
std::vector<uint8_t> GetTestHeader(size_t xsize, size_t ysize,
size_t bits_per_sample, size_t orientation,
size_t alpha_bits, bool xyb_encoded, bool have_container, bool metadata_default, bool insert_extra_box, const jxl::IccBytes& icc_profile) {
JxlMemoryManager* memory_manager = jxl::test::MemoryManager();
jxl::BitWriter writer{memory_manager};
EXPECT_TRUE(writer.WithMaxBits(
65536, // Large enough
jxl::LayerType::Header, nullptr, [&] { if (have_container) { const std::vector<uint8_t> signature_box = {
0, 0, 0, 0xc, 'J', 'X', 'L', ' ', 0xd, 0xa, 0x87, 0xa}; const std::vector<uint8_t> filetype_box = {
0, 0, 0, 0x14, 'f', 't', 'y', 'p', 'j', 'x', 'l', ' ', 0, 0, 0, 0, 'j', 'x', 'l', ' '}; const std::vector<uint8_t> extra_box_header = {0, 0, 0, 0xff, 't', 'e', 's', 't'}; // Beginning of codestream box, with an arbitrary size certainly large // enough to contain the header const std::vector<uint8_t> codestream_box_header = {
0, 0, 0, 0xff, 'j', 'x', 'l', 'c'};
for (uint8_t c : signature_box) {
writer.Write(8, c);
} for (uint8_t c : filetype_box) {
writer.Write(8, c);
} if (insert_extra_box) { for (uint8_t c : extra_box_header) {
writer.Write(8, c);
} for (size_t i = 0; i < 255 - 8; i++) {
writer.Write(8, 0);
}
} for (uint8_t c : codestream_box_header) {
writer.Write(8, c);
}
}
std::vector<std::vector<uint8_t>> test_samples; // Test with direct codestream
test_samples.push_back(GetTestHeader(
xsize[0], ysize[0], bits_per_sample[0], orientation[0], alpha_bits[0],
xyb_encoded, have_container[0], /*metadata_default=*/false, /*insert_extra_box=*/false, {})); // Test with container and different parameters
test_samples.push_back(GetTestHeader(
xsize[1], ysize[1], bits_per_sample[1], orientation[1], alpha_bits[1],
xyb_encoded, have_container[1], /*metadata_default=*/false, /*insert_extra_box=*/false, {}));
for (size_t i = 0; i < test_samples.size(); ++i) { const std::vector<uint8_t>& data = test_samples[i]; // Test decoding too small header first, until we reach the final byte. for (size_t size = 0; size <= data.size(); ++size) { // Test with a new decoder for each tested byte size.
JxlDecoder* dec = JxlDecoderCreate(nullptr);
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO)); const uint8_t* next_in = data.data();
size_t avail_in = size;
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in));
JxlDecoderStatus status = JxlDecoderProcessInput(dec);
if (size == data.size()) {
EXPECT_EQ(JXL_DEC_BASIC_INFO, status);
// All header bytes given so the decoder must have the basic info.
EXPECT_EQ(true, have_basic_info);
EXPECT_EQ(have_container[i], FROM_JXL_BOOL(info.have_container));
EXPECT_EQ(alpha_bits[i], info.alpha_bits); // Orientations 5..8 swap the dimensions if (orientation[i] >= 5) {
EXPECT_EQ(xsize[i], info.ysize);
EXPECT_EQ(ysize[i], info.xsize);
} else {
EXPECT_EQ(xsize[i], info.xsize);
EXPECT_EQ(ysize[i], info.ysize);
} // The API should set the orientation to identity by default since it // already applies the transformation internally by default.
EXPECT_EQ(1u, info.orientation);
EXPECT_EQ(3u, info.num_color_channels);
if (alpha_bits[i] != 0) { // Expect an extra channel
EXPECT_EQ(1u, info.num_extra_channels);
JxlExtraChannelInfo extra;
EXPECT_EQ(0, JxlDecoderGetExtraChannelInfo(dec, 0, &extra));
EXPECT_EQ(alpha_bits[i], extra.bits_per_sample);
EXPECT_EQ(JXL_CHANNEL_ALPHA, extra.type);
EXPECT_EQ(0, extra.alpha_premultiplied); // Verify the name "alpha_test" given to the alpha channel
EXPECT_EQ(10u, extra.name_length); char name[11];
EXPECT_EQ(0,
JxlDecoderGetExtraChannelName(dec, 0, name, sizeof(name)));
EXPECT_EQ(std::string("alpha_test"), std::string(name));
} else {
EXPECT_EQ(0u, info.num_extra_channels);
}
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
} else { // If we did not give the full header, the basic info should not be // available. Allow a few bytes of slack due to some bits for default // opsinmatrix/extension bits. if (size + 2 < data.size()) {
EXPECT_EQ(false, have_basic_info);
EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, status);
}
}
// Test that decoder doesn't allow setting a setting required at beginning // unless it's reset
EXPECT_EQ(JXL_DEC_ERROR,
JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO));
JxlDecoderReset(dec);
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO));
TEST(DecodeTest, BasicInfoSizeHintTest) { // Test on a file where the size hint is too small initially due to inserting // a box before the codestream (something that is normally not recommended)
size_t xsize = 50;
size_t ysize = 50;
size_t bits_per_sample = 16;
size_t orientation = 1;
size_t alpha_bits = 0; bool xyb_encoded = false;
std::vector<uint8_t> data = GetTestHeader(
xsize, ysize, bits_per_sample, orientation, alpha_bits, xyb_encoded, /*have_container=*/true, /*metadata_default=*/false, /*insert_extra_box=*/true, {});
JxlDecoderStatus status;
JxlDecoder* dec = JxlDecoderCreate(nullptr);
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO));
size_t hint0 = JxlDecoderSizeHintBasicInfo(dec); // Test that the test works as intended: we construct a file on purpose to // be larger than the first hint by having that extra box.
EXPECT_LT(hint0, data.size()); const uint8_t* next_in = data.data(); // Do as if we have only as many bytes as indicated by the hint available
size_t avail_in = std::min(hint0, data.size());
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in));
status = JxlDecoderProcessInput(dec);
EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, status); // Basic info cannot be available yet due to the extra inserted box.
EXPECT_EQ(false, !JxlDecoderGetBasicInfo(dec, nullptr));
size_t hint1 = JxlDecoderSizeHintBasicInfo(dec); // The hint must be larger than the previous hint (taking already processed // bytes into account, the hint is a hint for the next avail_in) since the // decoder now knows there is a box in between.
EXPECT_GT(hint1 + num_read, hint0);
avail_in = std::min<size_t>(hint1, data.size() - num_read);
next_in += num_read;
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in));
status = JxlDecoderProcessInput(dec);
EXPECT_EQ(JXL_DEC_BASIC_INFO, status);
JxlBasicInfo info; // We should have the basic info now, since we only added one box in-between, // and the decoder should have known its size, its implementation can return // a correct hint.
EXPECT_EQ(true, !JxlDecoderGetBasicInfo(dec, &info));
// Also test if the basic info is correct.
EXPECT_EQ(1, info.have_container);
EXPECT_EQ(xsize, info.xsize);
EXPECT_EQ(ysize, info.ysize);
EXPECT_EQ(orientation, info.orientation);
EXPECT_EQ(bits_per_sample, info.bits_per_sample);
// Tests the case where pixels and metadata ICC profile are the same
TEST(DecodeTest, IccProfileTestOriginal) {
jxl::IccBytes icc_profile = GetIccTestProfile(); bool xyb_encoded = false;
std::vector<uint8_t> data = GetIccTestHeader(icc_profile, xyb_encoded);
// Expect the opposite of xyb_encoded for uses_original_profile
JxlBasicInfo info;
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
EXPECT_EQ(JXL_TRUE, info.uses_original_profile);
// the encoded color profile expected to be not available, since the image // has an ICC profile instead
EXPECT_EQ(JXL_DEC_ERROR,
JxlDecoderGetColorAsEncodedProfile(
dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL, nullptr));
// Check that can get return status with NULL size
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL,
nullptr));
// The profiles must be equal. This requires they have equal size, and if // they do, we can get the profile and compare the contents.
EXPECT_EQ(icc_profile.size(), dec_profile_size); if (icc_profile.size() == dec_profile_size) {
jxl::IccBytes icc_profile2(icc_profile.size());
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile(
dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL,
icc_profile2.data(), icc_profile2.size()));
EXPECT_EQ(icc_profile, icc_profile2);
}
// the data is not xyb_encoded, so same result expected for the pixel data // color profile
EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderGetColorAsEncodedProfile(
dec, JXL_COLOR_PROFILE_TARGET_DATA, nullptr));
// Tests the case where pixels and metadata ICC profile are different
TEST(DecodeTest, IccProfileTestXybEncoded) {
jxl::IccBytes icc_profile = GetIccTestProfile(); bool xyb_encoded = true;
std::vector<uint8_t> data = GetIccTestHeader(icc_profile, xyb_encoded);
JxlDecoder* dec = JxlDecoderCreate(nullptr);
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSubscribeEvents(
dec, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING));
// Expect the opposite of xyb_encoded for uses_original_profile
JxlBasicInfo info;
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
EXPECT_EQ(JXL_FALSE, info.uses_original_profile);
// the encoded color profile expected to be not available, since the image // has an ICC profile instead
EXPECT_EQ(JXL_DEC_ERROR,
JxlDecoderGetColorAsEncodedProfile(
dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL, nullptr));
// Check that can get return status with NULL size
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL,
nullptr));
// The profiles must be equal. This requires they have equal size, and if // they do, we can get the profile and compare the contents.
EXPECT_EQ(icc_profile.size(), dec_profile_size); if (icc_profile.size() == dec_profile_size) {
jxl::IccBytes icc_profile2(icc_profile.size());
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile(
dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL,
icc_profile2.data(), icc_profile2.size()));
EXPECT_EQ(icc_profile, icc_profile2);
}
// Data is xyb_encoded, so the data profile is a different profile, encoded // as structured profile.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsEncodedProfile(
dec, JXL_COLOR_PROFILE_TARGET_DATA, nullptr));
JxlColorEncoding pixel_encoding;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetColorAsEncodedProfile(
dec, JXL_COLOR_PROFILE_TARGET_DATA, &pixel_encoding));
EXPECT_EQ(JXL_PRIMARIES_SRGB, pixel_encoding.primaries); // The API returns LINEAR by default when the colorspace cannot be represented // by enum values.
EXPECT_EQ(JXL_TRANSFER_FUNCTION_LINEAR, pixel_encoding.transfer_function);
// Test the same but with integer format.
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetColorAsEncodedProfile(
dec, JXL_COLOR_PROFILE_TARGET_DATA, &pixel_encoding));
EXPECT_EQ(JXL_PRIMARIES_SRGB, pixel_encoding.primaries);
EXPECT_EQ(JXL_TRANSFER_FUNCTION_LINEAR, pixel_encoding.transfer_function);
// Test after setting the preferred color profile to non-linear sRGB: // for XYB images with ICC profile, this setting is expected to take effect.
jxl::ColorEncoding temp_jxl_srgb = jxl::ColorEncoding::SRGB(false);
JxlColorEncoding pixel_encoding_srgb = temp_jxl_srgb.ToExternal();
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetPreferredColorProfile(dec, &pixel_encoding_srgb));
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetColorAsEncodedProfile(
dec, JXL_COLOR_PROFILE_TARGET_DATA, &pixel_encoding));
EXPECT_EQ(JXL_TRANSFER_FUNCTION_SRGB, pixel_encoding.transfer_function);
// The decoder can also output this as a generated ICC profile anyway, and // we're certain that it will differ from the above defined profile since // the sRGB data should not have swapped R/G/B primaries.
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA,
&dec_profile_size)); // We don't need to dictate exactly what size the generated ICC profile // must be (since there are many ways to represent the same color space), // but it should not be zero.
EXPECT_NE(0u, dec_profile_size);
jxl::IccBytes icc_profile2(dec_profile_size); if (0 != dec_profile_size) {
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile(
dec, JXL_COLOR_PROFILE_TARGET_DATA,
icc_profile2.data(), icc_profile2.size())); // expected not equal
EXPECT_NE(icc_profile, icc_profile2);
}
// Test setting another different preferred profile, to verify that the // returned JXL_COLOR_PROFILE_TARGET_DATA ICC profile is correctly // updated.
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetPreferredColorProfile(dec, &pixel_encoding_linear));
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetColorAsEncodedProfile(
dec, JXL_COLOR_PROFILE_TARGET_DATA, &pixel_encoding));
EXPECT_EQ(JXL_TRANSFER_FUNCTION_LINEAR, pixel_encoding.transfer_function);
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA,
&dec_profile_size));
EXPECT_NE(0u, dec_profile_size);
jxl::IccBytes icc_profile3(dec_profile_size); if (0 != dec_profile_size) {
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile(
dec, JXL_COLOR_PROFILE_TARGET_DATA,
icc_profile3.data(), icc_profile3.size())); // expected not equal to the previously set preferred profile.
EXPECT_NE(icc_profile2, icc_profile3);
}
JxlDecoderDestroy(dec);
}
// Test decoding ICC from partial files byte for byte. // This test must pass also if JXL_CRASH_ON_ERROR is enabled, that is, the // decoding of the ANS histogram and stream of the encoded ICC profile must also // handle the case of not enough input bytes with StatusCode::kNotEnoughBytes // rather than fatal error status codes.
TEST(DecodeTest, ICCPartialTest) {
jxl::IccBytes icc_profile = GetIccTestProfile();
std::vector<uint8_t> data = GetIccTestHeader(icc_profile, false);
for (;;) {
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in));
JxlDecoderStatus status = JxlDecoderProcessInput(dec);
size_t remaining = JxlDecoderReleaseInput(dec);
EXPECT_LE(remaining, avail_in);
next_in += avail_in - remaining;
avail_in = remaining; if (status == JXL_DEC_NEED_MORE_INPUT) { if (total_size >= data.size()) { // End of partial codestream with codestrema headers and ICC profile // reached, it should not require more input since full image is not // requested
FAIL(); break;
}
size_t increment = 1; if (total_size + increment > data.size()) {
increment = data.size() - total_size;
}
total_size += increment;
avail_in += increment;
} elseif (status == JXL_DEC_BASIC_INFO) {
EXPECT_FALSE(seen_basic_info);
seen_basic_info = true;
} elseif (status == JXL_DEC_COLOR_ENCODING) {
EXPECT_TRUE(seen_basic_info);
EXPECT_FALSE(seen_color_encoding);
seen_color_encoding = true;
// Sanity check that the ICC profile was decoded correctly
size_t dec_profile_size;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetICCProfileSize(
dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL, &dec_profile_size));
EXPECT_EQ(icc_profile.size(), dec_profile_size);
} elseif (status == JXL_DEC_SUCCESS) {
EXPECT_TRUE(seen_color_encoding); break;
} else { // We do not expect any other events or errors
FAIL(); break;
}
}
// If an orientation transformation is expected, to compare the pixels, also // apply this transformation to the original pixels. ConvertToExternal is // used to achieve this, with a temporary conversion to CodecInOut and back. if (config.orientation > 1 && !config.keep_orientation) {
jxl::Span<const uint8_t> bytes(pixels.data(), pixels.size());
jxl::ColorEncoding color_encoding =
jxl::ColorEncoding::SRGB(config.grayscale);
jxl::CodecInOut io{jxl::test::MemoryManager()}; if (config.include_alpha) io.metadata.m.SetAlphaBits(16);
io.metadata.m.color_encoding = color_encoding;
ASSERT_TRUE(io.SetSize(config.xsize, config.ysize));
for (uint8_t& pixel : pixels) pixel = 0;
EXPECT_TRUE(ConvertToExternal(
io.Main(), 16, /*float_out=*/false, orig_channels, JXL_BIG_ENDIAN,
xsize * 2 * orig_channels, nullptr, pixels.data(), pixels.size(), /*out_callback=*/{}, static_cast<jxl::Orientation>(config.orientation)));
} if (config.upsampling == 1) {
EXPECT_EQ(0u, jxl::test::ComparePixels(pixels.data(), pixels2.data(), xsize,
ysize, format_orig, format));
} else { // resampling is of course not lossless, so as a rough check: // count pixels that are more than off-by-25 in the 8-bit value of one of // the channels
EXPECT_LE(
jxl::test::ComparePixels(
pixels.data(), pixels2.data(), xsize, ysize, format_orig, format,
50.0 * (config.data_type == JXL_TYPE_UINT8 ? 1.0 : 256.0)),
300u);
}
// Test using the resizable runner for (size_t i = 0; i < 4; i++) {
make_test(ch_info[0], 300 << i, 33 << i, jxl::kNoPreview, /*add_intrinsic_size=*/false, CodeStreamBoxFormat::kCSBF_None,
JXL_ORIENT_IDENTITY, /*keep_orientation=*/false, out_formats[0], /*use_callback=*/false, /*set_buffer_early=*/false, /*resizable_runner=*/true, 1);
}
// Test orientations. for (int orientation = 2; orientation <= 8; ++orientation) { for (bool keep_orientation : {false, true}) { for (bool use_callback : {false, true}) { for (ChannelInfo ch : ch_info) { for (OutputFormat fmt : out_formats) {
make_test(ch, 280, 12, jxl::kNoPreview, /*add_intrinsic_size=*/false,
CodeStreamBoxFormat::kCSBF_None, static_cast<JxlOrientation>(orientation), /*keep_orientation=*/keep_orientation, fmt, /*use_callback=*/use_callback, /*set_buffer_early=*/true, /*resizable_runner=*/false, 1);
}
}
}
}
}
return all_tests;
}
std::ostream& operator<<(std::ostream& os, const PixelTestConfig& c) {
os << c.xsize << "x" << c.ysize; constchar* colors[] = {"", "G", "GA", "RGB", "RGBA"};
os << colors[(c.grayscale ? 1 : 3) + (c.include_alpha ? 1 : 0)];
os << "to";
os << colors[c.output_channels]; switch (c.data_type) { case JXL_TYPE_UINT8:
os << "u8"; break; case JXL_TYPE_UINT16:
os << "u16"; break; case JXL_TYPE_FLOAT:
os << "f32"; break; case JXL_TYPE_FLOAT16:
os << "f16"; break; default:
ADD_FAILURE();
}; if (jxl::test::GetDataBits(c.data_type) > jxl::kBitsPerByte) { if (c.endianness == JXL_NATIVE_ENDIAN) { // add nothing
} elseif (c.endianness == JXL_BIG_ENDIAN) {
os << "BE";
} elseif (c.endianness == JXL_LITTLE_ENDIAN) {
os << "LE";
}
} if (c.add_container != CodeStreamBoxFormat::kCSBF_None) {
os << "Box";
os << static_cast<size_t>(c.add_container);
} if (c.preview_mode == jxl::kSmallPreview) os << "Preview"; if (c.preview_mode == jxl::kBigPreview) os << "BigPreview"; if (c.add_intrinsic_size) os << "IntrinicSize"; if (c.use_callback) os << "Callback"; if (c.set_buffer_early) os << "EarlyBuffer"; if (c.use_resizable_runner) os << "ResizableRunner"; if (c.orientation != 1) os << "O" << c.orientation; if (c.keep_orientation) os << "Keep"; if (c.upsampling > 1) os << "x" << c.upsampling; return os;
}
// Test with the container for one of the pixel formats.
std::vector<uint8_t> pixels2 = jxl::DecodeWithAPI(
dec, jxl::Bytes(compressed.data(), compressed.size()), format, /*use_callback=*/true, /*set_buffer_early=*/true, /*use_resizable_runner=*/false, /*require_boxes=*/false, /*expect_success=*/true);
JxlDecoderReset(dec);
EXPECT_EQ(num_pixels * channels * 2, pixels2.size());
EXPECT_EQ(0u,
jxl::test::ComparePixels(pixels.data(), pixels2.data(), xsize,
ysize, format_orig, format));
}
{
JxlPixelFormat format = {channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, 0};
// The input pixels use the profile matching GetIccTestProfile, since we set // add_icc_profile for CreateTestJXLCodestream to true.
jxl::ColorEncoding color_encoding0;
EXPECT_TRUE(color_encoding0.SetICC(GetIccTestProfile(), JxlGetDefaultCms()));
jxl::Span<const uint8_t> span0(pixels.data(), pixels.size());
jxl::CodecInOut io0{memory_manager};
ASSERT_TRUE(io0.SetSize(xsize, ysize));
EXPECT_TRUE(ConvertFromExternal(span0, xsize, ysize, color_encoding0, /*bits_per_sample=*/16, format_orig, /*pool=*/nullptr, &io0.Main()));
// Tests the case of lossy sRGB image without alpha channel, decoded to RGB8 // and to RGBA8
TEST(DecodeTest, PixelTestOpaqueSrgbLossy) {
JxlMemoryManager* memory_manager = jxl::test::MemoryManager(); for (unsigned channels = 3; channels <= 4; channels++) {
JxlDecoder* dec = JxlDecoderCreate(nullptr);
std::vector<std::vector<uint8_t>> codestreams(kCSBF_NUM_ENTRIES);
std::vector<std::vector<uint8_t>> jpeg_codestreams(kCSBF_NUM_ENTRIES); for (size_t i = 0; i < kCSBF_NUM_ENTRIES; ++i) {
params.box_format = static_cast<CodeStreamBoxFormat>(i); if (reconstructible_jpeg) {
params.jpeg_codestream = &jpeg_codestreams[i];
}
codestreams[i] =
jxl::CreateTestJXLCodestream(jxl::Bytes(pixels.data(), pixels.size()),
xsize, ysize, channels, params);
}
// Test multiple step sizes, to test different combinations of the streaming // box parsing.
std::vector<size_t> increments = {1, 3, 17, 23, 120, 700, 1050};
for (size_t increment : increments) { for (size_t i = 0; i < kCSBF_NUM_ENTRIES; ++i) { if (reconstructible_jpeg && static_cast<CodeStreamBoxFormat>(i) ==
CodeStreamBoxFormat::kCSBF_None) { continue;
} const std::vector<uint8_t>& data = codestreams[i]; const uint8_t* next_in = data.data();
size_t avail_in = 0;
for (;;) {
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in));
JxlDecoderStatus status = JxlDecoderProcessInput(dec);
size_t remaining = JxlDecoderReleaseInput(dec);
EXPECT_LE(remaining, avail_in);
next_in += avail_in - remaining;
avail_in = remaining; if (status == JXL_DEC_NEED_MORE_INPUT) { if (total_size >= data.size()) { // End of test data reached, it should have successfully decoded the // image now.
FAIL(); break;
}
// End of the file reached, should be the final test. if (total_size + increment > data.size()) {
increment = data.size() - total_size;
}
total_size += increment;
avail_in += increment;
} elseif (status == JXL_DEC_BASIC_INFO) { // This event should happen exactly once
EXPECT_FALSE(seen_basic_info); if (seen_basic_info) break;
seen_basic_info = true;
JxlBasicInfo info;
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info));
EXPECT_EQ(info.xsize, xsize);
EXPECT_EQ(info.ysize, ysize);
} elseif (status == JXL_DEC_JPEG_RECONSTRUCTION) {
EXPECT_FALSE(seen_basic_info);
EXPECT_FALSE(seen_full_image);
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetJPEGBuffer(dec, jpeg_output.data(),
jpeg_output.size()));
seen_jpeg_recon = true;
} elseif (status == JXL_DEC_JPEG_NEED_MORE_OUTPUT) {
EXPECT_TRUE(seen_jpeg_recon);
used_jpeg_output =
jpeg_output.size() - JxlDecoderReleaseJPEGBuffer(dec);
jpeg_output.resize(jpeg_output.size() * 2);
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetJPEGBuffer(
dec, jpeg_output.data() + used_jpeg_output,
jpeg_output.size() - used_jpeg_output));
} elseif (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetImageOutBuffer(
dec, &format_orig, pixels2.data(), pixels2.size()));
} elseif (status == JXL_DEC_FULL_IMAGE) { // This event should happen exactly once
EXPECT_FALSE(seen_full_image); if (seen_full_image) break; // This event should happen after basic info
EXPECT_TRUE(seen_basic_info);
seen_full_image = true; if (reconstructible_jpeg) {
used_jpeg_output =
jpeg_output.size() - JxlDecoderReleaseJPEGBuffer(dec);
EXPECT_EQ(used_jpeg_output, jpeg_codestreams[i].size());
EXPECT_EQ(0, memcmp(jpeg_output.data(), jpeg_codestreams[i].data(),
used_jpeg_output));
} else {
EXPECT_EQ(pixels, pixels2);
}
} elseif (status == JXL_DEC_SUCCESS) {
EXPECT_TRUE(seen_full_image); break;
} else { // We do not expect any other events or errors
FAIL(); break;
}
}
// Ensure the decoder emitted the basic info and full image events
EXPECT_TRUE(seen_basic_info);
EXPECT_TRUE(seen_full_image);
JxlDecoderDestroy(dec);
}
}
}
// Tests the return status when trying to decode pixels on incomplete file: it // should return JXL_DEC_NEED_MORE_INPUT, not error.
TEST(DecodeTest, PixelPartialTest) { TestPartialStream(false); }
// Tests the return status when trying to decode JPEG bytes on incomplete file.
JXL_TRANSCODE_JPEG_TEST(DecodeTest, JPEGPartialTest) {
TEST_LIBJPEG_SUPPORT();
TestPartialStream(true);
}
// The DC event still exists, but is no longer implemented, it is deprecated.
TEST(DecodeTest, DCNotGettableTest) { // 1x1 pixel JXL image
std::string compressed( "\377\n\0\20\260\23\0H\200(" "\0\334\0U\17\0\0\250P\31e\334\340\345\\\317\227\37:," "\246m\\gh\253m\vK\22E\306\261I\252C&pH\22\353 " "\363\6\22\bp\0\200\237\34\231W2d\255$\1",
68);
// Since the image is only 1x1 pixel, there is only 1 group, the decoder is // unable to get DC size from this, and will not return the DC at all. Since // no full image is requested either, it is expected to return success.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
jxl::ButteraugliParams butteraugli_params; // TODO(lode): this ButteraugliDistance silently returns 0 (dangerous for // tests) if xsize or ysize is < 8, no matter how different the images, a // tiny size that could happen for a preview. ButteraugliDiffmap does // support smaller than 8x8, but jxl's ButteraugliDistance does not. Perhaps // move butteraugli's <8x8 handling from ButteraugliDiffmap to // ButteraugliComparator::Diffmap in butteraugli.cc.
EXPECT_LE(ButteraugliDistance(io0.frames, io1.frames, butteraugli_params,
*JxlGetDefaultCms(), /*distmap=*/nullptr, nullptr),
mode == jxl::kSmallPreview ? 0.7f : 1.2f);
JxlFrameHeader frame_header;
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetFrameHeader(dec, &frame_header));
EXPECT_EQ(frame_durations[i], frame_header.duration);
EXPECT_EQ(0u, frame_header.name_length); // For now, test with empty name, there's currently no easy way to encode // a jxl file with a frame name because ImageBundle doesn't have a // jxl::FrameHeader to set the name in. We can test the null termination // character though. char name;
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetFrameName(dec, &name, 1));
EXPECT_EQ(0, name);
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
EXPECT_EQ(0u, jxl::test::ComparePixels(frames[i].data(), pixels.data(),
xsize, ysize, format, format));
}
// After all frames were decoded, JxlDecoderProcessInput should return // success to indicate all is done.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
// After the full image was output, JxlDecoderProcessInput should return // success to indicate all is done.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
JxlDecoderDestroy(dec);
std::vector<uint32_t> frame_durations(num_frames); for (size_t i = 0; i < num_frames; ++i) {
frame_durations[i] = 5 + i;
}
for (size_t i = 0; i < num_frames; ++i) {
jxl::ImageBundle bundle(memory_manager, &io.metadata.m); if (i & 1) { // Mark some frames as referenceable, others not.
bundle.use_for_next_frame = true;
}
for (size_t i = 0; i < num_frames; ++i) {
printf("Decoding frame %d\n", static_cast<int>(i));
EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderSkipCurrentFrame(dec));
std::vector<uint8_t> pixels(buffer_size);
EXPECT_EQ(JXL_DEC_FRAME, JxlDecoderProcessInput(dec));
EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderSkipCurrentFrame(dec));
JxlFrameHeader frame_header;
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetFrameHeader(dec, &frame_header));
EXPECT_EQ(frame_durations[i], frame_header.duration);
EXPECT_EQ(i + 1 == num_frames, frame_header.is_last);
EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(
dec, &format, pixels.data(), pixels.size())); if (i == 2) {
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSkipCurrentFrame(dec)); continue;
}
EXPECT_EQ(JXL_DEC_FRAME_PROGRESSION, JxlDecoderProcessInput(dec));
EXPECT_EQ(8, JxlDecoderGetIntendedDownsamplingRatio(dec)); if (i == 3) {
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSkipCurrentFrame(dec)); continue;
}
EXPECT_EQ(JXL_DEC_FRAME_PROGRESSION, JxlDecoderProcessInput(dec));
EXPECT_EQ(4, JxlDecoderGetIntendedDownsamplingRatio(dec)); if (i == 4) {
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSkipCurrentFrame(dec)); continue;
}
EXPECT_EQ(JXL_DEC_FRAME_PROGRESSION, JxlDecoderProcessInput(dec));
EXPECT_EQ(2, JxlDecoderGetIntendedDownsamplingRatio(dec)); if (i == 5) {
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSkipCurrentFrame(dec)); continue;
}
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderSkipCurrentFrame(dec));
}
// After all frames were decoded, JxlDecoderProcessInput should return // success to indicate all is done.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
std::vector<uint32_t> frame_durations(num_frames); for (size_t i = 0; i < num_frames; ++i) {
frame_durations[i] = 5 + i;
}
for (size_t i = 0; i < num_frames; ++i) {
jxl::ImageBundle bundle(memory_manager, &io.metadata.m); if (i & 1) { // Mark some frames as referenceable, others not.
bundle.use_for_next_frame = true;
}
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
EXPECT_EQ(0u, jxl::test::ComparePixels(frames[i].data(), pixels.data(),
xsize, ysize, format, format));
}
// After all frames were decoded, JxlDecoderProcessInput should return // success to indicate all is done.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
// Test rewinding the decoder and skipping different frames
// Since this is after JXL_DEC_FRAME but before JXL_DEC_FULL_IMAGE, this // should only skip the next frame, not the currently processed one. if (test_skipping) JxlDecoderSkipFrames(dec, test_skipping);
for (size_t i = 0; i < num_frames; ++i) { if (i < 5) {
std::vector<uint8_t> frame_internal =
jxl::test::GetSomeTestImage(xsize, ysize, 3, i * 2 + 1); // An internal frame with 0 duration, and use_for_next_frame, this is a // frame that is not rendered and not output by the API, but on which the // rendered frames depend
jxl::ImageBundle bundle_internal(memory_manager, &io.metadata.m);
EXPECT_TRUE(ConvertFromExternal(
jxl::Bytes(frame_internal.data(), frame_internal.size()), xsize,
ysize, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*bits_per_sample=*/16, format, /*pool=*/nullptr, &bundle_internal));
bundle_internal.duration = 0;
bundle_internal.use_for_next_frame = true;
io.frames.push_back(std::move(bundle_internal));
}
std::vector<uint8_t> frame =
jxl::test::GetSomeTestImage(xsize, ysize, 3, i * 2); // Actual rendered frame
frame_durations[i] = 5 + i;
jxl::ImageBundle bundle(memory_manager, &io.metadata.m);
EXPECT_TRUE(ConvertFromExternal(jxl::Bytes(frame.data(), frame.size()),
xsize, ysize,
jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*bits_per_sample=*/16, format, /*pool=*/nullptr, &bundle));
bundle.duration = frame_durations[i]; // Create some variation in which frames depend on which. if (i != 3 && i != 9 && i != 10) {
bundle.use_for_next_frame = true;
} if (i != 12) {
bundle.blend = true; // Choose a blend mode that depends on the pixels of the saved frame and // doesn't use alpha
bundle.blendmode = jxl::BlendMode::kMul;
}
io.frames.push_back(std::move(bundle));
}
jxl::CompressParams cparams;
cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip.
cparams.speed_tier = jxl::SpeedTier::kThunder;
std::vector<uint8_t> compressed;
EXPECT_TRUE(jxl::test::EncodeFile(cparams, &io, &compressed));
// Independently decode all frames without any skipping, to create the // expected blended frames, for the actual tests below to compare with.
{
JxlDecoder* dec = JxlDecoderCreate(nullptr); const uint8_t* next_in = compressed.data();
size_t avail_in = compressed.size();
// After all frames were decoded, JxlDecoderProcessInput should return // success to indicate all is done.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
JxlThreadParallelRunnerDestroy(runner);
JxlDecoderDestroy(dec);
}
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
EXPECT_EQ(0u, jxl::test::ComparePixels(frames[i].data(), pixels.data(),
xsize, ysize, format, format));
}
// After all frames were decoded, JxlDecoderProcessInput should return // success to indicate all is done.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
// Test rewinding the decoder and skipping different frames
// Since this is after JXL_DEC_FRAME but before JXL_DEC_FULL_IMAGE, this // should only skip the next frame, not the currently processed one. if (test_skipping) JxlDecoderSkipFrames(dec, test_skipping);
for (size_t i = 0; i < num_frames; ++i) {
size_t cropxsize = 1 + xsize * 2 / (i + 1);
size_t cropysize = 1 + ysize * 3 / (i + 2); int cropx0 = i * 3 - 8; int cropy0 = i * 4 - 7; if (i < 5) {
std::vector<uint8_t> frame_internal =
jxl::test::GetSomeTestImage(xsize / 2, ysize / 2, 4, i * 2 + 1); // An internal frame with 0 duration, and use_for_next_frame, this is a // frame that is not rendered and not output by default by the API, but on // which the rendered frames depend
jxl::ImageBundle bundle_internal(memory_manager, &io.metadata.m);
EXPECT_TRUE(ConvertFromExternal(
jxl::Bytes(frame_internal.data(), frame_internal.size()), xsize / 2,
ysize / 2, jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*bits_per_sample=*/16, format, /*pool=*/nullptr, &bundle_internal));
bundle_internal.duration = 0;
bundle_internal.use_for_next_frame = true;
bundle_internal.origin = {13, 17};
io.frames.push_back(std::move(bundle_internal));
frame_durations_nc.push_back(0);
frame_xsize.push_back(xsize / 2);
frame_ysize.push_back(ysize / 2);
frame_x0.push_back(13);
frame_y0.push_back(17);
}
std::vector<uint8_t> frame =
jxl::test::GetSomeTestImage(cropxsize, cropysize, 4, i * 2); // Actual rendered frame
jxl::ImageBundle bundle(memory_manager, &io.metadata.m);
EXPECT_TRUE(ConvertFromExternal(jxl::Bytes(frame.data(), frame.size()),
cropxsize, cropysize,
jxl::ColorEncoding::SRGB(/*is_gray=*/false), /*bits_per_sample=*/16, format, /*pool=*/nullptr, &bundle));
bundle.duration = 5 + i;
frame_durations_nc.push_back(5 + i);
frame_durations_c.push_back(5 + i);
frame_xsize.push_back(cropxsize);
frame_ysize.push_back(cropysize);
frame_x0.push_back(cropx0);
frame_y0.push_back(cropy0);
bundle.origin = {cropx0, cropy0}; // Create some variation in which frames depend on which. if (i != 3 && i != 9 && i != 10) {
bundle.use_for_next_frame = true;
} if (i != 12) {
bundle.blend = true;
bundle.blendmode = jxl::BlendMode::kBlend;
}
io.frames.push_back(std::move(bundle));
}
jxl::CompressParams cparams;
cparams.SetLossless(); // Lossless to verify pixels exactly after roundtrip.
cparams.speed_tier = jxl::SpeedTier::kThunder;
std::vector<uint8_t> compressed;
EXPECT_TRUE(jxl::test::EncodeFile(cparams, &io, &compressed)); // try both with and without coalescing for (auto coalescing : {JXL_TRUE, JXL_FALSE}) { // Independently decode all frames without any skipping, to create the // expected blended frames, for the actual tests below to compare with.
{
JxlDecoder* dec = JxlDecoderCreate(nullptr); const uint8_t* next_in = compressed.data();
size_t avail_in = compressed.size();
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetCoalescing(dec, coalescing)); void* runner = JxlThreadParallelRunnerCreate(
nullptr, JxlThreadParallelRunnerDefaultNumWorkerThreads());
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetParallelRunner(
dec, JxlThreadParallelRunner, runner));
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSubscribeEvents(dec, JXL_DEC_FULL_IMAGE));
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in)); for (size_t i = 0; i < num_frames + (coalescing ? 0 : 5); ++i) {
EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
size_t buffer_size;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderImageOutBufferSize(dec, &format, &buffer_size)); if (coalescing) {
EXPECT_EQ(xsize * ysize * 8, buffer_size);
} else {
EXPECT_EQ(frame_xsize[i] * frame_ysize[i] * 8, buffer_size);
}
frames[i].resize(buffer_size);
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetImageOutBuffer(dec, &format, frames[i].data(),
frames[i].size()));
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
}
// After all frames were decoded, JxlDecoderProcessInput should return // success to indicate all is done.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
JxlThreadParallelRunnerDestroy(runner);
JxlDecoderDestroy(dec);
}
// After all frames were decoded, JxlDecoderProcessInput should return // success to indicate all is done.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
// Test rewinding the decoder and skipping different frames
// Since this is after JXL_DEC_FRAME but before JXL_DEC_FULL_IMAGE, this // should only skip the next frame, not the currently processed one. if (test_skipping) JxlDecoderSkipFrames(dec, test_skipping);
// 0 is merged frame as decoded with coalescing enabled (default) // 1-3 are non-coalesced frames as decoded with coalescing disabled // 4 is the manually merged frame
std::vector<uint8_t> frames[5];
frames[4].resize(xsize * ysize * 8, 0);
// try both with and without coalescing for (auto coalescing : {JXL_TRUE, JXL_FALSE}) { // Independently decode all frames without any skipping, to create the // expected blended frames, for the actual tests below to compare with.
{
JxlDecoder* dec = JxlDecoderCreate(nullptr); const uint8_t* next_in = compressed.data();
size_t avail_in = compressed.size();
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetCoalescing(dec, coalescing));
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetKeepOrientation(dec, keep_orientation)); void* runner = JxlThreadParallelRunnerCreate(
nullptr, JxlThreadParallelRunnerDefaultNumWorkerThreads());
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetParallelRunner(
dec, JxlThreadParallelRunner, runner));
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSubscribeEvents(dec, JXL_DEC_FULL_IMAGE));
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in)); for (size_t i = (coalescing ? 0 : 1); i < (coalescing ? 1 : 4); ++i) {
EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec));
JxlFrameHeader frame_header;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderGetFrameHeader(dec, &frame_header));
size_t buffer_size;
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderImageOutBufferSize(dec, &format, &buffer_size)); if (coalescing) {
EXPECT_EQ(xsize * ysize * 8, buffer_size);
} else {
EXPECT_EQ(frame_header.layer_info.xsize *
frame_header.layer_info.ysize * 8,
buffer_size);
}
frames[i].resize(buffer_size);
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetImageOutBuffer(dec, &format, frames[i].data(),
frames[i].size()));
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
EXPECT_EQ(frame_header.layer_info.blend_info.blendmode,
JXL_BLEND_REPLACE); if (coalescing) {
EXPECT_EQ(frame_header.layer_info.xsize, oxsize);
EXPECT_EQ(frame_header.layer_info.ysize, oysize);
EXPECT_EQ(frame_header.layer_info.crop_x0, 0);
EXPECT_EQ(frame_header.layer_info.crop_y0, 0);
} else { // manually merge this layer int x0 = frame_header.layer_info.crop_x0; int y0 = frame_header.layer_info.crop_y0; int w = frame_header.layer_info.xsize; int h = frame_header.layer_info.ysize; for (int y = 0; y < static_cast<int>(oysize); y++) { if (y < y0 || y >= y0 + h) continue; // pointers do whole 16-bit RGBA pixels at a time
uint64_t* row_merged = reinterpret_cast<uint64_t*>(
frames[4].data() + y * oxsize * 8);
uint64_t* row_layer = reinterpret_cast<uint64_t*>(
frames[i].data() + (y - y0) * w * 8); for (int x = 0; x < static_cast<int>(oxsize); x++) { if (x < x0 || x >= x0 + w) continue;
row_merged[x] = row_layer[x - x0];
}
}
}
}
// After all frames were decoded, JxlDecoderProcessInput should return // success to indicate all is done.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
JxlThreadParallelRunnerDestroy(runner);
JxlDecoderDestroy(dec);
}
}
EXPECT_EQ(0u, jxl::test::ComparePixels(frames[0].data(), frames[4].data(),
oxsize, oysize, format, format));
};
// Must process input further until we get JXL_DEC_NEED_MORE_INPUT, even if // data was already input before, since the processing of the frame only // happens at the JxlDecoderProcessInput call after JXL_DEC_FRAME.
EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, JxlDecoderProcessInput(dec));
// Crude test of actual pixel data: pixel threshold of about 4% (2560/65535). // 29000 pixels can be above the threshold
EXPECT_LE(jxl::test::ComparePixels(pixels2.data(), pixels.data(), xsize,
ysize, format, format, 2560.0),
29000u);
// Output callback not yet set
EXPECT_EQ(JXL_DEC_ERROR, JxlDecoderFlushImage(dec));
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutCallback(
dec, &format,
[](void* opaque, size_t x, size_t y,
size_t xsize, constvoid* pixels_row) { auto cb = static_cast<decltype(&callback)>(opaque);
(*cb)(x, y, xsize, pixels_row);
}, /*opaque=*/&callback));
// Must process input further until we get JXL_DEC_NEED_MORE_INPUT, even if // data was already input before, since the processing of the frame only // happens at the JxlDecoderProcessInput call after JXL_DEC_FRAME.
EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, JxlDecoderProcessInput(dec));
// Crude test of actual pixel data: pixel threshold of about 4% (2560/65535). // 29000 pixels can be above the threshold
EXPECT_LE(jxl::test::ComparePixels(pixels2.data(), pixels.data(), xsize,
ysize, format, format, 2560.0),
29000u);
// Must process input further until we get JXL_DEC_NEED_MORE_INPUT, even if // data was already input before, since the processing of the frame only // happens at the JxlDecoderProcessInput call after JXL_DEC_FRAME.
EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, JxlDecoderProcessInput(dec));
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
EXPECT_LE(jxl::test::ComparePixels(pixels2.data(), pixels.data(), xsize,
ysize, format, format, 2560.0),
11000u);
// Must process input further until we get JXL_DEC_NEED_MORE_INPUT, even if // data was already input before, since the processing of the frame only // happens at the JxlDecoderProcessInput call after JXL_DEC_FRAME.
EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, JxlDecoderProcessInput(dec));
EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec));
EXPECT_LE(jxl::test::ComparePixels(pixels2.data(), pixels.data(), xsize,
ysize, format, format, 2560.0),
70000u);
// Must process input further until we get JXL_DEC_NEED_MORE_INPUT, even if // data was already input before, since the processing of the frame only // happens at the JxlDecoderProcessInput call after JXL_DEC_FRAME.
EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, JxlDecoderProcessInput(dec));
auto next_pass = [&](int pass) { if (prog_detail <= kDC) return kNumPasses; if (prog_detail <= kLastPasses) { return std::min(pass + 2, kNumPasses);
} return pass + 1;
};
if (expect_flush) { // Return a particular downsampling ratio only after the last // pass for that downsampling was processed. int expected_downsampling_ratios[] = {8, 8, 4, 4, 2}; for (int p = 0; p < kNumPasses; p = next_pass(p)) {
EXPECT_EQ(JXL_DEC_FRAME_PROGRESSION, process_input());
EXPECT_EQ(expected_downsampling_ratios[p],
JxlDecoderGetIntendedDownsamplingRatio(dec));
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderFlushImage(dec));
passes[p] = passes[kNumPasses];
}
}
if (!expect_flush) { continue;
}
jxl::ButteraugliParams butteraugli_params;
std::vector<float> distances(kNumPasses + 1); for (int p = 0;; p = next_pass(p)) {
jxl::CodecInOut io1{memory_manager};
EXPECT_TRUE(jxl::ConvertFromExternal(
jxl::Bytes(passes[p].data(), passes[p].size()), xsize, ysize,
color_encoding, /*bits_per_sample=*/16, format, /*pool=*/nullptr, &io1.Main()));
distances[p] =
ButteraugliDistance(io.frames, io1.frames, butteraugli_params,
*JxlGetDefaultCms(), nullptr, nullptr); if (p == kNumPasses) break;
} constfloat kMaxDistance[kNumPasses + 1] = {30.0f, 20.0f, 10.0f,
5.0f, 3.0f, 2.0f};
EXPECT_LT(distances[kNumPasses], kMaxDistance[kNumPasses]); for (int p = 0; p < kNumPasses;) { int next_p = next_pass(p);
EXPECT_LT(distances[p], kMaxDistance[p]); // Verify that the returned pass image is actually not the // same as the next pass image, by checking that it has a bit // worse butteraugli score.
EXPECT_LT(distances[next_p] * 1.1f, distances[p]);
p = next_p;
}
}
}
}
// The non-essential final box size including 8-byte header
size_t final_box_size = unk3_box_size + 8;
size_t last_box_begin = compressed.size() - final_box_size; // Verify that the test is indeed setup correctly to be at the beginning of // the 'unkn' box header.
ASSERT_EQ(compressed[last_box_begin + 3], final_box_size);
ASSERT_EQ(compressed[last_box_begin + 4], 'u');
ASSERT_EQ(compressed[last_box_begin + 5], 'n');
ASSERT_EQ(compressed[last_box_begin + 6], 'k');
ASSERT_EQ(compressed[last_box_begin + 7], '3');
EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec));
EXPECT_EQ(JXL_DEC_FRAME, JxlDecoderProcessInput(dec)); // The decoder returns success despite not having seen the final unknown box // yet. This is because calling JxlDecoderCloseInput is not mandatory for // backwards compatibility, so it doesn't know more bytes follow, the current // bytes ended at a perfectly valid place.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
size_t remaining = JxlDecoderReleaseInput(dec); // Since the test was set up to end exactly at the boundary of the final // codestream box, and the decoder returned success, all bytes are expected to // be consumed until the end of the frame header.
EXPECT_EQ(remaining, last_box_begin - streampos.frames[0].toc_end);
// Now set the remaining non-codestream box as input.
EXPECT_EQ(JXL_DEC_SUCCESS,
JxlDecoderSetInput(dec, compressed.data() + last_box_begin,
compressed.size() - last_box_begin)); // Even though JxlDecoderProcessInput already returned JXL_DEC_SUCCESS before, // when calling it again now after setting more input, success is expected, no // event occurs but the box has been successfully skipped.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
// Value 0 means to not test the size: codestream is not required to be a // particular exact size.
std::vector<size_t> expected_box_sizes = {12, 20, 0, 34, 18, 0, 0, 0, 20};
// After the last DEC_BOX event, check that the input position is exactly at // the stat of the box header.
EXPECT_EQ(avail_in, expected_box_sizes.back());
// Even though all input is given, the decoder cannot assume there aren't // more boxes if the input was not closed.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetInput(dec, next_in, avail_in));
EXPECT_EQ(JXL_DEC_NEED_MORE_INPUT, JxlDecoderProcessInput(dec));
JxlDecoderCloseInput(dec);
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
// Now test whether the codestream extracted from the jxlp boxes can itself // also be decoded and gives the same pixels
{
JxlDecoder* dec = JxlDecoderCreate(nullptr);
EXPECT_EQ(0, num_boxes); // The data does not use the container format.
EXPECT_EQ(0u, jxl::test::ComparePixels(pixels.data(), pixels2.data(), xsize,
ysize, format_orig, format_orig));
// After the full image was output, JxlDecoderProcessInput should return // success to indicate all is done.
EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec));
JxlDecoderDestroy(dec);
for (size_t y = 0; y < ysize; y++) {
uint8_t* JXL_RESTRICT rowm = image.data() + stride * y;
uint8_t* JXL_RESTRICT rows = extra.data() + xsize * y; for (size_t x = 0; x < xsize; x++) { if (!render_spot) { // if spot color isn't rendered, main image should be as we made it // (red and blue are all zeroes)
EXPECT_EQ(rowm[x * 3 + 0], 0);
EXPECT_EQ(rowm[x * 3 + 1], (x + y > 255 ? 255 : x + y));
EXPECT_EQ(rowm[x * 3 + 2], 0);
} if (render_spot) { // if spot color is rendered, expect red and blue to look like the // spot color channel
EXPECT_LT(abs(rowm[x * 3 + 0] - (rows[x] * 0.25f)), 1);
EXPECT_LT(abs(rowm[x * 3 + 2] - (rows[x] * 0.5f)), 1);
}
EXPECT_EQ(rows[x], ((x ^ y) & 255));
}
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.