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