roundtrip_test.cc (46961B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #include <jxl/cms.h> 7 #include <jxl/codestream_header.h> 8 #include <jxl/color_encoding.h> 9 #include <jxl/decode.h> 10 #include <jxl/decode_cxx.h> 11 #include <jxl/encode.h> 12 #include <jxl/encode_cxx.h> 13 #include <jxl/types.h> 14 15 #include <cstddef> 16 #include <cstdint> 17 #include <cstdio> 18 #include <cstring> 19 #include <string> 20 #include <utility> 21 #include <vector> 22 23 #include "lib/extras/codec.h" 24 #include "lib/jxl/base/common.h" 25 #include "lib/jxl/base/span.h" 26 #include "lib/jxl/butteraugli/butteraugli.h" 27 #include "lib/jxl/color_encoding_internal.h" 28 #include "lib/jxl/dec_bit_reader.h" 29 #include "lib/jxl/enc_external_image.h" 30 #include "lib/jxl/encode_internal.h" 31 #include "lib/jxl/image.h" 32 #include "lib/jxl/image_ops.h" 33 #include "lib/jxl/image_test_utils.h" 34 #include "lib/jxl/test_memory_manager.h" 35 #include "lib/jxl/test_utils.h" 36 #include "lib/jxl/testing.h" 37 38 namespace { 39 40 using ::jxl::ImageF; 41 using ::jxl::test::ButteraugliDistance; 42 43 // Converts a test image to a CodecInOut. 44 // icc_profile can be empty to automatically deduce profile from the pixel 45 // format, or filled in to force this ICC profile 46 jxl::CodecInOut ConvertTestImage(const std::vector<uint8_t>& buf, 47 const size_t xsize, const size_t ysize, 48 const JxlPixelFormat& pixel_format, 49 const jxl::Bytes& icc_profile) { 50 jxl::CodecInOut io{jxl::test::MemoryManager()}; 51 jxl::test::Check(io.SetSize(xsize, ysize)); 52 53 bool is_gray = pixel_format.num_channels < 3; 54 bool has_alpha = 55 pixel_format.num_channels == 2 || pixel_format.num_channels == 4; 56 57 io.metadata.m.color_encoding.SetColorSpace(is_gray ? jxl::ColorSpace::kGray 58 : jxl::ColorSpace::kRGB); 59 if (has_alpha) { 60 // Note: alpha > 16 not yet supported by the C++ codec 61 switch (pixel_format.data_type) { 62 case JXL_TYPE_UINT8: 63 io.metadata.m.SetAlphaBits(8); 64 break; 65 case JXL_TYPE_UINT16: 66 case JXL_TYPE_FLOAT: 67 case JXL_TYPE_FLOAT16: 68 io.metadata.m.SetAlphaBits(16); 69 break; 70 default: 71 ADD_FAILURE() << "Roundtrip tests for data type " 72 << pixel_format.data_type << " not yet implemented."; 73 } 74 } 75 size_t bitdepth = 0; 76 switch (pixel_format.data_type) { 77 case JXL_TYPE_FLOAT: 78 bitdepth = 32; 79 io.metadata.m.SetFloat32Samples(); 80 break; 81 case JXL_TYPE_FLOAT16: 82 bitdepth = 16; 83 io.metadata.m.SetFloat16Samples(); 84 break; 85 case JXL_TYPE_UINT8: 86 bitdepth = 8; 87 io.metadata.m.SetUintSamples(8); 88 break; 89 case JXL_TYPE_UINT16: 90 bitdepth = 16; 91 io.metadata.m.SetUintSamples(16); 92 break; 93 default: 94 ADD_FAILURE() << "Roundtrip tests for data type " 95 << pixel_format.data_type << " not yet implemented."; 96 } 97 jxl::ColorEncoding color_encoding; 98 if (!icc_profile.empty()) { 99 jxl::IccBytes icc_profile_copy; 100 icc_profile.AppendTo(icc_profile_copy); 101 EXPECT_TRUE( 102 color_encoding.SetICC(std::move(icc_profile_copy), JxlGetDefaultCms())); 103 } else if (pixel_format.data_type == JXL_TYPE_FLOAT) { 104 color_encoding = jxl::ColorEncoding::LinearSRGB(is_gray); 105 } else { 106 color_encoding = jxl::ColorEncoding::SRGB(is_gray); 107 } 108 EXPECT_TRUE(ConvertFromExternal(jxl::Bytes(buf), xsize, ysize, color_encoding, 109 /*bits_per_sample=*/bitdepth, pixel_format, 110 /*pool=*/nullptr, &io.Main())); 111 return io; 112 } 113 114 template <typename T> 115 T ConvertTestPixel(float val); 116 117 template <> 118 float ConvertTestPixel<float>(const float val) { 119 return val; 120 } 121 122 template <> 123 uint16_t ConvertTestPixel<uint16_t>(const float val) { 124 return static_cast<uint16_t>(val * UINT16_MAX); 125 } 126 127 template <> 128 uint8_t ConvertTestPixel<uint8_t>(const float val) { 129 return static_cast<uint8_t>(val * UINT8_MAX); 130 } 131 132 // Returns a test image. 133 template <typename T> 134 std::vector<uint8_t> GetTestImage(const size_t xsize, const size_t ysize, 135 const JxlPixelFormat& pixel_format) { 136 std::vector<T> pixels(xsize * ysize * pixel_format.num_channels); 137 for (size_t y = 0; y < ysize; y++) { 138 for (size_t x = 0; x < xsize; x++) { 139 for (size_t chan = 0; chan < pixel_format.num_channels; chan++) { 140 float val; 141 switch (chan % 4) { 142 case 0: 143 val = static_cast<float>(y) / static_cast<float>(ysize); 144 break; 145 case 1: 146 val = static_cast<float>(x) / static_cast<float>(xsize); 147 break; 148 case 2: 149 val = static_cast<float>(x + y) / static_cast<float>(xsize + ysize); 150 break; 151 case 3: 152 default: 153 val = static_cast<float>(x * y) / static_cast<float>(xsize * ysize); 154 break; 155 } 156 pixels[(y * xsize + x) * pixel_format.num_channels + chan] = 157 ConvertTestPixel<T>(val); 158 } 159 } 160 } 161 std::vector<uint8_t> bytes(pixels.size() * sizeof(T)); 162 memcpy(bytes.data(), pixels.data(), sizeof(T) * pixels.size()); 163 return bytes; 164 } 165 166 void EncodeWithEncoder(JxlEncoder* enc, std::vector<uint8_t>* compressed) { 167 compressed->resize(64); 168 uint8_t* next_out = compressed->data(); 169 size_t avail_out = compressed->size() - (next_out - compressed->data()); 170 JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; 171 while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 172 process_result = JxlEncoderProcessOutput(enc, &next_out, &avail_out); 173 if (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 174 size_t offset = next_out - compressed->data(); 175 compressed->resize(compressed->size() * 2); 176 next_out = compressed->data() + offset; 177 avail_out = compressed->size() - offset; 178 } 179 } 180 compressed->resize(next_out - compressed->data()); 181 EXPECT_EQ(JXL_ENC_SUCCESS, process_result); 182 } 183 184 // Generates some pixels using some dimensions and pixel_format, 185 // compresses them, and verifies that the decoded version is similar to the 186 // original pixels. 187 // TODO(firsching): change this to be a parameterized test, like in 188 // decode_test.cc 189 template <typename T> 190 void VerifyRoundtripCompression( 191 const size_t xsize, const size_t ysize, 192 const JxlPixelFormat& input_pixel_format, 193 const JxlPixelFormat& output_pixel_format, const bool lossless, 194 const bool use_container, const uint32_t resampling = 1, 195 const bool already_downsampled = false, 196 const std::vector<std::pair<JxlExtraChannelType, std::string>>& 197 extra_channels = {}, 198 const int upsampling_mode = -1) { 199 size_t orig_xsize = xsize; 200 size_t orig_ysize = ysize; 201 if (already_downsampled) { 202 orig_xsize = jxl::DivCeil(xsize, resampling); 203 orig_ysize = jxl::DivCeil(ysize, resampling); 204 } 205 206 JxlPixelFormat extra_channel_pixel_format = input_pixel_format; 207 extra_channel_pixel_format.num_channels = 1; 208 const std::vector<uint8_t> extra_channel_bytes = 209 GetTestImage<T>(xsize, ysize, extra_channel_pixel_format); 210 const std::vector<uint8_t> original_bytes = 211 GetTestImage<T>(orig_xsize, orig_ysize, input_pixel_format); 212 jxl::CodecInOut original_io = ConvertTestImage( 213 original_bytes, orig_xsize, orig_ysize, input_pixel_format, {}); 214 215 JxlEncoder* enc = JxlEncoderCreate(nullptr); 216 EXPECT_NE(nullptr, enc); 217 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10)); 218 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc, use_container)); 219 JxlBasicInfo basic_info; 220 jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &input_pixel_format); 221 basic_info.xsize = xsize; 222 basic_info.ysize = ysize; 223 basic_info.uses_original_profile = lossless; 224 uint32_t num_channels = input_pixel_format.num_channels; 225 size_t has_interleaved_alpha = num_channels == 2 || num_channels == 4; 226 JxlPixelFormat output_pixel_format_with_extra_channel_alpha = 227 output_pixel_format; 228 229 // In the case where we have an alpha channel, but it is provided as an extra 230 // channel and not interleaved, we do two things here: 231 // 1. modify the original_io to have the correct alpha channel 232 // 2. change the output_format_with_extra_alpha to have an alpha channel 233 bool alpha_in_extra_channels_vector = false; 234 for (const auto& extra_channel : extra_channels) { 235 if (extra_channel.first == JXL_CHANNEL_ALPHA) { 236 alpha_in_extra_channels_vector = true; 237 } 238 } 239 if (alpha_in_extra_channels_vector && !has_interleaved_alpha) { 240 JXL_TEST_ASSIGN_OR_DIE( 241 ImageF alpha_channel, 242 ImageF::Create(jxl::test::MemoryManager(), xsize, ysize)); 243 EXPECT_TRUE(jxl::ConvertFromExternal( 244 extra_channel_bytes.data(), extra_channel_bytes.size(), xsize, ysize, 245 basic_info.bits_per_sample, extra_channel_pixel_format, 0, 246 /*pool=*/nullptr, &alpha_channel)); 247 248 original_io.metadata.m.SetAlphaBits(basic_info.bits_per_sample); 249 ASSERT_TRUE(original_io.Main().SetAlpha(std::move(alpha_channel))); 250 output_pixel_format_with_extra_channel_alpha.num_channels++; 251 } 252 // Those are the num_extra_channels including a potential alpha channel. 253 basic_info.num_extra_channels = extra_channels.size() + has_interleaved_alpha; 254 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info)); 255 EXPECT_EQ(enc->metadata.m.num_extra_channels, 256 extra_channels.size() + has_interleaved_alpha); 257 JxlColorEncoding color_encoding; 258 if (input_pixel_format.data_type == JXL_TYPE_FLOAT) { 259 JxlColorEncodingSetToLinearSRGB( 260 &color_encoding, 261 /*is_gray=*/input_pixel_format.num_channels < 3); 262 } else { 263 JxlColorEncodingSetToSRGB(&color_encoding, 264 /*is_gray=*/input_pixel_format.num_channels < 3); 265 } 266 267 std::vector<JxlExtraChannelInfo> channel_infos; 268 for (const auto& extra_channel : extra_channels) { 269 auto channel_type = extra_channel.first; 270 JxlExtraChannelInfo channel_info; 271 JxlEncoderInitExtraChannelInfo(channel_type, &channel_info); 272 channel_info.bits_per_sample = (lossless ? basic_info.bits_per_sample : 8); 273 channel_info.exponent_bits_per_sample = 274 (lossless ? basic_info.exponent_bits_per_sample : 0); 275 channel_infos.push_back(channel_info); 276 } 277 for (size_t index = 0; index < channel_infos.size(); index++) { 278 EXPECT_EQ(JXL_ENC_SUCCESS, 279 JxlEncoderSetExtraChannelInfo(enc, index + has_interleaved_alpha, 280 &channel_infos[index])); 281 std::string name = extra_channels[index].second; 282 EXPECT_EQ(JXL_ENC_SUCCESS, 283 JxlEncoderSetExtraChannelName(enc, index + has_interleaved_alpha, 284 name.c_str(), name.length())); 285 } 286 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc, &color_encoding)); 287 if (resampling > 1) { 288 EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetUpsamplingMode(enc, 3, 0)); 289 EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetUpsamplingMode(enc, resampling, -2)); 290 EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetUpsamplingMode(enc, resampling, 2)); 291 } 292 EXPECT_EQ(JXL_ENC_SUCCESS, 293 JxlEncoderSetUpsamplingMode(enc, resampling, upsampling_mode)); 294 JxlEncoderFrameSettings* frame_settings = 295 JxlEncoderFrameSettingsCreate(enc, nullptr); 296 JxlEncoderSetFrameLossless(frame_settings, lossless); 297 if (resampling > 1) { 298 EXPECT_EQ( 299 JXL_ENC_SUCCESS, 300 JxlEncoderFrameSettingsSetOption( 301 frame_settings, JXL_ENC_FRAME_SETTING_RESAMPLING, resampling)); 302 EXPECT_EQ(JXL_ENC_SUCCESS, 303 JxlEncoderFrameSettingsSetOption( 304 frame_settings, JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED, 305 already_downsampled)); 306 } 307 EXPECT_EQ( 308 JXL_ENC_SUCCESS, 309 JxlEncoderAddImageFrame(frame_settings, &input_pixel_format, 310 static_cast<const void*>(original_bytes.data()), 311 original_bytes.size())); 312 EXPECT_EQ(frame_settings->enc->input_queue.empty(), false); 313 for (size_t index = 0; index < channel_infos.size(); index++) { 314 EXPECT_EQ(JXL_ENC_SUCCESS, 315 JxlEncoderSetExtraChannelBuffer( 316 frame_settings, &extra_channel_pixel_format, 317 static_cast<const void*>(extra_channel_bytes.data()), 318 extra_channel_bytes.size(), index + has_interleaved_alpha)); 319 } 320 JxlEncoderCloseInput(enc); 321 std::vector<uint8_t> compressed; 322 EncodeWithEncoder(enc, &compressed); 323 JxlEncoderDestroy(enc); 324 325 JxlDecoder* dec = JxlDecoderCreate(nullptr); 326 EXPECT_NE(nullptr, dec); 327 328 const uint8_t* next_in = compressed.data(); 329 size_t avail_in = compressed.size(); 330 331 EXPECT_EQ(JXL_DEC_SUCCESS, 332 JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO | 333 JXL_DEC_COLOR_ENCODING | 334 JXL_DEC_FULL_IMAGE)); 335 336 JxlDecoderSetInput(dec, next_in, avail_in); 337 EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec)); 338 size_t buffer_size; 339 EXPECT_EQ( 340 JXL_DEC_SUCCESS, 341 JxlDecoderImageOutBufferSize( 342 dec, &output_pixel_format_with_extra_channel_alpha, &buffer_size)); 343 if (&input_pixel_format == &output_pixel_format_with_extra_channel_alpha && 344 !already_downsampled) { 345 EXPECT_EQ(buffer_size, original_bytes.size()); 346 } 347 348 JxlBasicInfo info; 349 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info)); 350 EXPECT_EQ(xsize, info.xsize); 351 EXPECT_EQ(ysize, info.ysize); 352 EXPECT_EQ(extra_channels.size() + has_interleaved_alpha, 353 info.num_extra_channels); 354 355 EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec)); 356 357 size_t icc_profile_size; 358 EXPECT_EQ(JXL_DEC_SUCCESS, 359 JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA, 360 &icc_profile_size)); 361 std::vector<uint8_t> icc_profile(icc_profile_size); 362 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile( 363 dec, JXL_COLOR_PROFILE_TARGET_DATA, 364 icc_profile.data(), icc_profile.size())); 365 366 std::vector<uint8_t> decoded_bytes(buffer_size); 367 368 EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec)); 369 370 EXPECT_EQ(JXL_DEC_SUCCESS, 371 JxlDecoderSetImageOutBuffer( 372 dec, &output_pixel_format_with_extra_channel_alpha, 373 decoded_bytes.data(), decoded_bytes.size())); 374 std::vector<std::vector<uint8_t>> extra_channel_decoded_bytes( 375 info.num_extra_channels - has_interleaved_alpha); 376 377 for (size_t index = has_interleaved_alpha; index < info.num_extra_channels; 378 index++) { 379 JxlExtraChannelInfo channel_info; 380 EXPECT_EQ(JXL_DEC_SUCCESS, 381 JxlDecoderGetExtraChannelInfo(dec, index, &channel_info)); 382 EXPECT_EQ(channel_info.type, 383 extra_channels[index - has_interleaved_alpha].first); 384 std::string input_name = 385 extra_channels[index - has_interleaved_alpha].second; 386 const size_t name_length = channel_info.name_length; 387 EXPECT_EQ(input_name.size(), name_length); 388 std::vector<char> output_name(name_length + 1); 389 EXPECT_EQ(JXL_DEC_SUCCESS, 390 JxlDecoderGetExtraChannelName(dec, index, output_name.data(), 391 output_name.size())); 392 EXPECT_EQ(0, 393 memcmp(input_name.data(), output_name.data(), input_name.size())); 394 size_t extra_buffer_size; 395 EXPECT_EQ(JXL_DEC_SUCCESS, 396 JxlDecoderExtraChannelBufferSize(dec, &output_pixel_format, 397 &extra_buffer_size, index)); 398 std::vector<uint8_t> extra_decoded_bytes(extra_buffer_size); 399 extra_channel_decoded_bytes[index - has_interleaved_alpha] = 400 std::move(extra_decoded_bytes); 401 EXPECT_EQ( 402 JXL_DEC_SUCCESS, 403 JxlDecoderSetExtraChannelBuffer( 404 dec, &output_pixel_format, 405 extra_channel_decoded_bytes[index - has_interleaved_alpha].data(), 406 extra_channel_decoded_bytes[index - has_interleaved_alpha].size(), 407 index)); 408 } 409 EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec)); 410 // Check if there are no further errors after getting the full image, e.g. 411 // check that the final codestream box is actually marked as last. 412 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderProcessInput(dec)); 413 414 JxlDecoderDestroy(dec); 415 416 jxl::CodecInOut decoded_io = ConvertTestImage( 417 decoded_bytes, xsize, ysize, output_pixel_format_with_extra_channel_alpha, 418 jxl::Bytes(icc_profile)); 419 420 if (already_downsampled) { 421 jxl::Image3F* color = decoded_io.Main().color(); 422 JXL_TEST_ASSIGN_OR_DIE(*color, jxl::DownsampleImage(*color, resampling)); 423 if (decoded_io.Main().HasAlpha()) { 424 ImageF* alpha = decoded_io.Main().alpha(); 425 JXL_TEST_ASSIGN_OR_DIE(*alpha, jxl::DownsampleImage(*alpha, resampling)); 426 } 427 EXPECT_TRUE(decoded_io.SetSize(color->xsize(), color->ysize())); 428 } 429 430 if (lossless && !already_downsampled) { 431 JXL_EXPECT_OK(jxl::SamePixels(*original_io.Main().color(), 432 *decoded_io.Main().color(), _)); 433 } else { 434 jxl::ButteraugliParams butteraugli_params; 435 float butteraugli_score = 436 ButteraugliDistance(original_io.frames, decoded_io.frames, 437 butteraugli_params, *JxlGetDefaultCms(), 438 /*distmap=*/nullptr, nullptr); 439 float target_score = 1.5f; 440 // upsampling mode 1 (unlike default and NN) does not downscale back to the 441 // already downsampled image 442 if (upsampling_mode == 1 && resampling >= 4 && already_downsampled) 443 target_score = 15.f; 444 EXPECT_LE(butteraugli_score, target_score); 445 } 446 JxlPixelFormat extra_channel_output_pixel_format = output_pixel_format; 447 extra_channel_output_pixel_format.num_channels = 1; 448 for (auto& extra_channel : extra_channel_decoded_bytes) { 449 EXPECT_EQ(extra_channel.size(), extra_channel_bytes.size()); 450 if (lossless) { 451 EXPECT_EQ(jxl::test::ComparePixels(extra_channel.data(), 452 extra_channel_bytes.data(), xsize, 453 ysize, extra_channel_pixel_format, 454 extra_channel_output_pixel_format), 455 0u); 456 EXPECT_EQ(extra_channel, extra_channel_bytes); 457 } 458 } 459 } 460 461 } // namespace 462 463 TEST(RoundtripTest, FloatFrameRoundtripTest) { 464 std::vector<std::vector<std::pair<JxlExtraChannelType, std::string>>> 465 extra_channels_cases = {{}, 466 {{JXL_CHANNEL_ALPHA, "my extra alpha channel"}}, 467 {{JXL_CHANNEL_CFA, "my cfa channel"}}, 468 {{JXL_CHANNEL_DEPTH, "depth"}, 469 {JXL_CHANNEL_SELECTION_MASK, "mask"}, 470 {JXL_CHANNEL_BLACK, "black"}, 471 {JXL_CHANNEL_CFA, "my cfa channel"}, 472 {JXL_CHANNEL_OPTIONAL, "optional channel"}}, 473 {{JXL_CHANNEL_DEPTH, "very deep"}}}; 474 for (bool use_container : {false, true}) { 475 for (bool lossless : {false, true}) { 476 for (uint32_t num_channels = 1; num_channels < 5; num_channels++) { 477 for (auto& extra_channels : extra_channels_cases) { 478 uint32_t has_alpha = static_cast<uint32_t>(num_channels % 2 == 0); 479 uint32_t total_extra_channels = has_alpha + extra_channels.size(); 480 // There's no support (yet) for lossless extra float 481 // channels, so we don't test it. 482 if (total_extra_channels == 0 || !lossless) { 483 JxlPixelFormat pixel_format = JxlPixelFormat{ 484 num_channels, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0}; 485 VerifyRoundtripCompression<float>( 486 63, 129, pixel_format, pixel_format, lossless, use_container, 1, 487 false, extra_channels); 488 } 489 } 490 } 491 } 492 } 493 } 494 495 TEST(RoundtripTest, Uint16FrameRoundtripTest) { 496 std::vector<std::vector<std::pair<JxlExtraChannelType, std::string>>> 497 extra_channels_cases = {{}, 498 {{JXL_CHANNEL_ALPHA, "my extra alpha channel"}}, 499 {{JXL_CHANNEL_CFA, "my cfa channel"}}, 500 {{JXL_CHANNEL_CFA, "my cfa channel"}, 501 {JXL_CHANNEL_BLACK, "k_channel"}}, 502 {{JXL_CHANNEL_DEPTH, "very deep"}}}; 503 for (int use_container = 0; use_container < 2; use_container++) { 504 for (int lossless = 0; lossless < 2; lossless++) { 505 for (uint32_t num_channels = 1; num_channels < 5; num_channels++) { 506 for (auto& extra_channels : extra_channels_cases) { 507 JxlPixelFormat pixel_format = JxlPixelFormat{ 508 num_channels, JXL_TYPE_UINT16, JXL_NATIVE_ENDIAN, 0}; 509 VerifyRoundtripCompression<uint16_t>( 510 63, 129, pixel_format, pixel_format, static_cast<bool>(lossless), 511 static_cast<bool>(use_container), 1, false, extra_channels); 512 } 513 } 514 } 515 } 516 } 517 518 TEST(RoundtripTest, Uint8FrameRoundtripTest) { 519 std::vector<std::vector<std::pair<JxlExtraChannelType, std::string>>> 520 extra_channels_cases = {{}, 521 {{JXL_CHANNEL_THERMAL, "temperature"}}, 522 {{JXL_CHANNEL_ALPHA, "my extra alpha channel"}}, 523 {{JXL_CHANNEL_CFA, "my cfa channel"}}, 524 {{JXL_CHANNEL_CFA, "my cfa channel"}, 525 {JXL_CHANNEL_BLACK, "k_channel"}}, 526 {{JXL_CHANNEL_DEPTH, "very deep"}}}; 527 for (int use_container = 0; use_container < 2; use_container++) { 528 for (int lossless = 0; lossless < 2; lossless++) { 529 for (uint32_t num_channels = 1; num_channels < 5; num_channels++) { 530 for (auto& extra_channels : extra_channels_cases) { 531 JxlPixelFormat pixel_format = JxlPixelFormat{ 532 num_channels, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; 533 VerifyRoundtripCompression<uint8_t>( 534 63, 129, pixel_format, pixel_format, static_cast<bool>(lossless), 535 static_cast<bool>(use_container), 1, false, extra_channels); 536 } 537 } 538 } 539 } 540 } 541 542 TEST(RoundtripTest, TestNonlinearSrgbAsXybEncoded) { 543 for (int use_container = 0; use_container < 2; use_container++) { 544 for (uint32_t num_channels = 1; num_channels < 5; num_channels++) { 545 JxlPixelFormat pixel_format_in = 546 JxlPixelFormat{num_channels, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; 547 JxlPixelFormat pixel_format_out = 548 JxlPixelFormat{num_channels, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0}; 549 VerifyRoundtripCompression<uint8_t>( 550 63, 129, pixel_format_in, pixel_format_out, 551 /*lossless=*/false, static_cast<bool>(use_container), 1, false, {}); 552 } 553 } 554 } 555 556 TEST(RoundtripTest, Resampling) { 557 JxlPixelFormat pixel_format = 558 JxlPixelFormat{3, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; 559 VerifyRoundtripCompression<uint8_t>(63, 129, pixel_format, pixel_format, 560 /*lossless=*/false, 561 /*use_container=*/false, 2, 562 /*already_downsampled=*/false); 563 564 // TODO(lode): also make this work for odd sizes. This requires a fix in 565 // enc_frame.cc to not set custom_size_or_origin to true due to even/odd 566 // mismatch. 567 for (int factor : {2, 4, 8}) { 568 for (int upsampling_mode : {-1, 0, 1}) { 569 VerifyRoundtripCompression<uint8_t>( 570 64, 128, pixel_format, pixel_format, 571 /*lossless=*/true, 572 /*use_container=*/false, factor, 573 /*already_downsampled=*/true, /*extra_channels=*/{}, upsampling_mode); 574 } 575 } 576 } 577 578 TEST(RoundtripTest, ExtraBoxesTest) { 579 JxlPixelFormat pixel_format = 580 JxlPixelFormat{4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0}; 581 const size_t xsize = 61; 582 const size_t ysize = 71; 583 584 const std::vector<uint8_t> original_bytes = 585 GetTestImage<float>(xsize, ysize, pixel_format); 586 jxl::CodecInOut original_io = 587 ConvertTestImage(original_bytes, xsize, ysize, pixel_format, {}); 588 589 JxlEncoder* enc = JxlEncoderCreate(nullptr); 590 EXPECT_NE(nullptr, enc); 591 592 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc, true)); 593 JxlBasicInfo basic_info; 594 jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); 595 basic_info.xsize = xsize; 596 basic_info.ysize = ysize; 597 basic_info.uses_original_profile = JXL_FALSE; 598 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10)); 599 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info)); 600 JxlColorEncoding color_encoding; 601 JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3); 602 if (pixel_format.data_type == JXL_TYPE_FLOAT) { 603 JxlColorEncodingSetToLinearSRGB(&color_encoding, is_gray); 604 } else { 605 JxlColorEncodingSetToSRGB(&color_encoding, is_gray); 606 } 607 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc, &color_encoding)); 608 JxlEncoderFrameSettings* frame_settings = 609 JxlEncoderFrameSettingsCreate(enc, nullptr); 610 JxlEncoderSetFrameLossless(frame_settings, JXL_FALSE); 611 EXPECT_EQ( 612 JXL_ENC_SUCCESS, 613 JxlEncoderAddImageFrame(frame_settings, &pixel_format, 614 static_cast<const void*>(original_bytes.data()), 615 original_bytes.size())); 616 JxlEncoderCloseInput(enc); 617 618 std::vector<uint8_t> compressed; 619 EncodeWithEncoder(enc, &compressed); 620 JxlEncoderDestroy(enc); 621 622 std::vector<uint8_t> extra_data(1023); 623 jxl::AppendBoxHeader(jxl::MakeBoxType("crud"), extra_data.size(), false, 624 &compressed); 625 compressed.insert(compressed.end(), extra_data.begin(), extra_data.end()); 626 627 JxlDecoder* dec = JxlDecoderCreate(nullptr); 628 EXPECT_NE(nullptr, dec); 629 630 const uint8_t* next_in = compressed.data(); 631 size_t avail_in = compressed.size(); 632 633 EXPECT_EQ(JXL_DEC_SUCCESS, 634 JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO | 635 JXL_DEC_COLOR_ENCODING | 636 JXL_DEC_FULL_IMAGE)); 637 638 JxlDecoderSetInput(dec, next_in, avail_in); 639 EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec)); 640 size_t buffer_size; 641 EXPECT_EQ(JXL_DEC_SUCCESS, 642 JxlDecoderImageOutBufferSize(dec, &pixel_format, &buffer_size)); 643 EXPECT_EQ(buffer_size, original_bytes.size()); 644 645 JxlBasicInfo info; 646 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info)); 647 EXPECT_EQ(xsize, info.xsize); 648 EXPECT_EQ(ysize, info.ysize); 649 650 EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec)); 651 652 size_t icc_profile_size; 653 EXPECT_EQ(JXL_DEC_SUCCESS, 654 JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA, 655 &icc_profile_size)); 656 std::vector<uint8_t> icc_profile(icc_profile_size); 657 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile( 658 dec, JXL_COLOR_PROFILE_TARGET_DATA, 659 icc_profile.data(), icc_profile.size())); 660 661 std::vector<uint8_t> decoded_bytes(buffer_size); 662 663 EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec)); 664 665 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetImageOutBuffer(dec, &pixel_format, 666 decoded_bytes.data(), 667 decoded_bytes.size())); 668 669 EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec)); 670 671 JxlDecoderDestroy(dec); 672 673 jxl::CodecInOut decoded_io = ConvertTestImage( 674 decoded_bytes, xsize, ysize, pixel_format, jxl::Bytes(icc_profile)); 675 676 jxl::ButteraugliParams butteraugli_params; 677 float butteraugli_score = 678 ButteraugliDistance(original_io.frames, decoded_io.frames, 679 butteraugli_params, *JxlGetDefaultCms(), 680 /*distmap=*/nullptr, nullptr); 681 EXPECT_LE(butteraugli_score, 1.0f); 682 } 683 684 TEST(RoundtripTest, MultiFrameTest) { 685 JxlPixelFormat pixel_format = 686 JxlPixelFormat{4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0}; 687 const size_t xsize = 61; 688 const size_t ysize = 71; 689 const size_t nb_frames = 4; 690 size_t compressed_size = 0; 691 692 for (int index_frames : {0, 1}) { 693 // use a vertical filmstrip of nb_frames frames 694 const std::vector<uint8_t> original_bytes = 695 GetTestImage<float>(xsize, ysize * nb_frames, pixel_format); 696 jxl::CodecInOut original_io = ConvertTestImage( 697 original_bytes, xsize, ysize * nb_frames, pixel_format, {}); 698 699 JxlEncoder* enc = JxlEncoderCreate(nullptr); 700 EXPECT_NE(nullptr, enc); 701 702 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc, true)); 703 JxlBasicInfo basic_info; 704 jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); 705 basic_info.xsize = xsize; 706 basic_info.ysize = ysize; 707 basic_info.uses_original_profile = JXL_FALSE; 708 basic_info.have_animation = JXL_TRUE; 709 basic_info.animation.tps_numerator = 1; 710 basic_info.animation.tps_denominator = 1; 711 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10)); 712 713 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info)); 714 JxlColorEncoding color_encoding; 715 JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3); 716 if (pixel_format.data_type == JXL_TYPE_FLOAT) { 717 JxlColorEncodingSetToLinearSRGB(&color_encoding, is_gray); 718 } else { 719 JxlColorEncodingSetToSRGB(&color_encoding, is_gray); 720 } 721 EXPECT_EQ(JXL_ENC_SUCCESS, 722 JxlEncoderSetColorEncoding(enc, &color_encoding)); 723 JxlEncoderFrameSettings* frame_settings = 724 JxlEncoderFrameSettingsCreate(enc, nullptr); 725 JxlEncoderSetFrameLossless(frame_settings, JXL_FALSE); 726 if (index_frames == 1) { 727 EXPECT_EQ(JXL_ENC_SUCCESS, 728 JxlEncoderFrameSettingsSetOption(frame_settings, 729 JXL_ENC_FRAME_INDEX_BOX, 1)); 730 } 731 732 size_t oneframesize = original_bytes.size() / nb_frames; 733 JxlFrameHeader frame_header; 734 JxlEncoderInitFrameHeader(&frame_header); 735 frame_header.duration = 1; 736 frame_header.is_last = JXL_FALSE; 737 738 for (size_t i = 0; i < nb_frames; i++) { 739 if (i + 1 == nb_frames) frame_header.is_last = JXL_TRUE; 740 JxlEncoderSetFrameHeader(frame_settings, &frame_header); 741 EXPECT_EQ( 742 JXL_ENC_SUCCESS, 743 JxlEncoderAddImageFrame(frame_settings, &pixel_format, 744 static_cast<const void*>( 745 original_bytes.data() + oneframesize * i), 746 oneframesize)); 747 } 748 JxlEncoderCloseInput(enc); 749 750 std::vector<uint8_t> compressed; 751 EncodeWithEncoder(enc, &compressed); 752 JxlEncoderDestroy(enc); 753 754 JxlDecoder* dec = JxlDecoderCreate(nullptr); 755 EXPECT_NE(nullptr, dec); 756 757 const uint8_t* next_in = compressed.data(); 758 size_t avail_in = compressed.size(); 759 760 if (index_frames == 0) { 761 compressed_size = avail_in; 762 } else { 763 // a non-empty jxli box should be added 764 EXPECT_LE(avail_in, compressed_size + 50); 765 EXPECT_GE(avail_in, compressed_size + 10); 766 } 767 768 EXPECT_EQ(JXL_DEC_SUCCESS, 769 JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO | 770 JXL_DEC_COLOR_ENCODING | 771 JXL_DEC_FULL_IMAGE)); 772 773 JxlDecoderSetInput(dec, next_in, avail_in); 774 EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec)); 775 size_t buffer_size; 776 EXPECT_EQ(JXL_DEC_SUCCESS, 777 JxlDecoderImageOutBufferSize(dec, &pixel_format, &buffer_size)); 778 EXPECT_EQ(buffer_size, oneframesize); 779 780 JxlBasicInfo info; 781 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info)); 782 EXPECT_EQ(xsize, info.xsize); 783 EXPECT_EQ(ysize, info.ysize); 784 785 EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec)); 786 787 size_t icc_profile_size; 788 EXPECT_EQ(JXL_DEC_SUCCESS, 789 JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_DATA, 790 &icc_profile_size)); 791 std::vector<uint8_t> icc_profile(icc_profile_size); 792 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile( 793 dec, JXL_COLOR_PROFILE_TARGET_DATA, 794 icc_profile.data(), icc_profile.size())); 795 796 std::vector<uint8_t> decoded_bytes(buffer_size * nb_frames); 797 798 EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec)); 799 800 for (size_t i = 0; i < nb_frames; i++) { 801 EXPECT_EQ(JXL_DEC_SUCCESS, 802 JxlDecoderSetImageOutBuffer( 803 dec, &pixel_format, decoded_bytes.data() + i * oneframesize, 804 buffer_size)); 805 806 EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec)); 807 } 808 JxlDecoderDestroy(dec); 809 jxl::CodecInOut decoded_io = 810 ConvertTestImage(decoded_bytes, xsize, ysize * nb_frames, pixel_format, 811 jxl::Bytes(icc_profile)); 812 813 jxl::ButteraugliParams butteraugli_params; 814 float butteraugli_score = 815 ButteraugliDistance(original_io.frames, decoded_io.frames, 816 butteraugli_params, *JxlGetDefaultCms(), 817 /*distmap=*/nullptr, nullptr); 818 EXPECT_LE(butteraugli_score, 1.0f); 819 } 820 } 821 822 static const unsigned char kEncodedTestProfile[] = { 823 0x1f, 0x8b, 0x1, 0x13, 0x10, 0x0, 0x0, 0x0, 0x20, 0x4c, 0xcc, 0x3, 824 0xe7, 0xa0, 0xa5, 0xa2, 0x90, 0xa4, 0x27, 0xe8, 0x79, 0x1d, 0xe3, 0x26, 825 0x57, 0x54, 0xef, 0x0, 0xe8, 0x97, 0x2, 0xce, 0xa1, 0xd7, 0x85, 0x16, 826 0xb4, 0x29, 0x94, 0x58, 0xf2, 0x56, 0xc0, 0x76, 0xea, 0x23, 0xec, 0x7c, 827 0x73, 0x51, 0x41, 0x40, 0x23, 0x21, 0x95, 0x4, 0x75, 0x12, 0xc9, 0xcc, 828 0x16, 0xbd, 0xb6, 0x99, 0xad, 0xf8, 0x75, 0x35, 0xb6, 0x42, 0xae, 0xae, 829 0xae, 0x86, 0x56, 0xf8, 0xcc, 0x16, 0x30, 0xb3, 0x45, 0xad, 0xd, 0x40, 830 0xd6, 0xd1, 0xd6, 0x99, 0x40, 0xbe, 0xe2, 0xdc, 0x31, 0x7, 0xa6, 0xb9, 831 0x27, 0x92, 0x38, 0x0, 0x3, 0x5e, 0x2c, 0xbe, 0xe6, 0xfb, 0x19, 0xbf, 832 0xf3, 0x6d, 0xbc, 0x4d, 0x64, 0xe5, 0xba, 0x76, 0xde, 0x31, 0x65, 0x66, 833 0x14, 0xa6, 0x3a, 0xc5, 0x8f, 0xb1, 0xb4, 0xba, 0x1f, 0xb1, 0xb8, 0xd4, 834 0x75, 0xba, 0x18, 0x86, 0x95, 0x3c, 0x26, 0xf6, 0x25, 0x62, 0x53, 0xfd, 835 0x9c, 0x94, 0x76, 0xf6, 0x95, 0x2c, 0xb1, 0xfd, 0xdc, 0xc0, 0xe4, 0x3f, 836 0xb3, 0xff, 0x67, 0xde, 0xd5, 0x94, 0xcc, 0xb0, 0x83, 0x2f, 0x28, 0x93, 837 0x92, 0x3, 0xa1, 0x41, 0x64, 0x60, 0x62, 0x70, 0x80, 0x87, 0xaf, 0xe7, 838 0x60, 0x4a, 0x20, 0x23, 0xb3, 0x11, 0x7, 0x38, 0x38, 0xd4, 0xa, 0x66, 839 0xb5, 0x93, 0x41, 0x90, 0x19, 0x17, 0x18, 0x60, 0xa5, 0xb, 0x7a, 0x24, 840 0xaa, 0x20, 0x81, 0xac, 0xa9, 0xa1, 0x70, 0xa6, 0x12, 0x8a, 0x4a, 0xa3, 841 0xa0, 0xf9, 0x9a, 0x97, 0xe7, 0xa8, 0xac, 0x8, 0xa8, 0xc4, 0x2a, 0x86, 842 0xa7, 0x69, 0x1e, 0x67, 0xe6, 0xbe, 0xa4, 0xd3, 0xff, 0x91, 0x61, 0xf6, 843 0x8a, 0xe6, 0xb5, 0xb3, 0x61, 0x9f, 0x19, 0x17, 0x98, 0x27, 0x6b, 0xe9, 844 0x8, 0x98, 0xe1, 0x21, 0x4a, 0x9, 0xb5, 0xd7, 0xca, 0xfa, 0x94, 0xd0, 845 0x69, 0x1a, 0xeb, 0x52, 0x1, 0x4e, 0xf5, 0xf6, 0xdf, 0x7f, 0xe7, 0x29, 846 0x70, 0xee, 0x4, 0xda, 0x2f, 0xa4, 0xff, 0xfe, 0xbb, 0x6f, 0xa8, 0xff, 847 0xfe, 0xdb, 0xaf, 0x8, 0xf6, 0x72, 0xa1, 0x40, 0x5d, 0xf0, 0x2d, 0x8, 848 0x82, 0x5b, 0x87, 0xbd, 0x10, 0x8, 0xe9, 0x7, 0xee, 0x4b, 0x80, 0xda, 849 0x4a, 0x4, 0xc5, 0x5e, 0xa0, 0xb7, 0x1e, 0x60, 0xb0, 0x59, 0x76, 0x60, 850 0xb, 0x2e, 0x19, 0x8a, 0x2e, 0x1c, 0xe6, 0x6, 0x20, 0xb8, 0x64, 0x18, 851 0x2a, 0xcf, 0x51, 0x94, 0xd4, 0xee, 0xc3, 0xfe, 0x39, 0x74, 0xd4, 0x2b, 852 0x48, 0xc9, 0x83, 0x4c, 0x9b, 0xd0, 0x4c, 0x35, 0x10, 0xe3, 0x9, 0xf7, 853 0x72, 0xf0, 0x7a, 0xe, 0xbf, 0x7d, 0x36, 0x2e, 0x19, 0x7e, 0x3f, 0xc, 854 0xf7, 0x93, 0xe7, 0xf4, 0x1d, 0x32, 0xc6, 0xb0, 0x89, 0xad, 0xe0, 0x28, 855 0xc1, 0xa7, 0x59, 0xe3, 0x0, 856 }; 857 858 TEST(RoundtripTest, TestICCProfile) { 859 // JxlEncoderSetICCProfile parses the ICC profile, so a valid profile is 860 // needed. The profile should be passed correctly through the roundtrip. 861 jxl::BitReader reader( 862 jxl::Bytes(kEncodedTestProfile, sizeof(kEncodedTestProfile))); 863 std::vector<uint8_t> icc; 864 ASSERT_TRUE(jxl::test::ReadICC(&reader, &icc)); 865 ASSERT_TRUE(reader.Close()); 866 867 JxlPixelFormat format = 868 JxlPixelFormat{3, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0}; 869 870 size_t xsize = 25; 871 size_t ysize = 37; 872 const std::vector<uint8_t> original_bytes = 873 GetTestImage<uint8_t>(xsize, ysize, format); 874 875 JxlEncoder* enc = JxlEncoderCreate(nullptr); 876 EXPECT_NE(nullptr, enc); 877 878 JxlBasicInfo basic_info; 879 jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &format); 880 basic_info.xsize = xsize; 881 basic_info.ysize = ysize; 882 basic_info.uses_original_profile = JXL_TRUE; 883 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info)); 884 885 EXPECT_EQ(JXL_ENC_SUCCESS, 886 JxlEncoderSetICCProfile(enc, icc.data(), icc.size())); 887 JxlEncoderFrameSettings* frame_settings = 888 JxlEncoderFrameSettingsCreate(enc, nullptr); 889 EXPECT_EQ( 890 JXL_ENC_SUCCESS, 891 JxlEncoderAddImageFrame(frame_settings, &format, 892 static_cast<const void*>(original_bytes.data()), 893 original_bytes.size())); 894 JxlEncoderCloseInput(enc); 895 896 std::vector<uint8_t> compressed; 897 EncodeWithEncoder(enc, &compressed); 898 JxlEncoderDestroy(enc); 899 900 JxlDecoder* dec = JxlDecoderCreate(nullptr); 901 EXPECT_NE(nullptr, dec); 902 903 const uint8_t* next_in = compressed.data(); 904 size_t avail_in = compressed.size(); 905 906 EXPECT_EQ(JXL_DEC_SUCCESS, 907 JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO | 908 JXL_DEC_COLOR_ENCODING | 909 JXL_DEC_FULL_IMAGE)); 910 911 JxlDecoderSetInput(dec, next_in, avail_in); 912 EXPECT_EQ(JXL_DEC_BASIC_INFO, JxlDecoderProcessInput(dec)); 913 size_t buffer_size; 914 EXPECT_EQ(JXL_DEC_SUCCESS, 915 JxlDecoderImageOutBufferSize(dec, &format, &buffer_size)); 916 EXPECT_EQ(buffer_size, original_bytes.size()); 917 918 JxlBasicInfo info; 919 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBasicInfo(dec, &info)); 920 EXPECT_EQ(xsize, info.xsize); 921 EXPECT_EQ(ysize, info.ysize); 922 923 EXPECT_EQ(JXL_DEC_COLOR_ENCODING, JxlDecoderProcessInput(dec)); 924 925 size_t dec_icc_size; 926 EXPECT_EQ(JXL_DEC_SUCCESS, 927 JxlDecoderGetICCProfileSize(dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL, 928 &dec_icc_size)); 929 EXPECT_EQ(icc.size(), dec_icc_size); 930 std::vector<uint8_t> dec_icc(dec_icc_size); 931 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetColorAsICCProfile( 932 dec, JXL_COLOR_PROFILE_TARGET_ORIGINAL, 933 dec_icc.data(), dec_icc.size())); 934 935 std::vector<uint8_t> decoded_bytes(buffer_size); 936 937 EXPECT_EQ(JXL_DEC_NEED_IMAGE_OUT_BUFFER, JxlDecoderProcessInput(dec)); 938 939 EXPECT_EQ(JXL_DEC_SUCCESS, 940 JxlDecoderSetImageOutBuffer(dec, &format, decoded_bytes.data(), 941 decoded_bytes.size())); 942 943 EXPECT_EQ(JXL_DEC_FULL_IMAGE, JxlDecoderProcessInput(dec)); 944 945 EXPECT_EQ(icc, dec_icc); 946 947 JxlDecoderDestroy(dec); 948 } 949 950 JXL_TRANSCODE_JPEG_TEST(RoundtripTest, TestJPEGReconstruction) { 951 TEST_LIBJPEG_SUPPORT(); 952 const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg"; 953 const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path); 954 955 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 956 JxlEncoderFrameSettings* frame_settings = 957 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 958 959 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc.get(), JXL_TRUE)); 960 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderStoreJPEGMetadata(enc.get(), JXL_TRUE)); 961 EXPECT_EQ(JXL_ENC_SUCCESS, 962 JxlEncoderAddJPEGFrame(frame_settings, orig.data(), orig.size())); 963 JxlEncoderCloseInput(enc.get()); 964 965 std::vector<uint8_t> compressed; 966 EncodeWithEncoder(enc.get(), &compressed); 967 968 JxlDecoderPtr dec = JxlDecoderMake(nullptr); 969 EXPECT_EQ(JXL_DEC_SUCCESS, 970 JxlDecoderSubscribeEvents( 971 dec.get(), JXL_DEC_JPEG_RECONSTRUCTION | JXL_DEC_FULL_IMAGE)); 972 JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size()); 973 EXPECT_EQ(JXL_DEC_JPEG_RECONSTRUCTION, JxlDecoderProcessInput(dec.get())); 974 std::vector<uint8_t> reconstructed_buffer(128); 975 EXPECT_EQ(JXL_DEC_SUCCESS, 976 JxlDecoderSetJPEGBuffer(dec.get(), reconstructed_buffer.data(), 977 reconstructed_buffer.size())); 978 size_t used = 0; 979 JxlDecoderStatus dec_process_result = JXL_DEC_JPEG_NEED_MORE_OUTPUT; 980 while (dec_process_result == JXL_DEC_JPEG_NEED_MORE_OUTPUT) { 981 used = reconstructed_buffer.size() - JxlDecoderReleaseJPEGBuffer(dec.get()); 982 reconstructed_buffer.resize(reconstructed_buffer.size() * 2); 983 EXPECT_EQ( 984 JXL_DEC_SUCCESS, 985 JxlDecoderSetJPEGBuffer(dec.get(), reconstructed_buffer.data() + used, 986 reconstructed_buffer.size() - used)); 987 dec_process_result = JxlDecoderProcessInput(dec.get()); 988 } 989 ASSERT_EQ(JXL_DEC_FULL_IMAGE, dec_process_result); 990 used = reconstructed_buffer.size() - JxlDecoderReleaseJPEGBuffer(dec.get()); 991 ASSERT_EQ(used, orig.size()); 992 EXPECT_EQ(0, memcmp(reconstructed_buffer.data(), orig.data(), used)); 993 } 994 995 JXL_TRANSCODE_JPEG_TEST(RoundtripTest, 996 TestJPEGReconstructionWithIncompleteHuffmanCode) { 997 TEST_LIBJPEG_SUPPORT(); 998 static constexpr uint8_t kJPEGBytes[] = { 999 // SOI 1000 0xff, 0xd8, // 1001 // SOF 1002 0xff, 0xc0, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, // 1003 0x01, 0x11, 0x00, // 1004 // DQT 1005 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x03, 0x02, // 1006 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05, // 1007 0x08, 0x05, 0x05, 0x04, 0x04, 0x05, 0x0a, 0x07, 0x07, 0x06, // 1008 0x08, 0x0c, 0x0a, 0x0c, 0x0c, 0x0b, 0x0a, 0x0b, 0x0b, 0x0d, // 1009 0x0e, 0x12, 0x10, 0x0d, 0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10, // 1010 0x16, 0x10, 0x11, 0x13, 0x14, 0x15, 0x15, 0x15, 0x0c, 0x0f, // 1011 0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15, 0x14, // 1012 // DHT 1013 0xff, 0xc4, 0x01, 0x30, // marker, len 1014 0x00, // slot id DC0 1015 // counts for lengths 1 - 16 (total_count: 12) 1016 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, // 1017 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1018 // symbols: 0 - 11 1019 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // 1020 0x0a, 0x0b, // 1021 0x13, // slot id AC3 1022 // counts for lengths 1 - 16 (total_count: 256) 1023 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, // 1024 0x01, 0x01, 0x01, 0x01, 0x01, 0xf9, // 1025 // symbols: 0 - 255 1026 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // 1027 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, // 1028 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, // 1029 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // 1030 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, // 1031 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, // 1032 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, // 1033 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, // 1034 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, // 1035 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, // 1036 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, // 1037 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // 1038 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, // 1039 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, // 1040 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, // 1041 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, // 1042 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, // 1043 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, // 1044 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, // 1045 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, // 1046 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, // 1047 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, // 1048 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, // 1049 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, // 1050 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, // 1051 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 1052 // SOS 1053 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x03, 0x00, 0x3f, 0x00, // 1054 // entropy coded data (first 16 bit: DC 0, next 9 bits: AC eob) 1055 0xfc, 0xaa, 0x00, 0x00, // 1056 // EOI 1057 0xff, 0xd9, // 1058 }; 1059 static constexpr size_t kJPEGSize = sizeof(kJPEGBytes); 1060 1061 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 1062 JxlEncoderFrameSettings* frame_settings = 1063 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 1064 1065 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc.get(), JXL_TRUE)); 1066 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderStoreJPEGMetadata(enc.get(), JXL_TRUE)); 1067 EXPECT_EQ(JXL_ENC_SUCCESS, 1068 JxlEncoderAddJPEGFrame(frame_settings, kJPEGBytes, kJPEGSize)); 1069 JxlEncoderCloseInput(enc.get()); 1070 1071 std::vector<uint8_t> compressed; 1072 EncodeWithEncoder(enc.get(), &compressed); 1073 1074 JxlDecoderPtr dec = JxlDecoderMake(nullptr); 1075 EXPECT_EQ(JXL_DEC_SUCCESS, 1076 JxlDecoderSubscribeEvents( 1077 dec.get(), JXL_DEC_JPEG_RECONSTRUCTION | JXL_DEC_FULL_IMAGE)); 1078 JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size()); 1079 EXPECT_EQ(JXL_DEC_JPEG_RECONSTRUCTION, JxlDecoderProcessInput(dec.get())); 1080 std::vector<uint8_t> reconstructed_buffer(128); 1081 EXPECT_EQ(JXL_DEC_SUCCESS, 1082 JxlDecoderSetJPEGBuffer(dec.get(), reconstructed_buffer.data(), 1083 reconstructed_buffer.size())); 1084 size_t used = 0; 1085 JxlDecoderStatus dec_process_result = JXL_DEC_JPEG_NEED_MORE_OUTPUT; 1086 while (dec_process_result == JXL_DEC_JPEG_NEED_MORE_OUTPUT) { 1087 used = reconstructed_buffer.size() - JxlDecoderReleaseJPEGBuffer(dec.get()); 1088 reconstructed_buffer.resize(reconstructed_buffer.size() * 2); 1089 EXPECT_EQ( 1090 JXL_DEC_SUCCESS, 1091 JxlDecoderSetJPEGBuffer(dec.get(), reconstructed_buffer.data() + used, 1092 reconstructed_buffer.size() - used)); 1093 dec_process_result = JxlDecoderProcessInput(dec.get()); 1094 } 1095 ASSERT_EQ(JXL_DEC_FULL_IMAGE, dec_process_result); 1096 used = reconstructed_buffer.size() - JxlDecoderReleaseJPEGBuffer(dec.get()); 1097 ASSERT_EQ(used, kJPEGSize); 1098 EXPECT_EQ(0, memcmp(reconstructed_buffer.data(), kJPEGBytes, used)); 1099 }