libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

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