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