tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

jxl.cc (15315B)


      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/enc/jxl.h"
      7 
      8 #include <jxl/codestream_header.h>
      9 #include <jxl/encode.h>
     10 #include <jxl/encode_cxx.h>
     11 #include <jxl/types.h>
     12 
     13 #include <algorithm>
     14 #include <cstddef>
     15 #include <cstdint>
     16 #include <cstdio>
     17 #include <vector>
     18 
     19 #include "lib/extras/packed_image.h"
     20 #include "lib/jxl/base/exif.h"
     21 
     22 namespace jxl {
     23 namespace extras {
     24 
     25 JxlEncoderStatus SetOption(const JXLOption& opt,
     26                           JxlEncoderFrameSettings* settings) {
     27  return opt.is_float
     28             ? JxlEncoderFrameSettingsSetFloatOption(settings, opt.id, opt.fval)
     29             : JxlEncoderFrameSettingsSetOption(settings, opt.id, opt.ival);
     30 }
     31 
     32 bool SetFrameOptions(const std::vector<JXLOption>& options, size_t frame_index,
     33                     size_t* option_idx, JxlEncoderFrameSettings* settings) {
     34  while (*option_idx < options.size()) {
     35    const auto& opt = options[*option_idx];
     36    if (opt.frame_index > frame_index) {
     37      break;
     38    }
     39    if (JXL_ENC_SUCCESS != SetOption(opt, settings)) {
     40      fprintf(stderr, "Setting option id %d failed.\n", opt.id);
     41      return false;
     42    }
     43    (*option_idx)++;
     44  }
     45  return true;
     46 }
     47 
     48 bool SetupFrame(JxlEncoder* enc, JxlEncoderFrameSettings* settings,
     49                const JxlFrameHeader& frame_header,
     50                const JXLCompressParams& params, const PackedPixelFile& ppf,
     51                size_t frame_index, size_t num_alpha_channels,
     52                size_t num_interleaved_alpha, size_t& option_idx) {
     53  if (JXL_ENC_SUCCESS != JxlEncoderSetFrameHeader(settings, &frame_header)) {
     54    fprintf(stderr, "JxlEncoderSetFrameHeader() failed.\n");
     55    return false;
     56  }
     57  if (!SetFrameOptions(params.options, frame_index, &option_idx, settings)) {
     58    return false;
     59  }
     60  if (num_alpha_channels > 0) {
     61    JxlExtraChannelInfo extra_channel_info;
     62    JxlEncoderInitExtraChannelInfo(JXL_CHANNEL_ALPHA, &extra_channel_info);
     63    extra_channel_info.bits_per_sample = ppf.info.alpha_bits;
     64    extra_channel_info.exponent_bits_per_sample = ppf.info.alpha_exponent_bits;
     65    if (params.premultiply != -1) {
     66      if (params.premultiply != 0 && params.premultiply != 1) {
     67        fprintf(stderr, "premultiply must be one of: -1, 0, 1.\n");
     68        return false;
     69      }
     70      extra_channel_info.alpha_premultiplied = params.premultiply;
     71    }
     72    if (JXL_ENC_SUCCESS !=
     73        JxlEncoderSetExtraChannelInfo(enc, 0, &extra_channel_info)) {
     74      fprintf(stderr, "JxlEncoderSetExtraChannelInfo() failed.\n");
     75      return false;
     76    }
     77    // We take the extra channel blend info frame_info, but don't do
     78    // clamping.
     79    JxlBlendInfo extra_channel_blend_info = frame_header.layer_info.blend_info;
     80    extra_channel_blend_info.clamp = JXL_FALSE;
     81    JxlEncoderSetExtraChannelBlendInfo(settings, 0, &extra_channel_blend_info);
     82  }
     83  // Add extra channel info for the rest of the extra channels.
     84  for (size_t i = 0; i < ppf.info.num_extra_channels; ++i) {
     85    if (i < ppf.extra_channels_info.size()) {
     86      const auto& ec_info = ppf.extra_channels_info[i].ec_info;
     87      if (JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelInfo(
     88                                 enc, num_interleaved_alpha + i, &ec_info)) {
     89        fprintf(stderr, "JxlEncoderSetExtraChannelInfo() failed.\n");
     90        return false;
     91      }
     92    }
     93  }
     94  return true;
     95 }
     96 
     97 bool ReadCompressedOutput(JxlEncoder* enc, std::vector<uint8_t>* compressed) {
     98  compressed->clear();
     99  compressed->resize(4096);
    100  uint8_t* next_out = compressed->data();
    101  size_t avail_out = compressed->size() - (next_out - compressed->data());
    102  JxlEncoderStatus result = JXL_ENC_NEED_MORE_OUTPUT;
    103  while (result == JXL_ENC_NEED_MORE_OUTPUT) {
    104    result = JxlEncoderProcessOutput(enc, &next_out, &avail_out);
    105    if (result == JXL_ENC_NEED_MORE_OUTPUT) {
    106      size_t offset = next_out - compressed->data();
    107      compressed->resize(compressed->size() * 2);
    108      next_out = compressed->data() + offset;
    109      avail_out = compressed->size() - offset;
    110    }
    111  }
    112  compressed->resize(next_out - compressed->data());
    113  if (result != JXL_ENC_SUCCESS) {
    114    fprintf(stderr, "JxlEncoderProcessOutput failed.\n");
    115    return false;
    116  }
    117  return true;
    118 }
    119 
    120 bool EncodeImageJXL(const JXLCompressParams& params, const PackedPixelFile& ppf,
    121                    const std::vector<uint8_t>* jpeg_bytes,
    122                    std::vector<uint8_t>* compressed) {
    123  auto encoder = JxlEncoderMake(params.memory_manager);
    124  JxlEncoder* enc = encoder.get();
    125 
    126  if (params.allow_expert_options) {
    127    JxlEncoderAllowExpertOptions(enc);
    128  }
    129 
    130  if (params.runner_opaque != nullptr &&
    131      JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(enc, params.runner,
    132                                                     params.runner_opaque)) {
    133    fprintf(stderr, "JxlEncoderSetParallelRunner failed\n");
    134    return false;
    135  }
    136 
    137  if (params.HasOutputProcessor() &&
    138      JXL_ENC_SUCCESS !=
    139          JxlEncoderSetOutputProcessor(enc, params.output_processor)) {
    140    fprintf(stderr, "JxlEncoderSetOutputProcessorfailed\n");
    141    return false;
    142  }
    143 
    144  auto* settings = JxlEncoderFrameSettingsCreate(enc, nullptr);
    145  size_t option_idx = 0;
    146  if (!SetFrameOptions(params.options, 0, &option_idx, settings)) {
    147    return false;
    148  }
    149  if (JXL_ENC_SUCCESS !=
    150      JxlEncoderSetFrameDistance(settings, params.distance)) {
    151    fprintf(stderr, "Setting frame distance failed.\n");
    152    return false;
    153  }
    154  if (params.debug_image) {
    155    JxlEncoderSetDebugImageCallback(settings, params.debug_image,
    156                                    params.debug_image_opaque);
    157  }
    158  if (params.stats) {
    159    JxlEncoderCollectStats(settings, params.stats);
    160  }
    161 
    162  bool has_jpeg_bytes = (jpeg_bytes != nullptr);
    163  bool use_boxes = !ppf.metadata.exif.empty() || !ppf.metadata.xmp.empty() ||
    164                   !ppf.metadata.jhgm.empty() || !ppf.metadata.jumbf.empty() ||
    165                   !ppf.metadata.iptc.empty();
    166  bool use_container = params.use_container || use_boxes ||
    167                       (has_jpeg_bytes && params.jpeg_store_metadata);
    168 
    169  if (JXL_ENC_SUCCESS !=
    170      JxlEncoderUseContainer(enc, static_cast<int>(use_container))) {
    171    fprintf(stderr, "JxlEncoderUseContainer failed.\n");
    172    return false;
    173  }
    174 
    175  if (has_jpeg_bytes) {
    176    if (params.jpeg_store_metadata &&
    177        JXL_ENC_SUCCESS != JxlEncoderStoreJPEGMetadata(enc, JXL_TRUE)) {
    178      fprintf(stderr, "Storing JPEG metadata failed.\n");
    179      return false;
    180    }
    181    if (params.jpeg_store_metadata && params.jpeg_strip_exif) {
    182      fprintf(stderr,
    183              "Cannot store metadata and strip exif at the same time.\n");
    184      return false;
    185    }
    186    if (params.jpeg_store_metadata && params.jpeg_strip_xmp) {
    187      fprintf(stderr,
    188              "Cannot store metadata and strip xmp at the same time.\n");
    189      return false;
    190    }
    191    if (!params.jpeg_store_metadata && params.jpeg_strip_exif) {
    192      JxlEncoderFrameSettingsSetOption(settings,
    193                                       JXL_ENC_FRAME_SETTING_JPEG_KEEP_EXIF, 0);
    194    }
    195    if (!params.jpeg_store_metadata && params.jpeg_strip_xmp) {
    196      JxlEncoderFrameSettingsSetOption(settings,
    197                                       JXL_ENC_FRAME_SETTING_JPEG_KEEP_XMP, 0);
    198    }
    199    if (params.jpeg_strip_jumbf) {
    200      JxlEncoderFrameSettingsSetOption(
    201          settings, JXL_ENC_FRAME_SETTING_JPEG_KEEP_JUMBF, 0);
    202    }
    203    if (JXL_ENC_SUCCESS != JxlEncoderAddJPEGFrame(settings, jpeg_bytes->data(),
    204                                                  jpeg_bytes->size())) {
    205      JxlEncoderError error = JxlEncoderGetError(enc);
    206      if (error == JXL_ENC_ERR_BAD_INPUT) {
    207        fprintf(stderr,
    208                "Error while decoding the JPEG image. It may be corrupt (e.g. "
    209                "truncated) or of an unsupported type (e.g. CMYK).\n");
    210      } else if (error == JXL_ENC_ERR_JBRD) {
    211        fprintf(stderr,
    212                "JPEG bitstream reconstruction data could not be created. "
    213                "Possibly there is too much tail data.\n"
    214                "Try using --allow_jpeg_reconstruction 0, to losslessly "
    215                "recompress the JPEG image data without bitstream "
    216                "reconstruction data.\n");
    217      } else {
    218        fprintf(stderr, "JxlEncoderAddJPEGFrame() failed.\n");
    219      }
    220      return false;
    221    }
    222  } else {
    223    size_t num_alpha_channels = 0;  // Adjusted below.
    224    JxlBasicInfo basic_info = ppf.info;
    225    basic_info.xsize *= params.already_downsampled;
    226    basic_info.ysize *= params.already_downsampled;
    227    if (basic_info.alpha_bits > 0) num_alpha_channels = 1;
    228    if (params.intensity_target > 0) {
    229      basic_info.intensity_target = params.intensity_target;
    230    }
    231    basic_info.num_extra_channels =
    232        std::max<uint32_t>(num_alpha_channels, ppf.info.num_extra_channels);
    233    basic_info.num_color_channels = ppf.info.num_color_channels;
    234    const bool lossless = (params.distance == 0);
    235    auto non_perceptual_option = std::find_if(
    236        params.options.begin(), params.options.end(), [](JXLOption option) {
    237          return option.id ==
    238                 JXL_ENC_FRAME_SETTING_DISABLE_PERCEPTUAL_HEURISTICS;
    239        });
    240    const bool non_perceptual = non_perceptual_option != params.options.end() &&
    241                                non_perceptual_option->ival == 1;
    242    basic_info.uses_original_profile = TO_JXL_BOOL(lossless || non_perceptual);
    243    if (params.override_bitdepth != 0) {
    244      basic_info.bits_per_sample = params.override_bitdepth;
    245      basic_info.exponent_bits_per_sample =
    246          params.override_bitdepth == 32 ? 8 : 0;
    247    }
    248    if (JXL_ENC_SUCCESS !=
    249        JxlEncoderSetCodestreamLevel(enc, params.codestream_level)) {
    250      fprintf(stderr, "Setting --codestream_level failed.\n");
    251      return false;
    252    }
    253    if (JXL_ENC_SUCCESS != JxlEncoderSetBasicInfo(enc, &basic_info)) {
    254      fprintf(stderr, "JxlEncoderSetBasicInfo() failed.\n");
    255      return false;
    256    }
    257    if (JXL_ENC_SUCCESS !=
    258        JxlEncoderSetUpsamplingMode(enc, params.already_downsampled,
    259                                    params.upsampling_mode)) {
    260      fprintf(stderr, "JxlEncoderSetUpsamplingMode() failed.\n");
    261      return false;
    262    }
    263    if (JXL_ENC_SUCCESS !=
    264        JxlEncoderSetFrameBitDepth(settings, &ppf.input_bitdepth)) {
    265      fprintf(stderr, "JxlEncoderSetFrameBitDepth() failed.\n");
    266      return false;
    267    }
    268    if (num_alpha_channels != 0 &&
    269        JXL_ENC_SUCCESS != JxlEncoderSetExtraChannelDistance(
    270                               settings, 0, params.alpha_distance)) {
    271      fprintf(stderr, "Setting alpha distance failed.\n");
    272      return false;
    273    }
    274    if (lossless &&
    275        JXL_ENC_SUCCESS != JxlEncoderSetFrameLossless(settings, JXL_TRUE)) {
    276      fprintf(stderr, "JxlEncoderSetFrameLossless() failed.\n");
    277      return false;
    278    }
    279    if (ppf.primary_color_representation == PackedPixelFile::kIccIsPrimary) {
    280      if (JXL_ENC_SUCCESS !=
    281          JxlEncoderSetICCProfile(enc, ppf.icc.data(), ppf.icc.size())) {
    282        fprintf(stderr, "JxlEncoderSetICCProfile() failed.\n");
    283        return false;
    284      }
    285    } else {
    286      if (JXL_ENC_SUCCESS !=
    287          JxlEncoderSetColorEncoding(enc, &ppf.color_encoding)) {
    288        fprintf(stderr, "JxlEncoderSetColorEncoding() failed.\n");
    289        return false;
    290      }
    291    }
    292 
    293    if (use_boxes) {
    294      if (JXL_ENC_SUCCESS != JxlEncoderUseBoxes(enc)) {
    295        fprintf(stderr, "JxlEncoderUseBoxes() failed.\n");
    296        return false;
    297      }
    298      // Prepend 4 zero bytes to exif for tiff header offset
    299      std::vector<uint8_t> exif_with_offset;
    300      bool bigendian;
    301      if (IsExif(ppf.metadata.exif, &bigendian)) {
    302        exif_with_offset.resize(ppf.metadata.exif.size() + 4);
    303        memcpy(exif_with_offset.data() + 4, ppf.metadata.exif.data(),
    304               ppf.metadata.exif.size());
    305      }
    306      const struct BoxInfo {
    307        const char* type;
    308        const std::vector<uint8_t>& bytes;
    309      } boxes[] = {
    310          {"Exif", exif_with_offset},   {"xml ", ppf.metadata.xmp},
    311          {"jumb", ppf.metadata.jumbf}, {"xml ", ppf.metadata.iptc},
    312          {"jhgm", ppf.metadata.jhgm},
    313      };
    314      for (auto box : boxes) {
    315        if (!box.bytes.empty()) {
    316          if (JXL_ENC_SUCCESS !=
    317              JxlEncoderAddBox(enc, box.type, box.bytes.data(),
    318                               box.bytes.size(),
    319                               TO_JXL_BOOL(params.compress_boxes))) {
    320            fprintf(stderr, "JxlEncoderAddBox() failed (%s).\n", box.type);
    321            return false;
    322          }
    323        }
    324      }
    325      JxlEncoderCloseBoxes(enc);
    326    }
    327 
    328    for (size_t num_frame = 0; num_frame < ppf.frames.size(); ++num_frame) {
    329      const jxl::extras::PackedFrame& pframe = ppf.frames[num_frame];
    330      const jxl::extras::PackedImage& pimage = pframe.color;
    331      JxlPixelFormat ppixelformat = pimage.format;
    332      size_t num_interleaved_alpha =
    333          (ppixelformat.num_channels - ppf.info.num_color_channels);
    334      if (!SetupFrame(enc, settings, pframe.frame_info, params, ppf, num_frame,
    335                      num_alpha_channels, num_interleaved_alpha, option_idx)) {
    336        return false;
    337      }
    338      if (JXL_ENC_SUCCESS != JxlEncoderAddImageFrame(settings, &ppixelformat,
    339                                                     pimage.pixels(),
    340                                                     pimage.pixels_size)) {
    341        fprintf(stderr, "JxlEncoderAddImageFrame() failed.\n");
    342        return false;
    343      }
    344      // Only set extra channel buffer if it is provided non-interleaved.
    345      for (size_t i = 0; i < pframe.extra_channels.size(); ++i) {
    346        if (JXL_ENC_SUCCESS !=
    347            JxlEncoderSetExtraChannelBuffer(settings, &ppixelformat,
    348                                            pframe.extra_channels[i].pixels(),
    349                                            pframe.extra_channels[i].stride *
    350                                                pframe.extra_channels[i].ysize,
    351                                            num_interleaved_alpha + i)) {
    352          fprintf(stderr, "JxlEncoderSetExtraChannelBuffer() failed.\n");
    353          return false;
    354        }
    355      }
    356    }
    357    for (size_t fi = 0; fi < ppf.chunked_frames.size(); ++fi) {
    358      ChunkedPackedFrame& chunked_frame = ppf.chunked_frames[fi];
    359      size_t num_interleaved_alpha =
    360          (chunked_frame.format.num_channels - ppf.info.num_color_channels);
    361      if (!SetupFrame(enc, settings, chunked_frame.frame_info, params, ppf, fi,
    362                      num_alpha_channels, num_interleaved_alpha, option_idx)) {
    363        return false;
    364      }
    365      const bool last_frame = fi + 1 == ppf.chunked_frames.size();
    366      if (JXL_ENC_SUCCESS !=
    367          JxlEncoderAddChunkedFrame(settings, TO_JXL_BOOL(last_frame),
    368                                    chunked_frame.GetInputSource())) {
    369        fprintf(stderr, "JxlEncoderAddChunkedFrame() failed.\n");
    370        return false;
    371      }
    372    }
    373  }
    374  JxlEncoderCloseInput(enc);
    375  if (params.HasOutputProcessor()) {
    376    if (JXL_ENC_SUCCESS != JxlEncoderFlushInput(enc)) {
    377      fprintf(stderr, "JxlEncoderAddChunkedFrame() failed.\n");
    378      return false;
    379    }
    380  } else if (!ReadCompressedOutput(enc, compressed)) {
    381    return false;
    382  }
    383  return true;
    384 }
    385 
    386 }  // namespace extras
    387 }  // namespace jxl