libjxl

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

jxl.cc (14678B)


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