encode_test.cc (85112B)
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/cms_interface.h> 8 #include <jxl/codestream_header.h> 9 #include <jxl/color_encoding.h> 10 #include <jxl/decode.h> 11 #include <jxl/decode_cxx.h> 12 #include <jxl/encode.h> 13 #include <jxl/encode_cxx.h> 14 #include <jxl/memory_manager.h> 15 #include <jxl/types.h> 16 17 #include <cstddef> 18 #include <cstdint> 19 #include <cstdio> 20 #include <cstdlib> 21 #include <cstring> 22 #include <mutex> 23 #include <ostream> 24 #include <set> 25 #include <string> 26 #include <tuple> 27 #include <utility> 28 #include <vector> 29 30 #include "lib/extras/codec.h" 31 #include "lib/extras/dec/jxl.h" 32 #include "lib/extras/metrics.h" 33 #include "lib/extras/packed_image.h" 34 #include "lib/jxl/base/byte_order.h" 35 #include "lib/jxl/base/c_callback_support.h" 36 #include "lib/jxl/base/override.h" 37 #include "lib/jxl/base/span.h" 38 #include "lib/jxl/base/status.h" 39 #include "lib/jxl/common.h" // JXL_HIGH_PRECISION 40 #include "lib/jxl/enc_params.h" 41 #include "lib/jxl/encode_internal.h" 42 #include "lib/jxl/modular/options.h" 43 #include "lib/jxl/test_image.h" 44 #include "lib/jxl/test_utils.h" 45 #include "lib/jxl/testing.h" 46 47 namespace { 48 bool SameDecodedPixels(const std::vector<uint8_t>& compressed0, 49 const std::vector<uint8_t>& compressed1) { 50 jxl::extras::JXLDecompressParams dparams; 51 dparams.accepted_formats = { 52 {3, JXL_TYPE_UINT16, JXL_LITTLE_ENDIAN, 0}, 53 {4, JXL_TYPE_UINT16, JXL_LITTLE_ENDIAN, 0}, 54 }; 55 jxl::extras::PackedPixelFile ppf0; 56 EXPECT_TRUE(DecodeImageJXL(compressed0.data(), compressed0.size(), dparams, 57 nullptr, &ppf0, nullptr)); 58 jxl::extras::PackedPixelFile ppf1; 59 EXPECT_TRUE(DecodeImageJXL(compressed1.data(), compressed1.size(), dparams, 60 nullptr, &ppf1, nullptr)); 61 return jxl::test::SamePixels(ppf0, ppf1); 62 } 63 } // namespace 64 65 TEST(EncodeTest, AddFrameAfterCloseInputTest) { 66 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 67 EXPECT_NE(nullptr, enc.get()); 68 69 JxlEncoderCloseInput(enc.get()); 70 71 size_t xsize = 64; 72 size_t ysize = 64; 73 JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; 74 std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0); 75 76 jxl::CodecInOut input_io = 77 jxl::test::SomeTestImageToCodecInOut(pixels, 4, xsize, ysize); 78 79 JxlBasicInfo basic_info; 80 jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); 81 basic_info.xsize = xsize; 82 basic_info.ysize = ysize; 83 basic_info.uses_original_profile = 0; 84 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); 85 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); 86 JxlColorEncoding color_encoding; 87 JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3); 88 JxlColorEncodingSetToSRGB(&color_encoding, is_gray); 89 EXPECT_EQ(JXL_ENC_SUCCESS, 90 JxlEncoderSetColorEncoding(enc.get(), &color_encoding)); 91 JxlEncoderFrameSettings* frame_settings = 92 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 93 EXPECT_EQ(JXL_ENC_ERROR, 94 JxlEncoderAddImageFrame(frame_settings, &pixel_format, 95 pixels.data(), pixels.size())); 96 } 97 98 TEST(EncodeTest, AddJPEGAfterCloseTest) { 99 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 100 EXPECT_NE(nullptr, enc.get()); 101 102 JxlEncoderCloseInput(enc.get()); 103 104 const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg"; 105 const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path); 106 107 JxlEncoderFrameSettings* frame_settings = 108 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 109 110 EXPECT_EQ(JXL_ENC_ERROR, 111 JxlEncoderAddJPEGFrame(frame_settings, orig.data(), orig.size())); 112 } 113 114 TEST(EncodeTest, AddFrameBeforeBasicInfoTest) { 115 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 116 EXPECT_NE(nullptr, enc.get()); 117 118 size_t xsize = 64; 119 size_t ysize = 64; 120 JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; 121 std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0); 122 123 jxl::CodecInOut input_io = 124 jxl::test::SomeTestImageToCodecInOut(pixels, 4, xsize, ysize); 125 126 JxlColorEncoding color_encoding; 127 JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3); 128 JxlColorEncodingSetToSRGB(&color_encoding, is_gray); 129 EXPECT_EQ(JXL_ENC_ERROR, 130 JxlEncoderSetColorEncoding(enc.get(), &color_encoding)); 131 JxlEncoderFrameSettings* frame_settings = 132 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 133 EXPECT_EQ(JXL_ENC_ERROR, 134 JxlEncoderAddImageFrame(frame_settings, &pixel_format, 135 pixels.data(), pixels.size())); 136 } 137 138 TEST(EncodeTest, DefaultAllocTest) { 139 JxlEncoder* enc = JxlEncoderCreate(nullptr); 140 EXPECT_NE(nullptr, enc); 141 JxlEncoderDestroy(enc); 142 } 143 144 TEST(EncodeTest, CustomAllocTest) { 145 struct CalledCounters { 146 int allocs = 0; 147 int frees = 0; 148 } counters; 149 150 JxlMemoryManager mm; 151 mm.opaque = &counters; 152 mm.alloc = [](void* opaque, size_t size) { 153 reinterpret_cast<CalledCounters*>(opaque)->allocs++; 154 return malloc(size); 155 }; 156 mm.free = [](void* opaque, void* address) { 157 reinterpret_cast<CalledCounters*>(opaque)->frees++; 158 free(address); 159 }; 160 161 { 162 JxlEncoderPtr enc = JxlEncoderMake(&mm); 163 EXPECT_NE(nullptr, enc.get()); 164 EXPECT_LE(1, counters.allocs); 165 EXPECT_EQ(0, counters.frees); 166 } 167 EXPECT_LE(1, counters.frees); 168 } 169 170 TEST(EncodeTest, DefaultParallelRunnerTest) { 171 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 172 EXPECT_NE(nullptr, enc.get()); 173 EXPECT_EQ(JXL_ENC_SUCCESS, 174 JxlEncoderSetParallelRunner(enc.get(), nullptr, nullptr)); 175 } 176 177 void VerifyFrameEncoding(size_t xsize, size_t ysize, JxlEncoder* enc, 178 const JxlEncoderFrameSettings* frame_settings, 179 size_t max_compressed_size, 180 bool lossy_use_original_profile) { 181 JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; 182 std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0); 183 184 jxl::CodecInOut input_io = 185 jxl::test::SomeTestImageToCodecInOut(pixels, 4, xsize, ysize); 186 187 JxlBasicInfo basic_info; 188 jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); 189 basic_info.xsize = xsize; 190 basic_info.ysize = ysize; 191 if (frame_settings->values.lossless || lossy_use_original_profile) { 192 basic_info.uses_original_profile = JXL_TRUE; 193 } else { 194 basic_info.uses_original_profile = JXL_FALSE; 195 } 196 // 16-bit alpha means this requires level 10 197 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10)); 198 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info)); 199 JxlColorEncoding color_encoding; 200 JxlColorEncodingSetToSRGB(&color_encoding, JXL_TRUE); 201 EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetColorEncoding(enc, &color_encoding)); 202 JxlColorEncodingSetToSRGB(&color_encoding, JXL_FALSE); 203 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetColorEncoding(enc, &color_encoding)); 204 pixel_format.num_channels = 1; 205 EXPECT_EQ(JXL_ENC_ERROR, 206 JxlEncoderAddImageFrame(frame_settings, &pixel_format, 207 pixels.data(), pixels.size())); 208 pixel_format.num_channels = 4; 209 EXPECT_EQ(JXL_ENC_SUCCESS, 210 JxlEncoderAddImageFrame(frame_settings, &pixel_format, 211 pixels.data(), pixels.size())); 212 JxlEncoderCloseInput(enc); 213 214 std::vector<uint8_t> compressed = std::vector<uint8_t>(64); 215 uint8_t* next_out = compressed.data(); 216 size_t avail_out = compressed.size() - (next_out - compressed.data()); 217 JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; 218 while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 219 process_result = JxlEncoderProcessOutput(enc, &next_out, &avail_out); 220 if (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 221 size_t offset = next_out - compressed.data(); 222 compressed.resize(compressed.size() * 2); 223 next_out = compressed.data() + offset; 224 avail_out = compressed.size() - offset; 225 } 226 } 227 compressed.resize(next_out - compressed.data()); 228 EXPECT_LE(compressed.size(), max_compressed_size); 229 EXPECT_EQ(JXL_ENC_SUCCESS, process_result); 230 jxl::CodecInOut decoded_io; 231 EXPECT_TRUE(jxl::test::DecodeFile( 232 {}, jxl::Bytes(compressed.data(), compressed.size()), &decoded_io)); 233 234 static constexpr double kMaxButteraugli = 235 #if JXL_HIGH_PRECISION 236 1.84; 237 #else 238 8.7; 239 #endif 240 EXPECT_LE( 241 ComputeDistance2(input_io.Main(), decoded_io.Main(), *JxlGetDefaultCms()), 242 kMaxButteraugli); 243 } 244 245 void VerifyFrameEncoding(JxlEncoder* enc, 246 const JxlEncoderFrameSettings* frame_settings) { 247 VerifyFrameEncoding(63, 129, enc, frame_settings, 27000, 248 /*lossy_use_original_profile=*/false); 249 } 250 251 TEST(EncodeTest, FrameEncodingTest) { 252 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 253 EXPECT_NE(nullptr, enc.get()); 254 VerifyFrameEncoding(enc.get(), 255 JxlEncoderFrameSettingsCreate(enc.get(), nullptr)); 256 } 257 258 TEST(EncodeTest, EncoderResetTest) { 259 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 260 EXPECT_NE(nullptr, enc.get()); 261 VerifyFrameEncoding(50, 200, enc.get(), 262 JxlEncoderFrameSettingsCreate(enc.get(), nullptr), 4550, 263 false); 264 // Encoder should become reusable for a new image from scratch after using 265 // reset. 266 JxlEncoderReset(enc.get()); 267 VerifyFrameEncoding(157, 77, enc.get(), 268 JxlEncoderFrameSettingsCreate(enc.get(), nullptr), 2300, 269 false); 270 } 271 272 TEST(EncodeTest, CmsTest) { 273 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 274 EXPECT_NE(nullptr, enc.get()); 275 bool cms_called = false; 276 JxlCmsInterface cms = *JxlGetDefaultCms(); 277 struct InitData { 278 void* original_init_data; 279 jpegxl_cms_init_func original_init; 280 bool* cms_called; 281 }; 282 InitData init_data = {/*original_init_data=*/cms.init_data, 283 /*original_init=*/cms.init, 284 /*cms_called=*/&cms_called}; 285 cms.init_data = &init_data; 286 cms.init = +[](void* raw_init_data, size_t num_threads, 287 size_t pixels_per_thread, const JxlColorProfile* input_profile, 288 const JxlColorProfile* output_profile, 289 float intensity_target) { 290 const InitData* init_data = static_cast<const InitData*>(raw_init_data); 291 *init_data->cms_called = true; 292 return init_data->original_init(init_data->original_init_data, num_threads, 293 pixels_per_thread, input_profile, 294 output_profile, intensity_target); 295 }; 296 JxlEncoderSetCms(enc.get(), cms); 297 JxlEncoderFrameSettings* frame_settings = 298 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 299 JxlEncoderSetFrameLossless(frame_settings, JXL_FALSE); 300 ASSERT_EQ(JXL_ENC_SUCCESS, 301 JxlEncoderFrameSettingsSetOption(frame_settings, 302 JXL_ENC_FRAME_SETTING_EFFORT, 8)); 303 VerifyFrameEncoding(enc.get(), frame_settings); 304 EXPECT_TRUE(cms_called); 305 } 306 307 TEST(EncodeTest, frame_settingsTest) { 308 { 309 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 310 EXPECT_NE(nullptr, enc.get()); 311 JxlEncoderFrameSettings* frame_settings = 312 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 313 EXPECT_EQ(JXL_ENC_SUCCESS, 314 JxlEncoderFrameSettingsSetOption( 315 frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 5)); 316 VerifyFrameEncoding(enc.get(), frame_settings); 317 EXPECT_EQ(jxl::SpeedTier::kHare, enc->last_used_cparams.speed_tier); 318 } 319 320 { 321 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 322 EXPECT_NE(nullptr, enc.get()); 323 JxlEncoderFrameSettings* frame_settings = 324 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 325 const size_t nb_options = 23; 326 const JxlEncoderFrameSettingId options[nb_options] = { 327 JXL_ENC_FRAME_SETTING_EFFORT, 328 JXL_ENC_FRAME_SETTING_BROTLI_EFFORT, 329 JXL_ENC_FRAME_SETTING_DECODING_SPEED, 330 JXL_ENC_FRAME_SETTING_RESAMPLING, 331 JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING, 332 JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED, 333 JXL_ENC_FRAME_SETTING_EPF, 334 JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X, 335 JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y, 336 JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, 337 JXL_ENC_FRAME_SETTING_PALETTE_COLORS, 338 JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM, 339 JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE, 340 JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE, 341 JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, 342 JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS, 343 JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL, 344 JXL_ENC_FRAME_INDEX_BOX, 345 JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES, 346 JXL_ENC_FRAME_SETTING_BUFFERING, 347 JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF, 348 JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP, 349 JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF}; 350 const int too_low[nb_options] = {0, -2, -2, 3, -2, -2, -2, -2, 351 -2, -2, -2, -2, -2, -2, -2, -2, 352 -2, -1, -2, -2, -2, -2, -2}; 353 const int too_high[nb_options] = {11, 12, 5, 16, 6, 2, 4, -3, 354 -3, 3, 70914, 3, 42, 4, 16, 12, 355 2, 2, 2, 4, 2, 2, 2}; 356 const int in_range[nb_options] = {5, 5, 3, 1, 1, 1, 3, -1, 357 0, 1, -1, -1, 3, 2, 15, -1, 358 -1, 1, 0, 0, -1, -1, -1}; 359 for (size_t i = 0; i < nb_options; i++) { 360 // Lower than currently supported values 361 EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderFrameSettingsSetOption( 362 frame_settings, options[i], too_low[i])); 363 // Higher than currently supported values 364 EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderFrameSettingsSetOption( 365 frame_settings, options[i], too_high[i])); 366 // Using SetFloatOption on integer options 367 EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderFrameSettingsSetFloatOption( 368 frame_settings, options[i], 1.0f)); 369 // Within range of the currently supported values 370 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderFrameSettingsSetOption( 371 frame_settings, options[i], in_range[i])); 372 } 373 // Effort 11 should only work when expert options are allowed 374 EXPECT_EQ(JXL_ENC_ERROR, 375 JxlEncoderFrameSettingsSetOption( 376 frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 11)); 377 JxlEncoderAllowExpertOptions(enc.get()); 378 EXPECT_EQ(JXL_ENC_SUCCESS, 379 JxlEncoderFrameSettingsSetOption( 380 frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 11)); 381 382 // Non-existing option 383 EXPECT_EQ(JXL_ENC_ERROR, 384 JxlEncoderFrameSettingsSetOption( 385 frame_settings, JXL_ENC_FRAME_SETTING_FILL_ENUM, 0)); 386 EXPECT_EQ(JXL_ENC_ERROR, 387 JxlEncoderFrameSettingsSetFloatOption( 388 frame_settings, JXL_ENC_FRAME_SETTING_FILL_ENUM, 0.f)); 389 390 // Float options 391 EXPECT_EQ(JXL_ENC_ERROR, 392 JxlEncoderFrameSettingsSetFloatOption( 393 frame_settings, JXL_ENC_FRAME_SETTING_PHOTON_NOISE, -1.0f)); 394 EXPECT_EQ(JXL_ENC_SUCCESS, 395 JxlEncoderFrameSettingsSetFloatOption( 396 frame_settings, JXL_ENC_FRAME_SETTING_PHOTON_NOISE, 100.0f)); 397 EXPECT_EQ( 398 JXL_ENC_ERROR, 399 JxlEncoderFrameSettingsSetFloatOption( 400 frame_settings, 401 JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, 101.0f)); 402 EXPECT_EQ( 403 JXL_ENC_ERROR, 404 JxlEncoderFrameSettingsSetFloatOption( 405 frame_settings, 406 JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, -2.0f)); 407 EXPECT_EQ( 408 JXL_ENC_SUCCESS, 409 JxlEncoderFrameSettingsSetFloatOption( 410 frame_settings, 411 JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, -1.0f)); 412 EXPECT_EQ(JXL_ENC_ERROR, 413 JxlEncoderFrameSettingsSetFloatOption( 414 frame_settings, 415 JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, 101.0f)); 416 EXPECT_EQ(JXL_ENC_ERROR, 417 JxlEncoderFrameSettingsSetFloatOption( 418 frame_settings, 419 JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, -2.0f)); 420 EXPECT_EQ(JXL_ENC_SUCCESS, 421 JxlEncoderFrameSettingsSetFloatOption( 422 frame_settings, 423 JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, -1.0f)); 424 EXPECT_EQ(JXL_ENC_ERROR, 425 JxlEncoderFrameSettingsSetFloatOption( 426 frame_settings, 427 JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, 101.0f)); 428 EXPECT_EQ(JXL_ENC_ERROR, 429 JxlEncoderFrameSettingsSetFloatOption( 430 frame_settings, 431 JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, -2.0f)); 432 EXPECT_EQ(JXL_ENC_SUCCESS, 433 JxlEncoderFrameSettingsSetFloatOption( 434 frame_settings, 435 JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, -1.0f)); 436 EXPECT_EQ(JXL_ENC_ERROR, 437 JxlEncoderFrameSettingsSetOption( 438 frame_settings, 439 JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, 50.0f)); 440 EXPECT_EQ(JXL_ENC_ERROR, 441 JxlEncoderFrameSettingsSetOption( 442 frame_settings, JXL_ENC_FRAME_SETTING_PHOTON_NOISE, 50.0f)); 443 444 VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3700, false); 445 } 446 447 { 448 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 449 EXPECT_NE(nullptr, enc.get()); 450 JxlEncoderFrameSettings* frame_settings = 451 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 452 EXPECT_EQ(JXL_ENC_SUCCESS, 453 JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE)); 454 VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3000, false); 455 EXPECT_EQ(true, enc->last_used_cparams.IsLossless()); 456 } 457 458 { 459 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 460 EXPECT_NE(nullptr, enc.get()); 461 JxlEncoderFrameSettings* frame_settings = 462 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 463 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetFrameDistance(frame_settings, 0.5)); 464 VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3130, false); 465 EXPECT_EQ(0.5, enc->last_used_cparams.butteraugli_distance); 466 } 467 468 { 469 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 470 JxlEncoderFrameSettings* frame_settings = 471 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 472 // Disallowed negative distance 473 EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetFrameDistance(frame_settings, -1)); 474 } 475 476 { 477 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 478 EXPECT_NE(nullptr, enc.get()); 479 JxlEncoderFrameSettings* frame_settings = 480 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 481 EXPECT_EQ(JXL_ENC_SUCCESS, 482 JxlEncoderFrameSettingsSetOption( 483 frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, 2)); 484 VerifyFrameEncoding(enc.get(), frame_settings); 485 EXPECT_EQ(2u, enc->last_used_cparams.decoding_speed_tier); 486 } 487 488 { 489 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 490 EXPECT_NE(nullptr, enc.get()); 491 JxlEncoderFrameSettings* frame_settings = 492 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 493 EXPECT_EQ(JXL_ENC_ERROR, 494 JxlEncoderFrameSettingsSetOption( 495 frame_settings, JXL_ENC_FRAME_SETTING_GROUP_ORDER, 100)); 496 EXPECT_EQ(JXL_ENC_SUCCESS, 497 JxlEncoderFrameSettingsSetOption( 498 frame_settings, JXL_ENC_FRAME_SETTING_GROUP_ORDER, 1)); 499 EXPECT_EQ( 500 JXL_ENC_SUCCESS, 501 JxlEncoderFrameSettingsSetOption( 502 frame_settings, JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X, 5)); 503 VerifyFrameEncoding(enc.get(), frame_settings); 504 EXPECT_EQ(true, enc->last_used_cparams.centerfirst); 505 EXPECT_EQ(5, enc->last_used_cparams.center_x); 506 } 507 508 { 509 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 510 EXPECT_NE(nullptr, enc.get()); 511 JxlEncoderFrameSettings* frame_settings = 512 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 513 EXPECT_EQ(JXL_ENC_SUCCESS, 514 JxlEncoderFrameSettingsSetOption( 515 frame_settings, JXL_ENC_FRAME_SETTING_RESPONSIVE, 0)); 516 EXPECT_EQ(JXL_ENC_SUCCESS, 517 JxlEncoderFrameSettingsSetOption( 518 frame_settings, JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, 1)); 519 EXPECT_EQ(JXL_ENC_SUCCESS, 520 JxlEncoderFrameSettingsSetOption( 521 frame_settings, JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC, -1)); 522 EXPECT_EQ(JXL_ENC_SUCCESS, 523 JxlEncoderFrameSettingsSetOption( 524 frame_settings, JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, 2)); 525 VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 3430, 526 /*lossy_use_original_profile=*/false); 527 EXPECT_EQ(false, enc->last_used_cparams.responsive); 528 EXPECT_EQ(jxl::Override::kOn, enc->last_used_cparams.progressive_mode); 529 EXPECT_EQ(2, enc->last_used_cparams.progressive_dc); 530 } 531 532 { 533 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 534 EXPECT_NE(nullptr, enc.get()); 535 JxlEncoderFrameSettings* frame_settings = 536 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 537 EXPECT_EQ( 538 JXL_ENC_SUCCESS, 539 JxlEncoderFrameSettingsSetFloatOption( 540 frame_settings, JXL_ENC_FRAME_SETTING_PHOTON_NOISE, 1777.777)); 541 VerifyFrameEncoding(enc.get(), frame_settings); 542 EXPECT_NEAR(1777.777f, enc->last_used_cparams.photon_noise_iso, 1E-4); 543 } 544 545 { 546 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 547 EXPECT_NE(nullptr, enc.get()); 548 JxlEncoderFrameSettings* frame_settings = 549 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 550 EXPECT_EQ(JXL_ENC_SUCCESS, 551 JxlEncoderFrameSettingsSetFloatOption( 552 frame_settings, 553 JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, 55.0f)); 554 EXPECT_EQ(JXL_ENC_SUCCESS, 555 JxlEncoderFrameSettingsSetFloatOption( 556 frame_settings, 557 JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, 25.0f)); 558 EXPECT_EQ(JXL_ENC_SUCCESS, 559 JxlEncoderFrameSettingsSetOption( 560 frame_settings, JXL_ENC_FRAME_SETTING_PALETTE_COLORS, 70000)); 561 EXPECT_EQ(JXL_ENC_SUCCESS, 562 JxlEncoderFrameSettingsSetOption( 563 frame_settings, JXL_ENC_FRAME_SETTING_LOSSY_PALETTE, 1)); 564 VerifyFrameEncoding(enc.get(), frame_settings); 565 EXPECT_NEAR(55.0f, 566 enc->last_used_cparams.channel_colors_pre_transform_percent, 567 1E-6); 568 EXPECT_NEAR(25.0f, enc->last_used_cparams.channel_colors_percent, 1E-6); 569 EXPECT_EQ(70000, enc->last_used_cparams.palette_colors); 570 EXPECT_EQ(true, enc->last_used_cparams.lossy_palette); 571 } 572 573 { 574 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 575 EXPECT_NE(nullptr, enc.get()); 576 JxlEncoderFrameSettings* frame_settings = 577 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 578 EXPECT_EQ( 579 JXL_ENC_SUCCESS, 580 JxlEncoderFrameSettingsSetOption( 581 frame_settings, JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE, 30)); 582 EXPECT_EQ(JXL_ENC_SUCCESS, 583 JxlEncoderFrameSettingsSetOption( 584 frame_settings, JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE, 2)); 585 EXPECT_EQ(JXL_ENC_SUCCESS, 586 JxlEncoderFrameSettingsSetOption( 587 frame_settings, JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, 14)); 588 EXPECT_EQ( 589 JXL_ENC_SUCCESS, 590 JxlEncoderFrameSettingsSetFloatOption( 591 frame_settings, 592 JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, 77.0f)); 593 EXPECT_EQ( 594 JXL_ENC_SUCCESS, 595 JxlEncoderFrameSettingsSetOption( 596 frame_settings, JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS, 7)); 597 VerifyFrameEncoding(enc.get(), frame_settings); 598 EXPECT_EQ(30, enc->last_used_cparams.colorspace); 599 EXPECT_EQ(2, enc->last_used_cparams.modular_group_size_shift); 600 EXPECT_EQ(jxl::Predictor::Best, enc->last_used_cparams.options.predictor); 601 EXPECT_NEAR(0.77f, enc->last_used_cparams.options.nb_repeats, 1E-6); 602 EXPECT_EQ(7, enc->last_used_cparams.options.max_properties); 603 } 604 605 { 606 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 607 EXPECT_NE(nullptr, enc.get()); 608 JxlEncoderFrameSettings* frame_settings = 609 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 610 EXPECT_EQ(JXL_ENC_SUCCESS, 611 JxlEncoderFrameSettingsSetOption( 612 frame_settings, JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL, 0)); 613 VerifyFrameEncoding(enc.get(), frame_settings); 614 EXPECT_EQ(false, enc->last_used_cparams.force_cfl_jpeg_recompression); 615 } 616 617 { 618 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 619 EXPECT_NE(nullptr, enc.get()); 620 JxlEncoderFrameSettings* frame_settings = 621 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 622 EXPECT_EQ(JXL_ENC_SUCCESS, 623 JxlEncoderFrameSettingsSetOption( 624 frame_settings, JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL, 1)); 625 VerifyFrameEncoding(enc.get(), frame_settings); 626 EXPECT_EQ(true, enc->last_used_cparams.force_cfl_jpeg_recompression); 627 } 628 } 629 630 TEST(EncodeTest, LossyEncoderUseOriginalProfileTest) { 631 { 632 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 633 ASSERT_NE(nullptr, enc.get()); 634 JxlEncoderFrameSettings* frame_settings = 635 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 636 VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 7897, true); 637 } 638 { 639 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 640 ASSERT_NE(nullptr, enc.get()); 641 JxlEncoderFrameSettings* frame_settings = 642 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 643 EXPECT_EQ(JXL_ENC_SUCCESS, 644 JxlEncoderFrameSettingsSetOption( 645 frame_settings, JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, 2)); 646 VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 8310, true); 647 } 648 { 649 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 650 ASSERT_NE(nullptr, enc.get()); 651 JxlEncoderFrameSettings* frame_settings = 652 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 653 ASSERT_EQ(JXL_ENC_SUCCESS, 654 JxlEncoderFrameSettingsSetOption( 655 frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 8)); 656 VerifyFrameEncoding(63, 129, enc.get(), frame_settings, 7228, true); 657 } 658 } 659 660 namespace { 661 // Returns a copy of buf from offset to offset+size, or a new zeroed vector if 662 // the result would have been out of bounds taking integer overflow into 663 // account. 664 std::vector<uint8_t> SliceSpan(const jxl::Span<const uint8_t>& buf, 665 size_t offset, size_t size) { 666 if (offset + size >= buf.size()) { 667 return std::vector<uint8_t>(size, 0); 668 } 669 if (offset + size < offset) { 670 return std::vector<uint8_t>(size, 0); 671 } 672 return std::vector<uint8_t>(buf.data() + offset, buf.data() + offset + size); 673 } 674 675 struct Box { 676 // The type of the box. 677 // If "uuid", use extended_type instead 678 char type[4] = {0, 0, 0, 0}; 679 680 // The extended_type is only used when type == "uuid". 681 // Extended types are not used in JXL. However, the box format itself 682 // supports this so they are handled correctly. 683 char extended_type[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 684 685 // Box data. 686 jxl::Span<const uint8_t> data = jxl::Bytes(nullptr, 0); 687 688 // If the size is not given, the datasize extends to the end of the file. 689 // If this field is false, the size field is not encoded when the box is 690 // serialized. 691 bool data_size_given = true; 692 693 // If successful, returns true and sets `in` to be the rest data (if any). 694 // If `in` contains a box with a size larger than `in.size()`, will not 695 // modify `in`, and will return true but the data `Span<uint8_t>` will 696 // remain set to nullptr. 697 // If unsuccessful, returns error and doesn't modify `in`. 698 jxl::Status Decode(jxl::Span<const uint8_t>* in) { 699 // Total box_size including this header itself. 700 uint64_t box_size = LoadBE32(SliceSpan(*in, 0, 4).data()); 701 size_t pos = 4; 702 703 memcpy(type, SliceSpan(*in, pos, 4).data(), 4); 704 pos += 4; 705 706 if (box_size == 1) { 707 // If the size is 1, it indicates extended size read from 64-bit integer. 708 box_size = LoadBE64(SliceSpan(*in, pos, 8).data()); 709 pos += 8; 710 } 711 712 if (!memcmp("uuid", type, 4)) { 713 memcpy(extended_type, SliceSpan(*in, pos, 16).data(), 16); 714 pos += 16; 715 } 716 717 // This is the end of the box header, the box data begins here. Handle 718 // the data size now. 719 const size_t header_size = pos; 720 721 if (box_size != 0) { 722 if (box_size < header_size) { 723 return JXL_FAILURE("Invalid box size"); 724 } 725 if (box_size > in->size()) { 726 // The box is fine, but the input is too short. 727 return true; 728 } 729 data_size_given = true; 730 data = jxl::Bytes(in->data() + header_size, box_size - header_size); 731 } else { 732 data_size_given = false; 733 data = jxl::Bytes(in->data() + header_size, in->size() - header_size); 734 } 735 736 *in = jxl::Bytes(in->data() + header_size + data.size(), 737 in->size() - header_size - data.size()); 738 return true; 739 } 740 }; 741 742 struct Container { 743 std::vector<Box> boxes; 744 745 // If successful, returns true and sets `in` to be the rest data (if any). 746 // If unsuccessful, returns error and doesn't modify `in`. 747 jxl::Status Decode(jxl::Span<const uint8_t>* in) { 748 boxes.clear(); 749 750 Box signature_box; 751 JXL_RETURN_IF_ERROR(signature_box.Decode(in)); 752 if (memcmp("JXL ", signature_box.type, 4) != 0) { 753 return JXL_FAILURE("Invalid magic signature"); 754 } 755 if (signature_box.data.size() != 4) 756 return JXL_FAILURE("Invalid magic signature"); 757 if (signature_box.data[0] != 0xd || signature_box.data[1] != 0xa || 758 signature_box.data[2] != 0x87 || signature_box.data[3] != 0xa) { 759 return JXL_FAILURE("Invalid magic signature"); 760 } 761 762 Box ftyp_box; 763 JXL_RETURN_IF_ERROR(ftyp_box.Decode(in)); 764 if (memcmp("ftyp", ftyp_box.type, 4) != 0) { 765 return JXL_FAILURE("Invalid ftyp"); 766 } 767 if (ftyp_box.data.size() != 12) return JXL_FAILURE("Invalid ftyp"); 768 const char* expected = "jxl \0\0\0\0jxl "; 769 if (memcmp(expected, ftyp_box.data.data(), 12) != 0) 770 return JXL_FAILURE("Invalid ftyp"); 771 772 while (!in->empty()) { 773 Box box = {}; 774 JXL_RETURN_IF_ERROR(box.Decode(in)); 775 if (box.data.data() == nullptr) { 776 // The decoding encountered a box, but not enough data yet. 777 return true; 778 } 779 boxes.emplace_back(box); 780 } 781 782 return true; 783 } 784 }; 785 786 } // namespace 787 788 TEST(EncodeTest, SingleFrameBoundedJXLCTest) { 789 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 790 EXPECT_NE(nullptr, enc.get()); 791 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseContainer(enc.get(), true)); 792 JxlEncoderFrameSettings* frame_settings = 793 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 794 795 size_t xsize = 71; 796 size_t ysize = 23; 797 JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; 798 std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0); 799 800 JxlBasicInfo basic_info; 801 jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); 802 basic_info.xsize = xsize; 803 basic_info.ysize = ysize; 804 basic_info.uses_original_profile = JXL_FALSE; 805 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); 806 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); 807 JxlColorEncoding color_encoding; 808 JxlColorEncodingSetToSRGB(&color_encoding, 809 /*is_gray=*/JXL_FALSE); 810 EXPECT_EQ(JXL_ENC_SUCCESS, 811 JxlEncoderSetColorEncoding(enc.get(), &color_encoding)); 812 EXPECT_EQ(JXL_ENC_SUCCESS, 813 JxlEncoderAddImageFrame(frame_settings, &pixel_format, 814 pixels.data(), pixels.size())); 815 JxlEncoderCloseInput(enc.get()); 816 817 std::vector<uint8_t> compressed = std::vector<uint8_t>(64); 818 uint8_t* next_out = compressed.data(); 819 size_t avail_out = compressed.size() - (next_out - compressed.data()); 820 JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; 821 while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 822 process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out); 823 if (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 824 size_t offset = next_out - compressed.data(); 825 compressed.resize(compressed.size() * 2); 826 next_out = compressed.data() + offset; 827 avail_out = compressed.size() - offset; 828 } 829 } 830 compressed.resize(next_out - compressed.data()); 831 EXPECT_EQ(JXL_ENC_SUCCESS, process_result); 832 833 Container container = {}; 834 jxl::Span<const uint8_t> encoded_span = 835 jxl::Bytes(compressed.data(), compressed.size()); 836 EXPECT_TRUE(container.Decode(&encoded_span)); 837 EXPECT_EQ(0u, encoded_span.size()); 838 bool found_jxlc = false; 839 bool found_jxlp = false; 840 // The encoder is allowed to either emit a jxlc or one or more jxlp. 841 for (size_t i = 0; i < container.boxes.size(); ++i) { 842 if (memcmp("jxlc", container.boxes[i].type, 4) == 0) { 843 EXPECT_EQ(false, found_jxlc); // Max 1 jxlc 844 EXPECT_EQ(false, found_jxlp); // Can't mix jxlc and jxlp 845 found_jxlc = true; 846 } 847 if (memcmp("jxlp", container.boxes[i].type, 4) == 0) { 848 EXPECT_EQ(false, found_jxlc); // Can't mix jxlc and jxlp 849 found_jxlp = true; 850 } 851 // The encoder shouldn't create an unbounded box in this case, with the 852 // single frame it knows the full size in time, so can help make decoding 853 // more efficient by giving the full box size of the final box. 854 EXPECT_EQ(true, container.boxes[i].data_size_given); 855 } 856 EXPECT_EQ(true, found_jxlc || found_jxlp); 857 } 858 859 TEST(EncodeTest, CodestreamLevelTest) { 860 size_t xsize = 64; 861 size_t ysize = 64; 862 JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; 863 std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0); 864 865 jxl::CodecInOut input_io = 866 jxl::test::SomeTestImageToCodecInOut(pixels, 4, xsize, ysize); 867 868 JxlBasicInfo basic_info; 869 jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); 870 basic_info.xsize = xsize; 871 basic_info.ysize = ysize; 872 basic_info.uses_original_profile = 0; 873 874 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 875 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); 876 JxlEncoderFrameSettings* frame_settings = 877 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 878 879 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); 880 JxlColorEncoding color_encoding; 881 JXL_BOOL is_gray = TO_JXL_BOOL(pixel_format.num_channels < 3); 882 JxlColorEncodingSetToSRGB(&color_encoding, is_gray); 883 EXPECT_EQ(JXL_ENC_SUCCESS, 884 JxlEncoderSetColorEncoding(enc.get(), &color_encoding)); 885 EXPECT_EQ(JXL_ENC_SUCCESS, 886 JxlEncoderAddImageFrame(frame_settings, &pixel_format, 887 pixels.data(), pixels.size())); 888 JxlEncoderCloseInput(enc.get()); 889 890 std::vector<uint8_t> compressed = std::vector<uint8_t>(64); 891 uint8_t* next_out = compressed.data(); 892 size_t avail_out = compressed.size() - (next_out - compressed.data()); 893 JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; 894 while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 895 process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out); 896 if (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 897 size_t offset = next_out - compressed.data(); 898 compressed.resize(compressed.size() * 2); 899 next_out = compressed.data() + offset; 900 avail_out = compressed.size() - offset; 901 } 902 } 903 compressed.resize(next_out - compressed.data()); 904 EXPECT_EQ(JXL_ENC_SUCCESS, process_result); 905 906 Container container = {}; 907 jxl::Span<const uint8_t> encoded_span = 908 jxl::Bytes(compressed.data(), compressed.size()); 909 EXPECT_TRUE(container.Decode(&encoded_span)); 910 EXPECT_EQ(0u, encoded_span.size()); 911 EXPECT_EQ(0, memcmp("jxll", container.boxes[0].type, 4)); 912 } 913 914 TEST(EncodeTest, CodestreamLevelVerificationTest) { 915 JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT8, JXL_BIG_ENDIAN, 0}; 916 917 JxlBasicInfo basic_info; 918 jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); 919 basic_info.xsize = 64; 920 basic_info.ysize = 64; 921 basic_info.uses_original_profile = JXL_FALSE; 922 923 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 924 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); 925 926 EXPECT_EQ(5, JxlEncoderGetRequiredCodestreamLevel(enc.get())); 927 928 // Set an image dimension that is too large for level 5, but fits in level 10 929 930 basic_info.xsize = 1ull << 30ull; 931 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 5)); 932 EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); 933 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); 934 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); 935 EXPECT_EQ(10, JxlEncoderGetRequiredCodestreamLevel(enc.get())); 936 937 // Set an image dimension that is too large even for level 10 938 939 basic_info.xsize = 1ull << 31ull; 940 EXPECT_EQ(JXL_ENC_ERROR, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); 941 } 942 943 TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGReconstructionTest)) { 944 const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg"; 945 const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path); 946 947 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 948 JxlEncoderFrameSettings* frame_settings = 949 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 950 951 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderStoreJPEGMetadata(enc.get(), JXL_TRUE)); 952 EXPECT_EQ(JXL_ENC_SUCCESS, 953 JxlEncoderAddJPEGFrame(frame_settings, orig.data(), orig.size())); 954 JxlEncoderCloseInput(enc.get()); 955 956 std::vector<uint8_t> compressed = std::vector<uint8_t>(64); 957 uint8_t* next_out = compressed.data(); 958 size_t avail_out = compressed.size() - (next_out - compressed.data()); 959 JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; 960 while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 961 process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out); 962 if (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 963 size_t offset = next_out - compressed.data(); 964 compressed.resize(compressed.size() * 2); 965 next_out = compressed.data() + offset; 966 avail_out = compressed.size() - offset; 967 } 968 } 969 compressed.resize(next_out - compressed.data()); 970 EXPECT_EQ(JXL_ENC_SUCCESS, process_result); 971 972 jxl::extras::JXLDecompressParams dparams; 973 jxl::test::DefaultAcceptedFormats(dparams); 974 std::vector<uint8_t> decoded_jpeg_bytes; 975 jxl::extras::PackedPixelFile ppf; 976 EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams, 977 nullptr, &ppf, &decoded_jpeg_bytes)); 978 979 EXPECT_EQ(decoded_jpeg_bytes.size(), orig.size()); 980 EXPECT_EQ(0, memcmp(decoded_jpeg_bytes.data(), orig.data(), orig.size())); 981 } 982 983 TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(ProgressiveJPEGReconstructionTest)) { 984 const std::string jpeg_path = "jxl/flower/flower.png.im_q85_420.jpg"; 985 const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path); 986 987 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 988 JxlEncoderFrameSettings* frame_settings = 989 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 990 991 frame_settings->values.cparams.progressive_mode = jxl::Override::kOn; 992 993 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderStoreJPEGMetadata(enc.get(), JXL_TRUE)); 994 EXPECT_EQ(JXL_ENC_SUCCESS, 995 JxlEncoderAddJPEGFrame(frame_settings, orig.data(), orig.size())); 996 JxlEncoderCloseInput(enc.get()); 997 998 std::vector<uint8_t> compressed = std::vector<uint8_t>(64); 999 uint8_t* next_out = compressed.data(); 1000 size_t avail_out = compressed.size() - (next_out - compressed.data()); 1001 JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; 1002 while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 1003 process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out); 1004 if (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 1005 size_t offset = next_out - compressed.data(); 1006 compressed.resize(compressed.size() * 2); 1007 next_out = compressed.data() + offset; 1008 avail_out = compressed.size() - offset; 1009 } 1010 } 1011 compressed.resize(next_out - compressed.data()); 1012 EXPECT_EQ(JXL_ENC_SUCCESS, process_result); 1013 1014 jxl::extras::JXLDecompressParams dparams; 1015 jxl::test::DefaultAcceptedFormats(dparams); 1016 std::vector<uint8_t> decoded_jpeg_bytes; 1017 jxl::extras::PackedPixelFile ppf; 1018 EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams, 1019 nullptr, &ppf, &decoded_jpeg_bytes)); 1020 1021 EXPECT_EQ(decoded_jpeg_bytes.size(), orig.size()); 1022 EXPECT_EQ(0, memcmp(decoded_jpeg_bytes.data(), orig.data(), orig.size())); 1023 } 1024 1025 static void ProcessEncoder(JxlEncoder* enc, std::vector<uint8_t>& compressed, 1026 uint8_t*& next_out, size_t& avail_out) { 1027 JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; 1028 while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 1029 process_result = JxlEncoderProcessOutput(enc, &next_out, &avail_out); 1030 if (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 1031 size_t offset = next_out - compressed.data(); 1032 compressed.resize(compressed.size() * 2); 1033 next_out = compressed.data() + offset; 1034 avail_out = compressed.size() - offset; 1035 } 1036 } 1037 size_t offset = next_out - compressed.data(); 1038 compressed.resize(next_out - compressed.data()); 1039 next_out = compressed.data() + offset; 1040 avail_out = compressed.size() - offset; 1041 EXPECT_EQ(JXL_ENC_SUCCESS, process_result); 1042 } 1043 1044 TEST(EncodeTest, BasicInfoTest) { 1045 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 1046 EXPECT_NE(nullptr, enc.get()); 1047 1048 JxlEncoderFrameSettings* frame_settings = 1049 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 1050 size_t xsize = 1; 1051 size_t ysize = 1; 1052 JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; 1053 std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0); 1054 JxlBasicInfo basic_info; 1055 jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); 1056 basic_info.xsize = xsize; 1057 basic_info.ysize = ysize; 1058 basic_info.uses_original_profile = 0; 1059 basic_info.have_animation = 1; 1060 basic_info.intensity_target = 123.4; 1061 basic_info.min_nits = 5.0; 1062 basic_info.linear_below = 12.7; 1063 basic_info.orientation = JXL_ORIENT_ROTATE_90_CW; 1064 basic_info.intrinsic_xsize = 88; 1065 basic_info.intrinsic_ysize = 99; 1066 basic_info.animation.tps_numerator = 55; 1067 basic_info.animation.tps_denominator = 77; 1068 basic_info.animation.num_loops = 10; 1069 basic_info.animation.have_timecodes = JXL_TRUE; 1070 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); 1071 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); 1072 JxlColorEncoding color_encoding; 1073 JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE); 1074 EXPECT_EQ(JXL_ENC_SUCCESS, 1075 JxlEncoderSetColorEncoding(enc.get(), &color_encoding)); 1076 1077 std::vector<uint8_t> compressed = std::vector<uint8_t>(64); 1078 uint8_t* next_out = compressed.data(); 1079 size_t avail_out = compressed.size() - (next_out - compressed.data()); 1080 EXPECT_EQ(JXL_ENC_SUCCESS, 1081 JxlEncoderAddImageFrame(frame_settings, &pixel_format, 1082 pixels.data(), pixels.size())); 1083 JxlEncoderCloseFrames(enc.get()); 1084 ProcessEncoder(enc.get(), compressed, next_out, avail_out); 1085 1086 // Decode to verify the boxes, we don't decode to pixels, only the boxes. 1087 JxlDecoderPtr dec = JxlDecoderMake(nullptr); 1088 EXPECT_NE(nullptr, dec.get()); 1089 EXPECT_EQ(JXL_DEC_SUCCESS, 1090 JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO)); 1091 // Allow testing the orientation field, without this setting it will be 1092 // overridden to identity. 1093 JxlDecoderSetKeepOrientation(dec.get(), JXL_TRUE); 1094 JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size()); 1095 JxlDecoderCloseInput(dec.get()); 1096 1097 for (;;) { 1098 JxlDecoderStatus status = JxlDecoderProcessInput(dec.get()); 1099 if (status == JXL_DEC_ERROR) { 1100 FAIL(); 1101 } else if (status == JXL_DEC_SUCCESS) { 1102 break; 1103 } else if (status == JXL_DEC_BASIC_INFO) { 1104 JxlBasicInfo basic_info2; 1105 EXPECT_EQ(JXL_DEC_SUCCESS, 1106 JxlDecoderGetBasicInfo(dec.get(), &basic_info2)); 1107 EXPECT_EQ(basic_info.xsize, basic_info2.xsize); 1108 EXPECT_EQ(basic_info.ysize, basic_info2.ysize); 1109 EXPECT_EQ(basic_info.bits_per_sample, basic_info2.bits_per_sample); 1110 EXPECT_EQ(basic_info.exponent_bits_per_sample, 1111 basic_info2.exponent_bits_per_sample); 1112 EXPECT_NEAR(basic_info.intensity_target, basic_info2.intensity_target, 1113 0.5); 1114 EXPECT_NEAR(basic_info.min_nits, basic_info2.min_nits, 0.5); 1115 EXPECT_NEAR(basic_info.linear_below, basic_info2.linear_below, 0.5); 1116 EXPECT_EQ(basic_info.relative_to_max_display, 1117 basic_info2.relative_to_max_display); 1118 EXPECT_EQ(basic_info.uses_original_profile, 1119 basic_info2.uses_original_profile); 1120 EXPECT_EQ(basic_info.orientation, basic_info2.orientation); 1121 EXPECT_EQ(basic_info.intrinsic_xsize, basic_info2.intrinsic_xsize); 1122 EXPECT_EQ(basic_info.intrinsic_ysize, basic_info2.intrinsic_ysize); 1123 EXPECT_EQ(basic_info.num_color_channels, basic_info2.num_color_channels); 1124 // TODO(lode): also test num_extra_channels, but currently there may be a 1125 // mismatch between 0 and 1 if there is alpha, until encoder support for 1126 // extra channels is fully implemented. 1127 EXPECT_EQ(basic_info.alpha_bits, basic_info2.alpha_bits); 1128 EXPECT_EQ(basic_info.alpha_exponent_bits, 1129 basic_info2.alpha_exponent_bits); 1130 EXPECT_EQ(basic_info.alpha_premultiplied, 1131 basic_info2.alpha_premultiplied); 1132 1133 EXPECT_EQ(basic_info.have_preview, basic_info2.have_preview); 1134 if (basic_info.have_preview) { 1135 EXPECT_EQ(basic_info.preview.xsize, basic_info2.preview.xsize); 1136 EXPECT_EQ(basic_info.preview.ysize, basic_info2.preview.ysize); 1137 } 1138 1139 EXPECT_EQ(basic_info.have_animation, basic_info2.have_animation); 1140 if (basic_info.have_animation) { 1141 EXPECT_EQ(basic_info.animation.tps_numerator, 1142 basic_info2.animation.tps_numerator); 1143 EXPECT_EQ(basic_info.animation.tps_denominator, 1144 basic_info2.animation.tps_denominator); 1145 EXPECT_EQ(basic_info.animation.num_loops, 1146 basic_info2.animation.num_loops); 1147 EXPECT_EQ(basic_info.animation.have_timecodes, 1148 basic_info2.animation.have_timecodes); 1149 } 1150 } else { 1151 FAIL(); // unexpected status 1152 } 1153 } 1154 } 1155 1156 TEST(EncodeTest, AnimationHeaderTest) { 1157 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 1158 EXPECT_NE(nullptr, enc.get()); 1159 1160 JxlEncoderFrameSettings* frame_settings = 1161 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 1162 size_t xsize = 1; 1163 size_t ysize = 1; 1164 JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; 1165 std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0); 1166 JxlBasicInfo basic_info; 1167 jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); 1168 basic_info.xsize = xsize; 1169 basic_info.ysize = ysize; 1170 basic_info.have_animation = JXL_TRUE; 1171 basic_info.animation.tps_numerator = 1000; 1172 basic_info.animation.tps_denominator = 1; 1173 basic_info.animation.have_timecodes = JXL_TRUE; 1174 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); 1175 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); 1176 JxlColorEncoding color_encoding; 1177 JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE); 1178 EXPECT_EQ(JXL_ENC_SUCCESS, 1179 JxlEncoderSetColorEncoding(enc.get(), &color_encoding)); 1180 1181 std::string frame_name = "test frame"; 1182 JxlFrameHeader header; 1183 JxlEncoderInitFrameHeader(&header); 1184 header.duration = 50; 1185 header.timecode = 800; 1186 header.layer_info.blend_info.blendmode = JXL_BLEND_BLEND; 1187 header.layer_info.blend_info.source = 2; 1188 header.layer_info.blend_info.clamp = 1; 1189 JxlBlendInfo extra_channel_blend_info; 1190 JxlEncoderInitBlendInfo(&extra_channel_blend_info); 1191 extra_channel_blend_info.blendmode = JXL_BLEND_MULADD; 1192 JxlEncoderSetFrameHeader(frame_settings, &header); 1193 JxlEncoderSetExtraChannelBlendInfo(frame_settings, 0, 1194 &extra_channel_blend_info); 1195 JxlEncoderSetFrameName(frame_settings, frame_name.c_str()); 1196 1197 std::vector<uint8_t> compressed = std::vector<uint8_t>(64); 1198 uint8_t* next_out = compressed.data(); 1199 size_t avail_out = compressed.size() - (next_out - compressed.data()); 1200 EXPECT_EQ(JXL_ENC_SUCCESS, 1201 JxlEncoderAddImageFrame(frame_settings, &pixel_format, 1202 pixels.data(), pixels.size())); 1203 JxlEncoderCloseFrames(enc.get()); 1204 ProcessEncoder(enc.get(), compressed, next_out, avail_out); 1205 1206 // Decode to verify the boxes, we don't decode to pixels, only the boxes. 1207 JxlDecoderPtr dec = JxlDecoderMake(nullptr); 1208 EXPECT_NE(nullptr, dec.get()); 1209 1210 // To test the blend_info fields, coalescing must be set to false in the 1211 // decoder. 1212 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetCoalescing(dec.get(), JXL_FALSE)); 1213 EXPECT_EQ(JXL_DEC_SUCCESS, 1214 JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_FRAME)); 1215 JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size()); 1216 JxlDecoderCloseInput(dec.get()); 1217 1218 bool seen_frame = false; 1219 1220 for (;;) { 1221 JxlDecoderStatus status = JxlDecoderProcessInput(dec.get()); 1222 if (status == JXL_DEC_ERROR) { 1223 FAIL(); 1224 } else if (status == JXL_DEC_SUCCESS) { 1225 break; 1226 } else if (status == JXL_DEC_FRAME) { 1227 seen_frame = true; 1228 JxlFrameHeader header2; 1229 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetFrameHeader(dec.get(), &header2)); 1230 EXPECT_EQ(header.duration, header2.duration); 1231 EXPECT_EQ(header.timecode, header2.timecode); 1232 EXPECT_EQ(header.layer_info.blend_info.blendmode, 1233 header2.layer_info.blend_info.blendmode); 1234 EXPECT_EQ(header.layer_info.blend_info.clamp, 1235 header2.layer_info.blend_info.clamp); 1236 EXPECT_EQ(header.layer_info.blend_info.source, 1237 header2.layer_info.blend_info.source); 1238 EXPECT_EQ(frame_name.size(), header2.name_length); 1239 JxlBlendInfo extra_channel_blend_info2; 1240 JxlDecoderGetExtraChannelBlendInfo(dec.get(), 0, 1241 &extra_channel_blend_info2); 1242 EXPECT_EQ(extra_channel_blend_info.blendmode, 1243 extra_channel_blend_info2.blendmode); 1244 if (header2.name_length > 0) { 1245 std::string frame_name2(header2.name_length + 1, '\0'); 1246 EXPECT_EQ(JXL_DEC_SUCCESS, 1247 JxlDecoderGetFrameName(dec.get(), &frame_name2.front(), 1248 frame_name2.size())); 1249 frame_name2.resize(header2.name_length); 1250 EXPECT_EQ(frame_name, frame_name2); 1251 } 1252 } else { 1253 FAIL(); // unexpected status 1254 } 1255 } 1256 1257 EXPECT_EQ(true, seen_frame); 1258 } 1259 TEST(EncodeTest, CroppedFrameTest) { 1260 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 1261 EXPECT_NE(nullptr, enc.get()); 1262 1263 JxlEncoderFrameSettings* frame_settings = 1264 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 1265 size_t xsize = 300; 1266 size_t ysize = 300; 1267 JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; 1268 std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0); 1269 std::vector<uint8_t> pixels2(pixels.size()); 1270 JxlBasicInfo basic_info; 1271 jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); 1272 // Encoding a 300x300 frame in an image that is only 100x100 1273 basic_info.xsize = 100; 1274 basic_info.ysize = 100; 1275 basic_info.uses_original_profile = JXL_TRUE; 1276 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); 1277 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); 1278 JxlColorEncoding color_encoding; 1279 JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE); 1280 EXPECT_EQ(JXL_ENC_SUCCESS, 1281 JxlEncoderSetColorEncoding(enc.get(), &color_encoding)); 1282 1283 JxlFrameHeader header; 1284 JxlEncoderInitFrameHeader(&header); 1285 header.layer_info.have_crop = JXL_TRUE; 1286 header.layer_info.xsize = xsize; 1287 header.layer_info.ysize = ysize; 1288 header.layer_info.crop_x0 = -50; 1289 header.layer_info.crop_y0 = -250; 1290 JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE); 1291 JxlEncoderSetFrameHeader(frame_settings, &header); 1292 JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 1293 1); 1294 1295 std::vector<uint8_t> compressed = std::vector<uint8_t>(100); 1296 uint8_t* next_out = compressed.data(); 1297 size_t avail_out = compressed.size() - (next_out - compressed.data()); 1298 EXPECT_EQ(JXL_ENC_SUCCESS, 1299 JxlEncoderAddImageFrame(frame_settings, &pixel_format, 1300 pixels.data(), pixels.size())); 1301 JxlEncoderCloseFrames(enc.get()); 1302 ProcessEncoder(enc.get(), compressed, next_out, avail_out); 1303 1304 JxlDecoderPtr dec = JxlDecoderMake(nullptr); 1305 EXPECT_NE(nullptr, dec.get()); 1306 // Non-coalesced decoding so we can get the full uncropped frame 1307 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderSetCoalescing(dec.get(), JXL_FALSE)); 1308 EXPECT_EQ( 1309 JXL_DEC_SUCCESS, 1310 JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE)); 1311 JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size()); 1312 JxlDecoderCloseInput(dec.get()); 1313 1314 bool seen_frame = false; 1315 bool checked_frame = false; 1316 for (;;) { 1317 JxlDecoderStatus status = JxlDecoderProcessInput(dec.get()); 1318 if (status == JXL_DEC_ERROR) { 1319 FAIL(); 1320 } else if (status == JXL_DEC_SUCCESS) { 1321 break; 1322 } else if (status == JXL_DEC_FRAME) { 1323 seen_frame = true; 1324 JxlFrameHeader header2; 1325 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetFrameHeader(dec.get(), &header2)); 1326 EXPECT_EQ(header.layer_info.xsize, header2.layer_info.xsize); 1327 EXPECT_EQ(header.layer_info.ysize, header2.layer_info.ysize); 1328 EXPECT_EQ(header.layer_info.crop_x0, header2.layer_info.crop_x0); 1329 EXPECT_EQ(header.layer_info.crop_y0, header2.layer_info.crop_y0); 1330 } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { 1331 EXPECT_EQ(JXL_DEC_SUCCESS, 1332 JxlDecoderSetImageOutBuffer(dec.get(), &pixel_format, 1333 pixels2.data(), pixels2.size())); 1334 } else if (status == JXL_DEC_FULL_IMAGE) { 1335 EXPECT_EQ(0, memcmp(pixels.data(), pixels2.data(), pixels.size())); 1336 checked_frame = true; 1337 } else { 1338 FAIL(); // unexpected status 1339 } 1340 } 1341 EXPECT_EQ(true, checked_frame); 1342 EXPECT_EQ(true, seen_frame); 1343 } 1344 1345 struct EncodeBoxTest : public testing::TestWithParam<std::tuple<bool, size_t>> { 1346 }; 1347 1348 TEST_P(EncodeBoxTest, JXL_BOXES_TEST(BoxTest)) { 1349 // Test with uncompressed boxes and with brob boxes 1350 bool compress_box = std::get<0>(GetParam()); 1351 size_t xml_box_size = std::get<1>(GetParam()); 1352 // TODO(firsching): use xml_box_size 1353 (void)xml_box_size; 1354 // Tests adding two metadata boxes with the encoder: an exif box before the 1355 // image frame, and an xml box after the image frame. Then verifies the 1356 // decoder can decode them, they are in the expected place, and have the 1357 // correct content after decoding. 1358 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 1359 EXPECT_NE(nullptr, enc.get()); 1360 1361 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderUseBoxes(enc.get())); 1362 1363 JxlEncoderFrameSettings* frame_settings = 1364 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 1365 size_t xsize = 50; 1366 size_t ysize = 17; 1367 JxlPixelFormat pixel_format = {4, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; 1368 std::vector<uint8_t> pixels = jxl::test::GetSomeTestImage(xsize, ysize, 4, 0); 1369 JxlBasicInfo basic_info; 1370 jxl::test::JxlBasicInfoSetFromPixelFormat(&basic_info, &pixel_format); 1371 basic_info.xsize = xsize; 1372 basic_info.ysize = ysize; 1373 basic_info.uses_original_profile = JXL_FALSE; 1374 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc.get(), 10)); 1375 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc.get(), &basic_info)); 1376 JxlColorEncoding color_encoding; 1377 JxlColorEncodingSetToSRGB(&color_encoding, 1378 /*is_gray=*/JXL_FALSE); 1379 EXPECT_EQ(JXL_ENC_SUCCESS, 1380 JxlEncoderSetColorEncoding(enc.get(), &color_encoding)); 1381 1382 std::vector<uint8_t> compressed = std::vector<uint8_t>(64); 1383 uint8_t* next_out = compressed.data(); 1384 size_t avail_out = compressed.size() - (next_out - compressed.data()); 1385 1386 // Add an early metadata box. Also add a valid 4-byte TIFF offset header 1387 // before the fake exif data of these box contents. 1388 constexpr const char* exif_test_string = "\0\0\0\0exif test data"; 1389 const uint8_t* exif_data = reinterpret_cast<const uint8_t*>(exif_test_string); 1390 // Skip the 4 zeroes for strlen 1391 const size_t exif_size = 4 + strlen(exif_test_string + 4); 1392 JxlEncoderAddBox(enc.get(), "Exif", exif_data, exif_size, 1393 TO_JXL_BOOL(compress_box)); 1394 1395 // Write to output 1396 ProcessEncoder(enc.get(), compressed, next_out, avail_out); 1397 1398 // Add image frame 1399 EXPECT_EQ(JXL_ENC_SUCCESS, 1400 JxlEncoderAddImageFrame(frame_settings, &pixel_format, 1401 pixels.data(), pixels.size())); 1402 // Indicate this is the last frame 1403 JxlEncoderCloseFrames(enc.get()); 1404 1405 // Write to output 1406 ProcessEncoder(enc.get(), compressed, next_out, avail_out); 1407 1408 // Add a late metadata box 1409 constexpr const char* xml_test_string = "<some random xml data>"; 1410 const uint8_t* xml_data = reinterpret_cast<const uint8_t*>(xml_test_string); 1411 size_t xml_size = strlen(xml_test_string); 1412 JxlEncoderAddBox(enc.get(), "XML ", xml_data, xml_size, 1413 TO_JXL_BOOL(compress_box)); 1414 1415 // Indicate this is the last box 1416 JxlEncoderCloseBoxes(enc.get()); 1417 1418 // Write to output 1419 ProcessEncoder(enc.get(), compressed, next_out, avail_out); 1420 1421 // Decode to verify the boxes, we don't decode to pixels, only the boxes. 1422 JxlDecoderPtr dec = JxlDecoderMake(nullptr); 1423 EXPECT_NE(nullptr, dec.get()); 1424 1425 if (compress_box) { 1426 EXPECT_EQ(JXL_DEC_SUCCESS, 1427 JxlDecoderSetDecompressBoxes(dec.get(), JXL_TRUE)); 1428 } 1429 1430 EXPECT_EQ(JXL_DEC_SUCCESS, 1431 JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_FRAME | JXL_DEC_BOX)); 1432 1433 JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size()); 1434 JxlDecoderCloseInput(dec.get()); 1435 1436 std::vector<uint8_t> dec_exif_box(exif_size); 1437 std::vector<uint8_t> dec_xml_box(xml_size); 1438 1439 for (bool post_frame = false;;) { 1440 JxlDecoderStatus status = JxlDecoderProcessInput(dec.get()); 1441 if (status == JXL_DEC_ERROR) { 1442 FAIL(); 1443 } else if (status == JXL_DEC_SUCCESS) { 1444 EXPECT_EQ(0, JxlDecoderReleaseBoxBuffer(dec.get())); 1445 break; 1446 } else if (status == JXL_DEC_FRAME) { 1447 post_frame = true; 1448 } else if (status == JXL_DEC_BOX) { 1449 // Since we gave the exif/xml box output buffer of the exact known 1450 // correct size, 0 bytes should be released. Same when no buffer was 1451 // set. 1452 EXPECT_EQ(0, JxlDecoderReleaseBoxBuffer(dec.get())); 1453 JxlBoxType type; 1454 EXPECT_EQ(JXL_DEC_SUCCESS, JxlDecoderGetBoxType(dec.get(), type, true)); 1455 if (!memcmp(type, "Exif", 4)) { 1456 // This box should have been encoded before the image frame 1457 EXPECT_EQ(false, post_frame); 1458 JxlDecoderSetBoxBuffer(dec.get(), dec_exif_box.data(), 1459 dec_exif_box.size()); 1460 } else if (!memcmp(type, "XML ", 4)) { 1461 // This box should have been encoded after the image frame 1462 EXPECT_EQ(true, post_frame); 1463 JxlDecoderSetBoxBuffer(dec.get(), dec_xml_box.data(), 1464 dec_xml_box.size()); 1465 } 1466 } else { 1467 FAIL(); // unexpected status 1468 } 1469 } 1470 1471 EXPECT_EQ(0, memcmp(exif_data, dec_exif_box.data(), exif_size)); 1472 EXPECT_EQ(0, memcmp(xml_data, dec_xml_box.data(), xml_size)); 1473 } 1474 1475 std::string nameBoxTest( 1476 const ::testing::TestParamInfo<std::tuple<bool, size_t>>& info) { 1477 return (std::get<0>(info.param) ? "C" : "Unc") + std::string("ompressed") + 1478 "_BoxSize_" + std::to_string((std::get<1>(info.param))); 1479 } 1480 1481 JXL_GTEST_INSTANTIATE_TEST_SUITE_P( 1482 EncodeBoxParamsTest, EncodeBoxTest, 1483 testing::Combine(testing::Values(false, true), 1484 testing::Values(256, 1485 jxl::kLargeBoxContentSizeThreshold + 77)), 1486 nameBoxTest); 1487 1488 TEST(EncodeTest, JXL_TRANSCODE_JPEG_TEST(JPEGFrameTest)) { 1489 TEST_LIBJPEG_SUPPORT(); 1490 for (int skip_basic_info = 0; skip_basic_info < 2; skip_basic_info++) { 1491 for (int skip_color_encoding = 0; skip_color_encoding < 2; 1492 skip_color_encoding++) { 1493 // cannot set color encoding if basic info is not set 1494 if (skip_basic_info && !skip_color_encoding) continue; 1495 const std::string jpeg_path = "jxl/flower/flower_cropped.jpg"; 1496 const std::vector<uint8_t> orig = jxl::test::ReadTestData(jpeg_path); 1497 jxl::CodecInOut orig_io; 1498 ASSERT_TRUE(SetFromBytes(jxl::Bytes(orig), &orig_io, 1499 /*pool=*/nullptr)); 1500 1501 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 1502 JxlEncoderFrameSettings* frame_settings = 1503 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 1504 JxlEncoderFrameSettingsSetOption(frame_settings, 1505 JXL_ENC_FRAME_SETTING_EFFORT, 1); 1506 if (!skip_basic_info) { 1507 JxlBasicInfo basic_info; 1508 JxlEncoderInitBasicInfo(&basic_info); 1509 basic_info.xsize = orig_io.xsize(); 1510 basic_info.ysize = orig_io.ysize(); 1511 basic_info.uses_original_profile = JXL_TRUE; 1512 EXPECT_EQ(JXL_ENC_SUCCESS, 1513 JxlEncoderSetBasicInfo(enc.get(), &basic_info)); 1514 } 1515 if (!skip_color_encoding) { 1516 JxlColorEncoding color_encoding; 1517 JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE); 1518 EXPECT_EQ(JXL_ENC_SUCCESS, 1519 JxlEncoderSetColorEncoding(enc.get(), &color_encoding)); 1520 } 1521 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderAddJPEGFrame( 1522 frame_settings, orig.data(), orig.size())); 1523 JxlEncoderCloseInput(enc.get()); 1524 1525 std::vector<uint8_t> compressed = std::vector<uint8_t>(64); 1526 uint8_t* next_out = compressed.data(); 1527 size_t avail_out = compressed.size() - (next_out - compressed.data()); 1528 JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT; 1529 while (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 1530 process_result = 1531 JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out); 1532 if (process_result == JXL_ENC_NEED_MORE_OUTPUT) { 1533 size_t offset = next_out - compressed.data(); 1534 compressed.resize(compressed.size() * 2); 1535 next_out = compressed.data() + offset; 1536 avail_out = compressed.size() - offset; 1537 } 1538 } 1539 compressed.resize(next_out - compressed.data()); 1540 EXPECT_EQ(JXL_ENC_SUCCESS, process_result); 1541 1542 jxl::CodecInOut decoded_io; 1543 EXPECT_TRUE(jxl::test::DecodeFile( 1544 {}, jxl::Bytes(compressed.data(), compressed.size()), &decoded_io)); 1545 1546 EXPECT_LE(ComputeDistance2(orig_io.Main(), decoded_io.Main(), 1547 *JxlGetDefaultCms()), 1548 3.5); 1549 } 1550 } 1551 } 1552 1553 namespace { 1554 class JxlStreamingAdapter { 1555 public: 1556 JxlStreamingAdapter(JxlEncoder* encoder, bool return_large_buffers, 1557 bool can_seek) 1558 : return_large_buffers_(return_large_buffers) { 1559 struct JxlEncoderOutputProcessor output_processor; 1560 output_processor.opaque = this; 1561 output_processor.get_buffer = 1562 METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::GetBuffer); 1563 if (can_seek) { 1564 output_processor.seek = METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::Seek); 1565 } else { 1566 output_processor.seek = nullptr; 1567 } 1568 output_processor.set_finalized_position = 1569 METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::SetFinalizedPosition); 1570 output_processor.release_buffer = 1571 METHOD_TO_C_CALLBACK(&JxlStreamingAdapter::ReleaseBuffer); 1572 EXPECT_EQ(JxlEncoderSetOutputProcessor(encoder, output_processor), 1573 JXL_ENC_SUCCESS); 1574 } 1575 1576 std::vector<uint8_t> output() && { 1577 output_.resize(position_); 1578 return std::move(output_); 1579 } 1580 1581 void* GetBuffer(size_t* size) { 1582 if (!return_large_buffers_) { 1583 *size = 1; 1584 } 1585 if (position_ + *size > output_.size()) { 1586 output_.resize(position_ + *size, 0xDA); 1587 } 1588 if (return_large_buffers_) { 1589 *size = output_.size() - position_; 1590 } 1591 return output_.data() + position_; 1592 } 1593 1594 void ReleaseBuffer(size_t written_bytes) { 1595 // TODO(veluca): check no more bytes were written. 1596 Seek(position_ + written_bytes); 1597 } 1598 1599 void Seek(uint64_t position) { 1600 EXPECT_GE(position, finalized_position_); 1601 position_ = position; 1602 } 1603 1604 void SetFinalizedPosition(uint64_t finalized_position) { 1605 EXPECT_GE(finalized_position, finalized_position_); 1606 finalized_position_ = finalized_position; 1607 EXPECT_GE(position_, finalized_position_); 1608 } 1609 1610 void CheckFinalWatermarkPosition() const { 1611 EXPECT_EQ(finalized_position_, position_); 1612 } 1613 1614 private: 1615 std::vector<uint8_t> output_; 1616 size_t position_ = 0; 1617 size_t finalized_position_ = 0; 1618 bool return_large_buffers_; 1619 }; 1620 1621 class JxlChunkedFrameInputSourceAdapter { 1622 private: 1623 static const void* GetDataAt(const jxl::extras::PackedImage& image, 1624 size_t xpos, size_t ypos, size_t* row_offset) { 1625 JxlDataType data_type = image.format.data_type; 1626 size_t num_channels = image.format.num_channels; 1627 size_t bytes_per_pixel = 1628 num_channels * jxl::extras::PackedImage::BitsPerChannel(data_type) / 8; 1629 *row_offset = image.stride; 1630 return static_cast<uint8_t*>(image.pixels()) + bytes_per_pixel * xpos + 1631 ypos * image.stride; 1632 } 1633 1634 public: 1635 // Constructor to wrap the image data or any other state 1636 explicit JxlChunkedFrameInputSourceAdapter( 1637 jxl::extras::PackedImage color_channel, 1638 jxl::extras::PackedImage extra_channel) 1639 : colorchannel_(std::move(color_channel)), 1640 extra_channel_(std::move(extra_channel)) {} 1641 ~JxlChunkedFrameInputSourceAdapter() { EXPECT_TRUE(active_buffers_.empty()); } 1642 1643 void GetColorChannelsPixelFormat(JxlPixelFormat* pixel_format) { 1644 *pixel_format = colorchannel_.format; 1645 } 1646 1647 const void* GetColorChannelDataAt(size_t xpos, size_t ypos, size_t xsize, 1648 size_t ysize, size_t* row_offset) { 1649 const void* p = GetDataAt(colorchannel_, xpos, ypos, row_offset); 1650 std::lock_guard<std::mutex> lock(mtx_); 1651 active_buffers_.insert(p); 1652 return p; 1653 } 1654 1655 void GetExtraChannelPixelFormat(size_t ec_index, 1656 JxlPixelFormat* pixel_format) { 1657 // In this test, we we the same color channel data, so `ec_index` is never 1658 // used 1659 *pixel_format = extra_channel_.format; 1660 } 1661 1662 const void* GetExtraChannelDataAt(size_t ec_index, size_t xpos, size_t ypos, 1663 size_t xsize, size_t ysize, 1664 size_t* row_offset) { 1665 // In this test, we we the same color channel data, so `ec_index` is never 1666 // used 1667 const void* p = GetDataAt(extra_channel_, xpos, ypos, row_offset); 1668 std::lock_guard<std::mutex> lock(mtx_); 1669 active_buffers_.insert(p); 1670 return p; 1671 } 1672 void ReleaseCurrentData(const void* buffer) { 1673 std::lock_guard<std::mutex> lock(mtx_); 1674 auto iter = active_buffers_.find(buffer); 1675 if (iter != active_buffers_.end()) { 1676 active_buffers_.erase(iter); 1677 } 1678 } 1679 1680 JxlChunkedFrameInputSource GetInputSource() { 1681 return JxlChunkedFrameInputSource{ 1682 this, 1683 METHOD_TO_C_CALLBACK( 1684 &JxlChunkedFrameInputSourceAdapter::GetColorChannelsPixelFormat), 1685 METHOD_TO_C_CALLBACK( 1686 &JxlChunkedFrameInputSourceAdapter::GetColorChannelDataAt), 1687 METHOD_TO_C_CALLBACK( 1688 &JxlChunkedFrameInputSourceAdapter::GetExtraChannelPixelFormat), 1689 METHOD_TO_C_CALLBACK( 1690 &JxlChunkedFrameInputSourceAdapter::GetExtraChannelDataAt), 1691 METHOD_TO_C_CALLBACK( 1692 &JxlChunkedFrameInputSourceAdapter::ReleaseCurrentData)}; 1693 } 1694 1695 private: 1696 const jxl::extras::PackedImage colorchannel_; 1697 const jxl::extras::PackedImage extra_channel_; 1698 std::mutex mtx_; 1699 std::set<const void*> active_buffers_; 1700 }; 1701 1702 struct StreamingTestParam { 1703 size_t bitmask; 1704 bool use_container() const { return static_cast<bool>(bitmask & 0x1); } 1705 bool return_large_buffers() const { return static_cast<bool>(bitmask & 0x2); } 1706 bool multiple_frames() const { return static_cast<bool>(bitmask & 0x4); } 1707 bool fast_lossless() const { return static_cast<bool>(bitmask & 0x8); } 1708 bool can_seek() const { return static_cast<bool>(bitmask & 0x10); } 1709 bool with_extra_channels() const { return static_cast<bool>(bitmask & 0x20); } 1710 bool color_includes_alpha() const { 1711 return static_cast<bool>(bitmask & 0x40); 1712 } 1713 bool onegroup() const { return static_cast<bool>(bitmask & 0x80); } 1714 1715 bool is_lossless() const { return fast_lossless(); } 1716 1717 static std::vector<StreamingTestParam> All() { 1718 std::vector<StreamingTestParam> params; 1719 for (size_t bitmask = 0; bitmask < 256; bitmask++) { 1720 params.push_back(StreamingTestParam{bitmask}); 1721 } 1722 return params; 1723 } 1724 }; 1725 1726 std::ostream& operator<<(std::ostream& out, StreamingTestParam p) { 1727 if (p.use_container()) { 1728 out << "WithContainer_"; 1729 } else { 1730 out << "WithoutContainer_"; 1731 } 1732 if (p.return_large_buffers()) { 1733 out << "WithLargeBuffers_"; 1734 } else { 1735 out << "WithSmallBuffers_"; 1736 } 1737 if (p.multiple_frames()) out << "WithMultipleFrames_"; 1738 if (p.fast_lossless()) out << "FastLossless_"; 1739 if (!p.can_seek()) { 1740 out << "CannotSeek_"; 1741 } else { 1742 out << "CanSeek_"; 1743 } 1744 if (p.with_extra_channels()) { 1745 out << "WithExtraChannels_"; 1746 } else { 1747 out << "WithoutExtraChannels_"; 1748 } 1749 if (p.color_includes_alpha()) { 1750 out << "ColorIncludesAlpha_"; 1751 } else { 1752 out << "ColorWithoutAlpha_"; 1753 } 1754 if (p.onegroup()) { 1755 out << "OneGroup_"; 1756 } else { 1757 out << "MultiGroup_"; 1758 } 1759 return out; 1760 } 1761 1762 } // namespace 1763 1764 class EncoderStreamingTest : public testing::TestWithParam<StreamingTestParam> { 1765 public: 1766 static void SetupImage(const StreamingTestParam& p, size_t xsize, 1767 size_t ysize, size_t num_channels, 1768 size_t bits_per_sample, jxl::test::TestImage& image) { 1769 image.SetDimensions(xsize, ysize) 1770 .SetDataType(JXL_TYPE_UINT8) 1771 .SetChannels(num_channels) 1772 .SetAllBitDepths(bits_per_sample); 1773 if (p.onegroup()) { 1774 image.SetRowAlignment(128); 1775 } 1776 image.AddFrame().RandomFill(); 1777 } 1778 static void SetUpBasicInfo(JxlBasicInfo& basic_info, size_t xsize, 1779 size_t ysize, size_t number_extra_channels, 1780 bool include_alpha, bool is_lossless) { 1781 basic_info.xsize = xsize; 1782 basic_info.ysize = ysize; 1783 basic_info.num_extra_channels = 1784 number_extra_channels + (include_alpha ? 1 : 0); 1785 basic_info.uses_original_profile = TO_JXL_BOOL(is_lossless); 1786 } 1787 1788 static void SetupEncoder(JxlEncoderFrameSettings* frame_settings, 1789 const StreamingTestParam& p, 1790 const JxlBasicInfo& basic_info, 1791 size_t number_extra_channels, bool streaming) { 1792 JxlEncoderStruct* enc = frame_settings->enc; 1793 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info)); 1794 if (p.fast_lossless()) { 1795 EXPECT_EQ(JXL_ENC_SUCCESS, 1796 JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE)); 1797 EXPECT_EQ(JXL_ENC_SUCCESS, 1798 JxlEncoderFrameSettingsSetOption( 1799 frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, 1)); 1800 } 1801 JxlColorEncoding color_encoding; 1802 JxlColorEncodingSetToSRGB(&color_encoding, /*is_gray=*/JXL_FALSE); 1803 EXPECT_EQ(JXL_ENC_SUCCESS, 1804 JxlEncoderSetColorEncoding(enc, &color_encoding)); 1805 EXPECT_EQ(JXL_ENC_SUCCESS, 1806 JxlEncoderFrameSettingsSetOption(frame_settings, 1807 JXL_ENC_FRAME_SETTING_BUFFERING, 1808 streaming ? 3 : 0)); 1809 EXPECT_EQ(JXL_ENC_SUCCESS, 1810 JxlEncoderFrameSettingsSetOption( 1811 frame_settings, 1812 JXL_ENC_FRAME_SETTING_USE_FULL_IMAGE_HEURISTICS, 0)); 1813 if (p.use_container()) { 1814 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetCodestreamLevel(enc, 10)); 1815 } 1816 for (size_t i = 0; i < number_extra_channels; i++) { 1817 JxlExtraChannelInfo channel_info; 1818 JxlExtraChannelType channel_type = JXL_CHANNEL_THERMAL; 1819 JxlEncoderInitExtraChannelInfo(channel_type, &channel_info); 1820 EXPECT_EQ(JXL_ENC_SUCCESS, 1821 JxlEncoderSetExtraChannelInfo(enc, i, &channel_info)); 1822 } 1823 } 1824 1825 static void SetupInputNonStreaming(JxlEncoderFrameSettings* frame_settings, 1826 const StreamingTestParam& p, 1827 size_t number_extra_channels, 1828 const jxl::extras::PackedImage& frame, 1829 const jxl::extras::PackedImage& ec_frame) { 1830 size_t frame_count = static_cast<int>(p.multiple_frames()) + 1; 1831 for (size_t i = 0; i < frame_count; i++) { 1832 { 1833 // Copy pixel data here because it is only guaranteed to be available 1834 // during the call to JxlEncoderAddImageFrame(). 1835 std::vector<uint8_t> pixels(frame.pixels_size); 1836 memcpy(pixels.data(), frame.pixels(), pixels.size()); 1837 EXPECT_EQ(JXL_ENC_SUCCESS, 1838 JxlEncoderAddImageFrame(frame_settings, &frame.format, 1839 pixels.data(), pixels.size())); 1840 } 1841 for (size_t i = 0; i < number_extra_channels; i++) { 1842 // Copy pixel data here because it is only guaranteed to be available 1843 // during the call to JxlEncoderSetExtraChannelBuffer(). 1844 std::vector<uint8_t> ec_pixels(ec_frame.pixels_size); 1845 memcpy(ec_pixels.data(), ec_frame.pixels(), ec_pixels.size()); 1846 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetExtraChannelBuffer( 1847 frame_settings, &ec_frame.format, 1848 ec_pixels.data(), ec_pixels.size(), i)); 1849 } 1850 } 1851 JxlEncoderCloseInput(frame_settings->enc); 1852 } 1853 1854 static void SetupInputStreaming(JxlEncoderFrameSettings* frame_settings, 1855 const StreamingTestParam& p, 1856 size_t number_extra_channels, 1857 const jxl::extras::PackedImage& frame, 1858 const jxl::extras::PackedImage& ec_frame) { 1859 size_t frame_count = static_cast<int>(p.multiple_frames()) + 1; 1860 for (size_t i = 0; i < frame_count; i++) { 1861 // Create local copy of pixels and adapter because they are only 1862 // guarantted to be available during the JxlEncoderAddChunkedFrame() call. 1863 JxlChunkedFrameInputSourceAdapter chunked_frame_adapter(frame.Copy(), 1864 ec_frame.Copy()); 1865 EXPECT_EQ(JXL_ENC_SUCCESS, 1866 JxlEncoderAddChunkedFrame( 1867 // should only set `JXL_TRUE` in the lass pass of the loop 1868 frame_settings, i + 1 == frame_count ? JXL_TRUE : JXL_FALSE, 1869 chunked_frame_adapter.GetInputSource())); 1870 } 1871 } 1872 }; 1873 1874 TEST_P(EncoderStreamingTest, OutputCallback) { 1875 const StreamingTestParam p = GetParam(); 1876 size_t xsize = p.onegroup() ? 17 : 257; 1877 size_t ysize = p.onegroup() ? 19 : 259; 1878 size_t number_extra_channels = p.with_extra_channels() ? 5 : 0; 1879 jxl::test::TestImage image; 1880 SetupImage(p, xsize, ysize, p.color_includes_alpha() ? 4 : 3, 1881 p.use_container() ? 16 : 8, image); 1882 jxl::test::TestImage ec_image; 1883 SetupImage(p, xsize, ysize, 1, 8, ec_image); 1884 const auto& frame = image.ppf().frames[0].color; 1885 const auto& ec_frame = ec_image.ppf().frames[0].color; 1886 JxlBasicInfo basic_info = image.ppf().info; 1887 SetUpBasicInfo(basic_info, xsize, ysize, number_extra_channels, 1888 p.color_includes_alpha(), p.is_lossless()); 1889 1890 std::vector<uint8_t> compressed = std::vector<uint8_t>(64); 1891 // without sreaming 1892 { 1893 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 1894 ASSERT_NE(nullptr, enc.get()); 1895 JxlEncoderFrameSettings* frame_settings = 1896 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 1897 SetupEncoder(frame_settings, p, basic_info, number_extra_channels, false); 1898 SetupInputNonStreaming(frame_settings, p, number_extra_channels, frame, 1899 ec_frame); 1900 uint8_t* next_out = compressed.data(); 1901 size_t avail_out = compressed.size(); 1902 ProcessEncoder(enc.get(), compressed, next_out, avail_out); 1903 } 1904 1905 std::vector<uint8_t> streaming_compressed; 1906 // with streaming 1907 { 1908 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 1909 ASSERT_NE(nullptr, enc.get()); 1910 JxlEncoderFrameSettings* frame_settings = 1911 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 1912 SetupEncoder(frame_settings, p, basic_info, number_extra_channels, true); 1913 SetupInputNonStreaming(frame_settings, p, number_extra_channels, frame, 1914 ec_frame); 1915 JxlStreamingAdapter streaming_adapter(enc.get(), p.return_large_buffers(), 1916 p.can_seek()); 1917 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderFlushInput(enc.get())); 1918 streaming_adapter.CheckFinalWatermarkPosition(); 1919 streaming_compressed = std::move(streaming_adapter).output(); 1920 } 1921 1922 EXPECT_TRUE(SameDecodedPixels(compressed, streaming_compressed)); 1923 EXPECT_LE(streaming_compressed.size(), compressed.size() + 1024); 1924 } 1925 1926 TEST_P(EncoderStreamingTest, ChunkedFrame) { 1927 const StreamingTestParam p = GetParam(); 1928 size_t xsize = p.onegroup() ? 17 : 257; 1929 size_t ysize = p.onegroup() ? 19 : 259; 1930 size_t number_extra_channels = p.with_extra_channels() ? 5 : 0; 1931 jxl::test::TestImage image; 1932 SetupImage(p, xsize, ysize, p.color_includes_alpha() ? 4 : 3, 1933 p.use_container() ? 16 : 8, image); 1934 jxl::test::TestImage ec_image; 1935 SetupImage(p, xsize, ysize, 1, 8, ec_image); 1936 const auto& frame = image.ppf().frames[0].color; 1937 const auto& ec_frame = ec_image.ppf().frames[0].color; 1938 JxlBasicInfo basic_info = image.ppf().info; 1939 SetUpBasicInfo(basic_info, xsize, ysize, number_extra_channels, 1940 p.color_includes_alpha(), p.is_lossless()); 1941 std::vector<uint8_t> compressed = std::vector<uint8_t>(64); 1942 std::vector<uint8_t> streaming_compressed = std::vector<uint8_t>(64); 1943 1944 // without streaming 1945 { 1946 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 1947 ASSERT_NE(nullptr, enc.get()); 1948 JxlEncoderFrameSettings* frame_settings = 1949 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 1950 SetupEncoder(frame_settings, p, basic_info, number_extra_channels, false); 1951 SetupInputNonStreaming(frame_settings, p, number_extra_channels, frame, 1952 ec_frame); 1953 uint8_t* next_out = compressed.data(); 1954 size_t avail_out = compressed.size(); 1955 ProcessEncoder(enc.get(), compressed, next_out, avail_out); 1956 } 1957 1958 // with streaming 1959 { 1960 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 1961 ASSERT_NE(nullptr, enc.get()); 1962 JxlEncoderFrameSettings* frame_settings = 1963 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 1964 SetupEncoder(frame_settings, p, basic_info, number_extra_channels, true); 1965 SetupInputStreaming(frame_settings, p, number_extra_channels, frame, 1966 ec_frame); 1967 uint8_t* next_out = streaming_compressed.data(); 1968 size_t avail_out = streaming_compressed.size(); 1969 ProcessEncoder(enc.get(), streaming_compressed, next_out, avail_out); 1970 } 1971 1972 EXPECT_TRUE(SameDecodedPixels(compressed, streaming_compressed)); 1973 EXPECT_LE(streaming_compressed.size(), compressed.size() + 1024); 1974 } 1975 1976 TEST_P(EncoderStreamingTest, ChunkedAndOutputCallback) { 1977 const StreamingTestParam p = GetParam(); 1978 size_t xsize = p.onegroup() ? 17 : 257; 1979 size_t ysize = p.onegroup() ? 19 : 259; 1980 size_t number_extra_channels = p.with_extra_channels() ? 5 : 0; 1981 jxl::test::TestImage image; 1982 SetupImage(p, xsize, ysize, p.color_includes_alpha() ? 4 : 3, 1983 p.use_container() ? 16 : 8, image); 1984 jxl::test::TestImage ec_image; 1985 SetupImage(p, xsize, ysize, 1, 8, ec_image); 1986 const auto& frame = image.ppf().frames[0].color; 1987 const auto& ec_frame = ec_image.ppf().frames[0].color; 1988 JxlBasicInfo basic_info = image.ppf().info; 1989 SetUpBasicInfo(basic_info, xsize, ysize, number_extra_channels, 1990 p.color_includes_alpha(), p.is_lossless()); 1991 1992 std::vector<uint8_t> compressed = std::vector<uint8_t>(64); 1993 1994 // without streaming 1995 { 1996 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 1997 ASSERT_NE(nullptr, enc.get()); 1998 JxlEncoderFrameSettings* frame_settings = 1999 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 2000 SetupEncoder(frame_settings, p, basic_info, number_extra_channels, false); 2001 SetupInputNonStreaming(frame_settings, p, number_extra_channels, frame, 2002 ec_frame); 2003 uint8_t* next_out = compressed.data(); 2004 size_t avail_out = compressed.size(); 2005 ProcessEncoder(enc.get(), compressed, next_out, avail_out); 2006 } 2007 2008 std::vector<uint8_t> streaming_compressed; 2009 // with streaming 2010 { 2011 JxlEncoderPtr enc = JxlEncoderMake(nullptr); 2012 ASSERT_NE(nullptr, enc.get()); 2013 JxlEncoderFrameSettings* frame_settings = 2014 JxlEncoderFrameSettingsCreate(enc.get(), nullptr); 2015 SetupEncoder(frame_settings, p, basic_info, number_extra_channels, true); 2016 JxlStreamingAdapter streaming_adapter = 2017 JxlStreamingAdapter(enc.get(), p.return_large_buffers(), p.can_seek()); 2018 SetupInputStreaming(frame_settings, p, number_extra_channels, frame, 2019 ec_frame); 2020 streaming_adapter.CheckFinalWatermarkPosition(); 2021 streaming_compressed = std::move(streaming_adapter).output(); 2022 } 2023 2024 EXPECT_TRUE(SameDecodedPixels(compressed, streaming_compressed)); 2025 EXPECT_LE(streaming_compressed.size(), compressed.size() + 1024); 2026 } 2027 2028 JXL_GTEST_INSTANTIATE_TEST_SUITE_P( 2029 EncoderStreamingTest, EncoderStreamingTest, 2030 testing::ValuesIn(StreamingTestParam::All())); 2031 2032 TEST(EncoderTest, CMYK) { 2033 size_t xsize = 257; 2034 size_t ysize = 259; 2035 jxl::test::TestImage image; 2036 image.SetDimensions(xsize, ysize) 2037 .SetDataType(JXL_TYPE_UINT8) 2038 .SetChannels(3) 2039 .SetAllBitDepths(8); 2040 image.AddFrame().RandomFill(); 2041 jxl::test::TestImage ec_image; 2042 ec_image.SetDataType(JXL_TYPE_UINT8) 2043 .SetDimensions(xsize, ysize) 2044 .SetChannels(1) 2045 .SetAllBitDepths(8); 2046 ec_image.AddFrame().RandomFill(); 2047 const auto& frame = image.ppf().frames[0].color; 2048 const auto& ec_frame = ec_image.ppf().frames[0].color; 2049 JxlBasicInfo basic_info = image.ppf().info; 2050 basic_info.xsize = xsize; 2051 basic_info.ysize = ysize; 2052 basic_info.num_extra_channels = 1; 2053 basic_info.uses_original_profile = JXL_TRUE; 2054 2055 std::vector<uint8_t> compressed = std::vector<uint8_t>(64); 2056 JxlEncoderPtr enc_ptr = JxlEncoderMake(nullptr); 2057 JxlEncoderStruct* enc = enc_ptr.get(); 2058 ASSERT_NE(nullptr, enc); 2059 JxlEncoderFrameSettings* frame_settings = 2060 JxlEncoderFrameSettingsCreate(enc, nullptr); 2061 2062 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetBasicInfo(enc, &basic_info)); 2063 JxlExtraChannelInfo channel_info; 2064 JxlExtraChannelType channel_type = JXL_CHANNEL_BLACK; 2065 JxlEncoderInitExtraChannelInfo(channel_type, &channel_info); 2066 EXPECT_EQ(JXL_ENC_SUCCESS, 2067 JxlEncoderSetExtraChannelInfo(enc, 0, &channel_info)); 2068 const std::vector<uint8_t> icc = jxl::test::ReadTestData( 2069 "external/Compact-ICC-Profiles/profiles/" 2070 "CGATS001Compat-v2-micro.icc"); 2071 EXPECT_EQ(JXL_ENC_SUCCESS, 2072 JxlEncoderSetICCProfile(enc, icc.data(), icc.size())); 2073 EXPECT_EQ(JXL_ENC_SUCCESS, 2074 JxlEncoderAddImageFrame(frame_settings, &frame.format, 2075 frame.pixels(), frame.pixels_size)); 2076 EXPECT_EQ(JXL_ENC_SUCCESS, JxlEncoderSetExtraChannelBuffer( 2077 frame_settings, &ec_frame.format, 2078 ec_frame.pixels(), ec_frame.pixels_size, 0)); 2079 JxlEncoderCloseInput(frame_settings->enc); 2080 uint8_t* next_out = compressed.data(); 2081 size_t avail_out = compressed.size(); 2082 ProcessEncoder(enc, compressed, next_out, avail_out); 2083 2084 jxl::extras::JXLDecompressParams dparams; 2085 dparams.accepted_formats = { 2086 {3, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0}, 2087 }; 2088 jxl::extras::PackedPixelFile ppf; 2089 EXPECT_TRUE(DecodeImageJXL(compressed.data(), compressed.size(), dparams, 2090 nullptr, &ppf, nullptr)); 2091 }