jxl.cc (22268B)
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 "lib/extras/dec/jxl.h" 7 8 #include <jxl/cms.h> 9 #include <jxl/decode.h> 10 #include <jxl/decode_cxx.h> 11 #include <jxl/types.h> 12 13 #include <cinttypes> 14 15 #include "lib/extras/common.h" 16 #include "lib/extras/dec/color_description.h" 17 #include "lib/jxl/base/exif.h" 18 #include "lib/jxl/base/printf_macros.h" 19 #include "lib/jxl/base/status.h" 20 21 namespace jxl { 22 namespace extras { 23 namespace { 24 25 struct BoxProcessor { 26 explicit BoxProcessor(JxlDecoder* dec) : dec_(dec) { Reset(); } 27 28 void InitializeOutput(std::vector<uint8_t>* out) { 29 JXL_ASSERT(out != nullptr); 30 box_data_ = out; 31 AddMoreOutput(); 32 } 33 34 bool AddMoreOutput() { 35 JXL_ASSERT(box_data_ != nullptr); 36 Flush(); 37 static const size_t kBoxOutputChunkSize = 1 << 16; 38 box_data_->resize(box_data_->size() + kBoxOutputChunkSize); 39 next_out_ = box_data_->data() + total_size_; 40 avail_out_ = box_data_->size() - total_size_; 41 if (JXL_DEC_SUCCESS != 42 JxlDecoderSetBoxBuffer(dec_, next_out_, avail_out_)) { 43 fprintf(stderr, "JxlDecoderSetBoxBuffer failed\n"); 44 return false; 45 } 46 return true; 47 } 48 49 void FinalizeOutput() { 50 if (box_data_ == nullptr) return; 51 Flush(); 52 box_data_->resize(total_size_); 53 Reset(); 54 } 55 56 private: 57 JxlDecoder* dec_; 58 std::vector<uint8_t>* box_data_; 59 uint8_t* next_out_; 60 size_t avail_out_; 61 size_t total_size_; 62 63 void Reset() { 64 box_data_ = nullptr; 65 next_out_ = nullptr; 66 avail_out_ = 0; 67 total_size_ = 0; 68 } 69 void Flush() { 70 if (box_data_ == nullptr) return; 71 size_t remaining = JxlDecoderReleaseBoxBuffer(dec_); 72 size_t bytes_written = avail_out_ - remaining; 73 next_out_ += bytes_written; 74 avail_out_ -= bytes_written; 75 total_size_ += bytes_written; 76 } 77 }; 78 79 void SetBitDepthFromDataType(JxlDataType data_type, uint32_t* bits_per_sample, 80 uint32_t* exponent_bits_per_sample) { 81 switch (data_type) { 82 case JXL_TYPE_UINT8: 83 *bits_per_sample = 8; 84 *exponent_bits_per_sample = 0; 85 break; 86 case JXL_TYPE_UINT16: 87 *bits_per_sample = 16; 88 *exponent_bits_per_sample = 0; 89 break; 90 case JXL_TYPE_FLOAT16: 91 *bits_per_sample = 16; 92 *exponent_bits_per_sample = 5; 93 break; 94 case JXL_TYPE_FLOAT: 95 *bits_per_sample = 32; 96 *exponent_bits_per_sample = 8; 97 break; 98 } 99 } 100 101 template <typename T> 102 void UpdateBitDepth(JxlBitDepth bit_depth, JxlDataType data_type, T* info) { 103 if (bit_depth.type == JXL_BIT_DEPTH_FROM_PIXEL_FORMAT) { 104 SetBitDepthFromDataType(data_type, &info->bits_per_sample, 105 &info->exponent_bits_per_sample); 106 } else if (bit_depth.type == JXL_BIT_DEPTH_CUSTOM) { 107 info->bits_per_sample = bit_depth.bits_per_sample; 108 info->exponent_bits_per_sample = bit_depth.exponent_bits_per_sample; 109 } 110 } 111 112 } // namespace 113 114 bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size, 115 const JXLDecompressParams& dparams, size_t* decoded_bytes, 116 PackedPixelFile* ppf, std::vector<uint8_t>* jpeg_bytes) { 117 JxlSignature sig = JxlSignatureCheck(bytes, bytes_size); 118 // silently return false if this is not a JXL file 119 if (sig == JXL_SIG_INVALID) return false; 120 121 auto decoder = JxlDecoderMake(/*memory_manager=*/nullptr); 122 JxlDecoder* dec = decoder.get(); 123 ppf->frames.clear(); 124 125 if (dparams.runner_opaque != nullptr && 126 JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec, dparams.runner, 127 dparams.runner_opaque)) { 128 fprintf(stderr, "JxlEncoderSetParallelRunner failed\n"); 129 return false; 130 } 131 132 JxlPixelFormat format = {}; // Initialize to calm down clang-tidy. 133 std::vector<JxlPixelFormat> accepted_formats = dparams.accepted_formats; 134 135 JxlColorEncoding color_encoding; 136 size_t num_color_channels = 0; 137 if (!dparams.color_space.empty()) { 138 if (!jxl::ParseDescription(dparams.color_space, &color_encoding)) { 139 fprintf(stderr, "Failed to parse color space %s.\n", 140 dparams.color_space.c_str()); 141 return false; 142 } 143 num_color_channels = 144 color_encoding.color_space == JXL_COLOR_SPACE_GRAY ? 1 : 3; 145 } 146 147 bool can_reconstruct_jpeg = false; 148 std::vector<uint8_t> jpeg_data_chunk; 149 if (jpeg_bytes != nullptr) { 150 // This bound is very likely to be enough to hold the entire 151 // reconstructed JPEG, to avoid having to do expensive retries. 152 jpeg_data_chunk.resize(bytes_size * 3 / 2 + 1024); 153 jpeg_bytes->resize(0); 154 } 155 156 int events = (JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE); 157 158 bool max_passes_defined = 159 (dparams.max_passes < std::numeric_limits<uint32_t>::max()); 160 if (max_passes_defined || dparams.max_downsampling > 1) { 161 events |= JXL_DEC_FRAME_PROGRESSION; 162 if (max_passes_defined) { 163 JxlDecoderSetProgressiveDetail(dec, JxlProgressiveDetail::kPasses); 164 } else { 165 JxlDecoderSetProgressiveDetail(dec, JxlProgressiveDetail::kLastPasses); 166 } 167 } 168 if (jpeg_bytes != nullptr) { 169 events |= JXL_DEC_JPEG_RECONSTRUCTION; 170 } else { 171 events |= (JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME | JXL_DEC_PREVIEW_IMAGE | 172 JXL_DEC_BOX); 173 if (accepted_formats.empty()) { 174 // decoding just the metadata, not the pixel data 175 events ^= (JXL_DEC_FULL_IMAGE | JXL_DEC_PREVIEW_IMAGE); 176 } 177 } 178 if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec, events)) { 179 fprintf(stderr, "JxlDecoderSubscribeEvents failed\n"); 180 return false; 181 } 182 if (jpeg_bytes == nullptr) { 183 if (JXL_DEC_SUCCESS != JxlDecoderSetRenderSpotcolors( 184 dec, TO_JXL_BOOL(dparams.render_spotcolors))) { 185 fprintf(stderr, "JxlDecoderSetRenderSpotColors failed\n"); 186 return false; 187 } 188 if (JXL_DEC_SUCCESS != JxlDecoderSetKeepOrientation( 189 dec, TO_JXL_BOOL(dparams.keep_orientation))) { 190 fprintf(stderr, "JxlDecoderSetKeepOrientation failed\n"); 191 return false; 192 } 193 if (JXL_DEC_SUCCESS != JxlDecoderSetUnpremultiplyAlpha( 194 dec, TO_JXL_BOOL(dparams.unpremultiply_alpha))) { 195 fprintf(stderr, "JxlDecoderSetUnpremultiplyAlpha failed\n"); 196 return false; 197 } 198 if (dparams.display_nits > 0 && 199 JXL_DEC_SUCCESS != 200 JxlDecoderSetDesiredIntensityTarget(dec, dparams.display_nits)) { 201 fprintf(stderr, "Decoder failed to set desired intensity target\n"); 202 return false; 203 } 204 if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec, JXL_TRUE)) { 205 fprintf(stderr, "JxlDecoderSetDecompressBoxes failed\n"); 206 return false; 207 } 208 } 209 if (JXL_DEC_SUCCESS != JxlDecoderSetInput(dec, bytes, bytes_size)) { 210 fprintf(stderr, "Decoder failed to set input\n"); 211 return false; 212 } 213 uint32_t progression_index = 0; 214 bool codestream_done = accepted_formats.empty(); 215 BoxProcessor boxes(dec); 216 for (;;) { 217 JxlDecoderStatus status = JxlDecoderProcessInput(dec); 218 if (status == JXL_DEC_ERROR) { 219 fprintf(stderr, "Failed to decode image\n"); 220 return false; 221 } else if (status == JXL_DEC_NEED_MORE_INPUT) { 222 if (codestream_done) { 223 break; 224 } 225 if (dparams.allow_partial_input) { 226 if (JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec)) { 227 fprintf(stderr, 228 "Input file is truncated and there is no preview " 229 "available yet.\n"); 230 return false; 231 } 232 break; 233 } 234 size_t released_size = JxlDecoderReleaseInput(dec); 235 fprintf(stderr, 236 "Input file is truncated (total bytes: %" PRIuS 237 ", processed bytes: %" PRIuS 238 ") and --allow_partial_files is not present.\n", 239 bytes_size, bytes_size - released_size); 240 return false; 241 } else if (status == JXL_DEC_BOX) { 242 boxes.FinalizeOutput(); 243 JxlBoxType box_type; 244 if (JXL_DEC_SUCCESS != JxlDecoderGetBoxType(dec, box_type, JXL_TRUE)) { 245 fprintf(stderr, "JxlDecoderGetBoxType failed\n"); 246 return false; 247 } 248 std::vector<uint8_t>* box_data = nullptr; 249 if (memcmp(box_type, "Exif", 4) == 0) { 250 box_data = &ppf->metadata.exif; 251 } else if (memcmp(box_type, "iptc", 4) == 0) { 252 box_data = &ppf->metadata.iptc; 253 } else if (memcmp(box_type, "jumb", 4) == 0) { 254 box_data = &ppf->metadata.jumbf; 255 } else if (memcmp(box_type, "xml ", 4) == 0) { 256 box_data = &ppf->metadata.xmp; 257 } 258 if (box_data) { 259 boxes.InitializeOutput(box_data); 260 } 261 } else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) { 262 boxes.AddMoreOutput(); 263 } else if (status == JXL_DEC_JPEG_RECONSTRUCTION) { 264 can_reconstruct_jpeg = true; 265 // Decoding to JPEG. 266 if (JXL_DEC_SUCCESS != JxlDecoderSetJPEGBuffer(dec, 267 jpeg_data_chunk.data(), 268 jpeg_data_chunk.size())) { 269 fprintf(stderr, "Decoder failed to set JPEG Buffer\n"); 270 return false; 271 } 272 } else if (status == JXL_DEC_JPEG_NEED_MORE_OUTPUT) { 273 JXL_ASSERT(jpeg_bytes != nullptr); // Help clang-tidy. 274 // Decoded a chunk to JPEG. 275 size_t used_jpeg_output = 276 jpeg_data_chunk.size() - JxlDecoderReleaseJPEGBuffer(dec); 277 jpeg_bytes->insert(jpeg_bytes->end(), jpeg_data_chunk.data(), 278 jpeg_data_chunk.data() + used_jpeg_output); 279 if (used_jpeg_output == 0) { 280 // Chunk is too small. 281 jpeg_data_chunk.resize(jpeg_data_chunk.size() * 2); 282 } 283 if (JXL_DEC_SUCCESS != JxlDecoderSetJPEGBuffer(dec, 284 jpeg_data_chunk.data(), 285 jpeg_data_chunk.size())) { 286 fprintf(stderr, "Decoder failed to set JPEG Buffer\n"); 287 return false; 288 } 289 } else if (status == JXL_DEC_BASIC_INFO) { 290 if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec, &ppf->info)) { 291 fprintf(stderr, "JxlDecoderGetBasicInfo failed\n"); 292 return false; 293 } 294 if (accepted_formats.empty()) continue; 295 if (num_color_channels != 0) { 296 // Mark the change in number of color channels due to the requested 297 // color space. 298 ppf->info.num_color_channels = num_color_channels; 299 } 300 if (dparams.output_bitdepth.type == JXL_BIT_DEPTH_CUSTOM) { 301 // Select format based on custom bits per sample. 302 ppf->info.bits_per_sample = dparams.output_bitdepth.bits_per_sample; 303 } 304 // Select format according to accepted formats. 305 if (!jxl::extras::SelectFormat(accepted_formats, ppf->info, &format)) { 306 fprintf(stderr, "SelectFormat failed\n"); 307 return false; 308 } 309 bool have_alpha = (format.num_channels == 2 || format.num_channels == 4); 310 if (!have_alpha) { 311 // Mark in the basic info that alpha channel was dropped. 312 ppf->info.alpha_bits = 0; 313 } else { 314 if (dparams.unpremultiply_alpha) { 315 // Mark in the basic info that alpha was unpremultiplied. 316 ppf->info.alpha_premultiplied = JXL_FALSE; 317 } 318 } 319 bool alpha_found = false; 320 for (uint32_t i = 0; i < ppf->info.num_extra_channels; ++i) { 321 JxlExtraChannelInfo eci; 322 if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec, i, &eci)) { 323 fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n"); 324 return false; 325 } 326 if (eci.type == JXL_CHANNEL_ALPHA && have_alpha && !alpha_found) { 327 // Skip the first alpha channels because it is already present in the 328 // interleaved image. 329 alpha_found = true; 330 continue; 331 } 332 std::string name(eci.name_length + 1, 0); 333 if (JXL_DEC_SUCCESS != 334 JxlDecoderGetExtraChannelName( 335 dec, i, const_cast<char*>(name.data()), name.size())) { 336 fprintf(stderr, "JxlDecoderGetExtraChannelName failed\n"); 337 return false; 338 } 339 name.resize(eci.name_length); 340 ppf->extra_channels_info.push_back({eci, i, name}); 341 } 342 } else if (status == JXL_DEC_COLOR_ENCODING) { 343 if (!dparams.color_space.empty()) { 344 if (ppf->info.uses_original_profile) { 345 fprintf(stderr, 346 "Warning: --color_space ignored because the image is " 347 "not XYB encoded.\n"); 348 } else { 349 JxlDecoderSetCms(dec, *JxlGetDefaultCms()); 350 if (JXL_DEC_SUCCESS != 351 JxlDecoderSetPreferredColorProfile(dec, &color_encoding)) { 352 fprintf(stderr, "Failed to set color space.\n"); 353 return false; 354 } 355 } 356 } 357 size_t icc_size = 0; 358 JxlColorProfileTarget target = JXL_COLOR_PROFILE_TARGET_DATA; 359 if (JXL_DEC_SUCCESS != 360 JxlDecoderGetICCProfileSize(dec, target, &icc_size)) { 361 fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n"); 362 } 363 if (icc_size != 0) { 364 ppf->primary_color_representation = PackedPixelFile::kIccIsPrimary; 365 ppf->icc.resize(icc_size); 366 if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile( 367 dec, target, ppf->icc.data(), icc_size)) { 368 fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n"); 369 return false; 370 } 371 } 372 if (JXL_DEC_SUCCESS == JxlDecoderGetColorAsEncodedProfile( 373 dec, target, &ppf->color_encoding)) { 374 ppf->primary_color_representation = 375 PackedPixelFile::kColorEncodingIsPrimary; 376 } else { 377 ppf->color_encoding.color_space = JXL_COLOR_SPACE_UNKNOWN; 378 } 379 icc_size = 0; 380 target = JXL_COLOR_PROFILE_TARGET_ORIGINAL; 381 if (JXL_DEC_SUCCESS != 382 JxlDecoderGetICCProfileSize(dec, target, &icc_size)) { 383 fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n"); 384 } 385 if (icc_size != 0) { 386 ppf->orig_icc.resize(icc_size); 387 if (JXL_DEC_SUCCESS != 388 JxlDecoderGetColorAsICCProfile(dec, target, ppf->orig_icc.data(), 389 icc_size)) { 390 fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n"); 391 return false; 392 } 393 } 394 } else if (status == JXL_DEC_FRAME) { 395 jxl::extras::PackedFrame frame(ppf->info.xsize, ppf->info.ysize, format); 396 if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec, &frame.frame_info)) { 397 fprintf(stderr, "JxlDecoderGetFrameHeader failed\n"); 398 return false; 399 } 400 frame.name.resize(frame.frame_info.name_length + 1, 0); 401 if (JXL_DEC_SUCCESS != 402 JxlDecoderGetFrameName(dec, const_cast<char*>(frame.name.data()), 403 frame.name.size())) { 404 fprintf(stderr, "JxlDecoderGetFrameName failed\n"); 405 return false; 406 } 407 frame.name.resize(frame.frame_info.name_length); 408 ppf->frames.emplace_back(std::move(frame)); 409 progression_index = 0; 410 } else if (status == JXL_DEC_FRAME_PROGRESSION) { 411 size_t downsampling = JxlDecoderGetIntendedDownsamplingRatio(dec); 412 if ((max_passes_defined && progression_index >= dparams.max_passes) || 413 (!max_passes_defined && downsampling <= dparams.max_downsampling)) { 414 if (JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec)) { 415 fprintf(stderr, "JxlDecoderFlushImage failed\n"); 416 return false; 417 } 418 if (ppf->frames.back().frame_info.is_last) { 419 break; 420 } 421 if (JXL_DEC_SUCCESS != JxlDecoderSkipCurrentFrame(dec)) { 422 fprintf(stderr, "JxlDecoderSkipCurrentFrame failed\n"); 423 return false; 424 } 425 } 426 ++progression_index; 427 } else if (status == JXL_DEC_NEED_PREVIEW_OUT_BUFFER) { 428 size_t buffer_size; 429 if (JXL_DEC_SUCCESS != 430 JxlDecoderPreviewOutBufferSize(dec, &format, &buffer_size)) { 431 fprintf(stderr, "JxlDecoderPreviewOutBufferSize failed\n"); 432 return false; 433 } 434 ppf->preview_frame = std::unique_ptr<jxl::extras::PackedFrame>( 435 new jxl::extras::PackedFrame(ppf->info.preview.xsize, 436 ppf->info.preview.ysize, format)); 437 if (buffer_size != ppf->preview_frame->color.pixels_size) { 438 fprintf(stderr, "Invalid out buffer size %" PRIuS " %" PRIuS "\n", 439 buffer_size, ppf->preview_frame->color.pixels_size); 440 return false; 441 } 442 if (JXL_DEC_SUCCESS != 443 JxlDecoderSetPreviewOutBuffer( 444 dec, &format, ppf->preview_frame->color.pixels(), buffer_size)) { 445 fprintf(stderr, "JxlDecoderSetPreviewOutBuffer failed\n"); 446 return false; 447 } 448 } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { 449 if (jpeg_bytes != nullptr) { 450 break; 451 } 452 size_t buffer_size; 453 if (JXL_DEC_SUCCESS != 454 JxlDecoderImageOutBufferSize(dec, &format, &buffer_size)) { 455 fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n"); 456 return false; 457 } 458 jxl::extras::PackedFrame& frame = ppf->frames.back(); 459 if (buffer_size != frame.color.pixels_size) { 460 fprintf(stderr, "Invalid out buffer size %" PRIuS " %" PRIuS "\n", 461 buffer_size, frame.color.pixels_size); 462 return false; 463 } 464 465 if (dparams.use_image_callback) { 466 auto callback = [](void* opaque, size_t x, size_t y, size_t num_pixels, 467 const void* pixels) { 468 auto* ppf = reinterpret_cast<jxl::extras::PackedPixelFile*>(opaque); 469 jxl::extras::PackedImage& color = ppf->frames.back().color; 470 uint8_t* pixels_buffer = reinterpret_cast<uint8_t*>(color.pixels()); 471 size_t sample_size = color.pixel_stride(); 472 memcpy(pixels_buffer + (color.stride * y + sample_size * x), pixels, 473 num_pixels * sample_size); 474 }; 475 if (JXL_DEC_SUCCESS != 476 JxlDecoderSetImageOutCallback(dec, &format, callback, ppf)) { 477 fprintf(stderr, "JxlDecoderSetImageOutCallback failed\n"); 478 return false; 479 } 480 } else { 481 if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec, &format, 482 frame.color.pixels(), 483 buffer_size)) { 484 fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n"); 485 return false; 486 } 487 } 488 if (JXL_DEC_SUCCESS != 489 JxlDecoderSetImageOutBitDepth(dec, &dparams.output_bitdepth)) { 490 fprintf(stderr, "JxlDecoderSetImageOutBitDepth failed\n"); 491 return false; 492 } 493 UpdateBitDepth(dparams.output_bitdepth, format.data_type, &ppf->info); 494 bool have_alpha = (format.num_channels == 2 || format.num_channels == 4); 495 if (have_alpha) { 496 // Interleaved alpha channels has the same bit depth as color channels. 497 ppf->info.alpha_bits = ppf->info.bits_per_sample; 498 ppf->info.alpha_exponent_bits = ppf->info.exponent_bits_per_sample; 499 } 500 JxlPixelFormat ec_format = format; 501 ec_format.num_channels = 1; 502 for (auto& eci : ppf->extra_channels_info) { 503 frame.extra_channels.emplace_back(ppf->info.xsize, ppf->info.ysize, 504 ec_format); 505 auto& ec = frame.extra_channels.back(); 506 size_t buffer_size; 507 if (JXL_DEC_SUCCESS != JxlDecoderExtraChannelBufferSize( 508 dec, &ec_format, &buffer_size, eci.index)) { 509 fprintf(stderr, "JxlDecoderExtraChannelBufferSize failed\n"); 510 return false; 511 } 512 if (buffer_size != ec.pixels_size) { 513 fprintf(stderr, 514 "Invalid extra channel buffer size" 515 " %" PRIuS " %" PRIuS "\n", 516 buffer_size, ec.pixels_size); 517 return false; 518 } 519 if (JXL_DEC_SUCCESS != 520 JxlDecoderSetExtraChannelBuffer(dec, &ec_format, ec.pixels(), 521 buffer_size, eci.index)) { 522 fprintf(stderr, "JxlDecoderSetExtraChannelBuffer failed\n"); 523 return false; 524 } 525 UpdateBitDepth(dparams.output_bitdepth, ec_format.data_type, 526 &eci.ec_info); 527 } 528 } else if (status == JXL_DEC_SUCCESS) { 529 // Decoding finished successfully. 530 break; 531 } else if (status == JXL_DEC_PREVIEW_IMAGE) { 532 // Nothing to do. 533 } else if (status == JXL_DEC_FULL_IMAGE) { 534 if (jpeg_bytes != nullptr || ppf->frames.back().frame_info.is_last) { 535 codestream_done = true; 536 } 537 } else { 538 fprintf(stderr, "Error: unexpected status: %d\n", 539 static_cast<int>(status)); 540 return false; 541 } 542 } 543 boxes.FinalizeOutput(); 544 if (!ppf->metadata.exif.empty()) { 545 // Verify that Exif box has a valid TIFF header at the specified offset. 546 // Discard bytes preceding the header. 547 if (ppf->metadata.exif.size() >= 4) { 548 uint32_t offset = LoadBE32(ppf->metadata.exif.data()); 549 if (offset <= ppf->metadata.exif.size() - 8) { 550 std::vector<uint8_t> exif(ppf->metadata.exif.begin() + 4 + offset, 551 ppf->metadata.exif.end()); 552 bool bigendian; 553 if (IsExif(exif, &bigendian)) { 554 ppf->metadata.exif = std::move(exif); 555 } else { 556 fprintf(stderr, "Warning: invalid TIFF header in Exif\n"); 557 } 558 } else { 559 fprintf(stderr, "Warning: invalid Exif offset: %" PRIu32 "\n", offset); 560 } 561 } else { 562 fprintf(stderr, "Warning: invalid Exif length: %" PRIuS "\n", 563 ppf->metadata.exif.size()); 564 } 565 } 566 if (jpeg_bytes != nullptr) { 567 if (!can_reconstruct_jpeg) return false; 568 size_t used_jpeg_output = 569 jpeg_data_chunk.size() - JxlDecoderReleaseJPEGBuffer(dec); 570 jpeg_bytes->insert(jpeg_bytes->end(), jpeg_data_chunk.data(), 571 jpeg_data_chunk.data() + used_jpeg_output); 572 } 573 if (decoded_bytes) { 574 *decoded_bytes = bytes_size - JxlDecoderReleaseInput(dec); 575 } 576 return true; 577 } 578 579 } // namespace extras 580 } // namespace jxl