tor-browser

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

file-jxl-load.cc (17661B)


      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 "plugins/gimp/file-jxl-load.h"
      7 
      8 #include <jxl/decode.h>
      9 #include <jxl/decode_cxx.h>
     10 
     11 #define _PROFILE_ORIGIN_ JXL_COLOR_PROFILE_TARGET_ORIGINAL
     12 #define _PROFILE_TARGET_ JXL_COLOR_PROFILE_TARGET_DATA
     13 #define LOAD_PROC "file-jxl-load"
     14 
     15 namespace jxl {
     16 
     17 bool SetJpegXlOutBuffer(
     18    std::unique_ptr<JxlDecoderStruct, JxlDecoderDestroyStruct> *dec,
     19    JxlPixelFormat *format, size_t *buffer_size, gpointer *pixels_buffer_1) {
     20  if (JXL_DEC_SUCCESS !=
     21      JxlDecoderImageOutBufferSize(dec->get(), format, buffer_size)) {
     22    g_printerr(LOAD_PROC " Error: JxlDecoderImageOutBufferSize failed\n");
     23    return false;
     24  }
     25  *pixels_buffer_1 = g_malloc(*buffer_size);
     26  if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec->get(), format,
     27                                                     *pixels_buffer_1,
     28                                                     *buffer_size)) {
     29    g_printerr(LOAD_PROC " Error: JxlDecoderSetImageOutBuffer failed\n");
     30    return false;
     31  }
     32  return true;
     33 }
     34 
     35 bool LoadJpegXlImage(const gchar *const filename, gint32 *const image_id) {
     36  bool stop_processing = false;
     37  JxlDecoderStatus status = JXL_DEC_NEED_MORE_INPUT;
     38  std::vector<uint8_t> icc_profile;
     39  GimpColorProfile *profile_icc = nullptr;
     40  GimpColorProfile *profile_int = nullptr;
     41  bool is_linear = false;
     42  uint32_t xsize = 0;
     43  uint32_t ysize = 0;
     44  int32_t crop_x0 = 0;
     45  int32_t crop_y0 = 0;
     46  size_t layer_idx = 0;
     47  uint32_t frame_duration = 0;
     48  double tps_denom = 1.f;
     49  double tps_numerator = 1.f;
     50 
     51  gint32 layer;
     52 
     53  gpointer pixels_buffer_1 = nullptr;
     54  gpointer pixels_buffer_2 = nullptr;
     55  size_t buffer_size = 0;
     56 
     57  GimpImageBaseType image_type = GIMP_RGB;
     58  GimpImageType layer_type = GIMP_RGB_IMAGE;
     59  GimpPrecision precision = GIMP_PRECISION_U16_GAMMA;
     60  JxlBasicInfo info = {};
     61  JxlPixelFormat format = {};
     62  JxlAnimationHeader animation = {};
     63  JxlBlendMode blend_mode = JXL_BLEND_BLEND;
     64  std::vector<char> frame_name;
     65 
     66  format.num_channels = 4;
     67  format.data_type = JXL_TYPE_FLOAT;
     68  format.endianness = JXL_NATIVE_ENDIAN;
     69  format.align = 0;
     70 
     71  bool is_gray = false;
     72 
     73  JpegXlGimpProgress gimp_load_progress(
     74      ("Opening JPEG XL file:" + std::string(filename)).c_str());
     75  gimp_load_progress.update();
     76 
     77  // read file
     78  std::ifstream instream(filename, std::ios::in | std::ios::binary);
     79  std::vector<uint8_t> compressed((std::istreambuf_iterator<char>(instream)),
     80                                  std::istreambuf_iterator<char>());
     81  instream.close();
     82 
     83  gimp_load_progress.update();
     84 
     85  // multi-threaded parallel runner.
     86  auto runner = JxlResizableParallelRunnerMake(nullptr);
     87 
     88  auto dec = JxlDecoderMake(nullptr);
     89  if (JXL_DEC_SUCCESS !=
     90      JxlDecoderSubscribeEvents(
     91          dec.get(), JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING |
     92                         JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME_PROGRESSION |
     93                         JXL_DEC_FRAME)) {
     94    g_printerr(LOAD_PROC " Error: JxlDecoderSubscribeEvents failed\n");
     95    return false;
     96  }
     97 
     98  if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(),
     99                                                     JxlResizableParallelRunner,
    100                                                     runner.get())) {
    101    g_printerr(LOAD_PROC " Error: JxlDecoderSetParallelRunner failed\n");
    102    return false;
    103  }
    104  // TODO(user): make this work with coalescing set to false, while handling
    105  // frames with duration 0 and references to earlier frames correctly.
    106  if (JXL_DEC_SUCCESS != JxlDecoderSetCoalescing(dec.get(), JXL_TRUE)) {
    107    g_printerr(LOAD_PROC " Error: JxlDecoderSetCoalescing failed\n");
    108    return false;
    109  }
    110 
    111  // grand decode loop...
    112  JxlDecoderSetInput(dec.get(), compressed.data(), compressed.size());
    113 
    114  if (JXL_DEC_SUCCESS != JxlDecoderSetProgressiveDetail(
    115                             dec.get(), JxlProgressiveDetail::kPasses)) {
    116    g_printerr(LOAD_PROC " Error: JxlDecoderSetProgressiveDetail failed\n");
    117    return false;
    118  }
    119 
    120  while (true) {
    121    gimp_load_progress.update();
    122 
    123    if (!stop_processing) status = JxlDecoderProcessInput(dec.get());
    124 
    125    if (status == JXL_DEC_BASIC_INFO) {
    126      if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) {
    127        g_printerr(LOAD_PROC " Error: JxlDecoderGetBasicInfo failed\n");
    128        return false;
    129      }
    130 
    131      xsize = info.xsize;
    132      ysize = info.ysize;
    133      if (info.have_animation) {
    134        animation = info.animation;
    135        tps_denom = animation.tps_denominator;
    136        tps_numerator = animation.tps_numerator;
    137      }
    138 
    139      JxlResizableParallelRunnerSetThreads(
    140          runner.get(), JxlResizableParallelRunnerSuggestThreads(xsize, ysize));
    141    } else if (status == JXL_DEC_COLOR_ENCODING) {
    142      // check for ICC profile
    143      size_t icc_size = 0;
    144      JxlColorEncoding color_encoding;
    145      if (JXL_DEC_SUCCESS !=
    146          JxlDecoderGetColorAsEncodedProfile(dec.get(), _PROFILE_ORIGIN_,
    147                                             &color_encoding)) {
    148        // Attempt to load ICC profile when no internal color encoding
    149        if (JXL_DEC_SUCCESS != JxlDecoderGetICCProfileSize(
    150                                   dec.get(), _PROFILE_ORIGIN_, &icc_size)) {
    151          g_printerr(LOAD_PROC
    152                     " Warning: JxlDecoderGetICCProfileSize failed\n");
    153        }
    154 
    155        if (icc_size > 0) {
    156          icc_profile.resize(icc_size);
    157          if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile(
    158                                     dec.get(), _PROFILE_ORIGIN_,
    159                                     icc_profile.data(), icc_profile.size())) {
    160            g_printerr(LOAD_PROC
    161                       " Warning: JxlDecoderGetColorAsICCProfile failed\n");
    162          }
    163 
    164          profile_icc = gimp_color_profile_new_from_icc_profile(
    165              icc_profile.data(), icc_profile.size(), nullptr);
    166 
    167          if (profile_icc) {
    168            is_linear = gimp_color_profile_is_linear(profile_icc);
    169            g_printerr(LOAD_PROC " Info: Color profile is_linear = %d\n",
    170                       is_linear);
    171          } else {
    172            g_printerr(LOAD_PROC " Warning: Failed to read ICC profile.\n");
    173          }
    174        } else {
    175          g_printerr(LOAD_PROC " Warning: Empty ICC data.\n");
    176        }
    177      }
    178 
    179      // Internal color profile detection...
    180      if (JXL_DEC_SUCCESS ==
    181          JxlDecoderGetColorAsEncodedProfile(dec.get(), _PROFILE_TARGET_,
    182                                             &color_encoding)) {
    183        g_printerr(LOAD_PROC " Info: Internal color encoding detected.\n");
    184 
    185        // figure out linearity of internal profile
    186        switch (color_encoding.transfer_function) {
    187          case JXL_TRANSFER_FUNCTION_LINEAR:
    188            is_linear = true;
    189            break;
    190 
    191          case JXL_TRANSFER_FUNCTION_709:
    192          case JXL_TRANSFER_FUNCTION_PQ:
    193          case JXL_TRANSFER_FUNCTION_HLG:
    194          case JXL_TRANSFER_FUNCTION_GAMMA:
    195          case JXL_TRANSFER_FUNCTION_DCI:
    196          case JXL_TRANSFER_FUNCTION_SRGB:
    197            is_linear = false;
    198            break;
    199 
    200          case JXL_TRANSFER_FUNCTION_UNKNOWN:
    201          default:
    202            if (profile_icc) {
    203              g_printerr(LOAD_PROC
    204                         " Info: Unknown transfer function.  "
    205                         "ICC profile is present.");
    206            } else {
    207              g_printerr(LOAD_PROC
    208                         " Info: Unknown transfer function.  "
    209                         "No ICC profile present.");
    210            }
    211            break;
    212        }
    213 
    214        switch (color_encoding.color_space) {
    215          case JXL_COLOR_SPACE_RGB:
    216            if (color_encoding.white_point == JXL_WHITE_POINT_D65 &&
    217                color_encoding.primaries == JXL_PRIMARIES_SRGB) {
    218              if (is_linear) {
    219                profile_int = gimp_color_profile_new_rgb_srgb_linear();
    220              } else {
    221                profile_int = gimp_color_profile_new_rgb_srgb();
    222              }
    223            } else if (!is_linear &&
    224                       color_encoding.white_point == JXL_WHITE_POINT_D65 &&
    225                       (color_encoding.primaries_green_xy[0] == 0.2100 ||
    226                        color_encoding.primaries_green_xy[1] == 0.7100)) {
    227              // Probably Adobe RGB
    228              profile_int = gimp_color_profile_new_rgb_adobe();
    229            } else if (profile_icc) {
    230              g_printerr(LOAD_PROC
    231                         " Info: Unknown RGB colorspace.  "
    232                         "Using ICC profile.\n");
    233            } else {
    234              g_printerr(LOAD_PROC
    235                         " Info: Unknown RGB colorspace.  "
    236                         "Treating as sRGB.\n");
    237              if (is_linear) {
    238                profile_int = gimp_color_profile_new_rgb_srgb_linear();
    239              } else {
    240                profile_int = gimp_color_profile_new_rgb_srgb();
    241              }
    242            }
    243            break;
    244 
    245          case JXL_COLOR_SPACE_GRAY:
    246            is_gray = true;
    247            if (!profile_icc ||
    248                color_encoding.white_point == JXL_WHITE_POINT_D65) {
    249              if (is_linear) {
    250                profile_int = gimp_color_profile_new_d65_gray_linear();
    251              } else {
    252                profile_int = gimp_color_profile_new_d65_gray_srgb_trc();
    253              }
    254            }
    255            break;
    256          case JXL_COLOR_SPACE_XYB:
    257          case JXL_COLOR_SPACE_UNKNOWN:
    258          default:
    259            if (profile_icc) {
    260              g_printerr(LOAD_PROC
    261                         " Info: Unknown colorspace.  Using ICC profile.\n");
    262            } else {
    263              g_error(
    264                  LOAD_PROC
    265                  " Warning: Unknown colorspace. Treating as sRGB profile.\n");
    266 
    267              if (is_linear) {
    268                profile_int = gimp_color_profile_new_rgb_srgb_linear();
    269              } else {
    270                profile_int = gimp_color_profile_new_rgb_srgb();
    271              }
    272            }
    273            break;
    274        }
    275      }
    276 
    277      // set pixel format
    278      if (info.num_color_channels > 1) {
    279        if (info.alpha_bits == 0) {
    280          image_type = GIMP_RGB;
    281          layer_type = GIMP_RGB_IMAGE;
    282          format.num_channels = info.num_color_channels;
    283        } else {
    284          image_type = GIMP_RGB;
    285          layer_type = GIMP_RGBA_IMAGE;
    286          format.num_channels = info.num_color_channels + 1;
    287        }
    288      } else if (info.num_color_channels == 1) {
    289        if (info.alpha_bits == 0) {
    290          image_type = GIMP_GRAY;
    291          layer_type = GIMP_GRAY_IMAGE;
    292          format.num_channels = info.num_color_channels;
    293        } else {
    294          image_type = GIMP_GRAY;
    295          layer_type = GIMP_GRAYA_IMAGE;
    296          format.num_channels = info.num_color_channels + 1;
    297        }
    298      }
    299 
    300      // Set image bit depth and linearity
    301      if (info.bits_per_sample <= 8) {
    302        if (is_linear) {
    303          precision = GIMP_PRECISION_U8_LINEAR;
    304        } else {
    305          precision = GIMP_PRECISION_U8_GAMMA;
    306        }
    307      } else if (info.bits_per_sample <= 16) {
    308        if (info.exponent_bits_per_sample > 0) {
    309          if (is_linear) {
    310            precision = GIMP_PRECISION_HALF_LINEAR;
    311          } else {
    312            precision = GIMP_PRECISION_HALF_GAMMA;
    313          }
    314        } else if (is_linear) {
    315          precision = GIMP_PRECISION_U16_LINEAR;
    316        } else {
    317          precision = GIMP_PRECISION_U16_GAMMA;
    318        }
    319      } else {
    320        if (info.exponent_bits_per_sample > 0) {
    321          if (is_linear) {
    322            precision = GIMP_PRECISION_FLOAT_LINEAR;
    323          } else {
    324            precision = GIMP_PRECISION_FLOAT_GAMMA;
    325          }
    326        } else if (is_linear) {
    327          precision = GIMP_PRECISION_U32_LINEAR;
    328        } else {
    329          precision = GIMP_PRECISION_U32_GAMMA;
    330        }
    331      }
    332 
    333      // create new image
    334      if (is_linear) {
    335        *image_id = gimp_image_new_with_precision(xsize, ysize, image_type,
    336                                                  GIMP_PRECISION_FLOAT_LINEAR);
    337      } else {
    338        *image_id = gimp_image_new_with_precision(xsize, ysize, image_type,
    339                                                  GIMP_PRECISION_FLOAT_GAMMA);
    340      }
    341 
    342      if (profile_int) {
    343        gimp_image_set_color_profile(*image_id, profile_int);
    344      } else if (!profile_icc) {
    345        g_printerr(LOAD_PROC " Warning: No color profile.\n");
    346      }
    347    } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
    348      // get image from decoder in FLOAT
    349      format.data_type = JXL_TYPE_FLOAT;
    350      if (!SetJpegXlOutBuffer(&dec, &format, &buffer_size, &pixels_buffer_1))
    351        return false;
    352    } else if (status == JXL_DEC_FULL_IMAGE) {
    353      // create and insert layer
    354      gchar *layer_name;
    355      if (layer_idx == 0 && !info.have_animation) {
    356        layer_name = g_strdup_printf("Background");
    357      } else {
    358        const char *blend = (blend_mode == JXL_BLEND_REPLACE) ? " (replace)"
    359                            : (blend_mode == JXL_BLEND_BLEND) ? " (combine)"
    360                                                              : "";
    361        char *temp_frame_name = nullptr;
    362        bool must_free_frame_name = false;
    363        if (frame_name.size() == 0) {
    364          temp_frame_name = g_strdup_printf("Frame %lu", layer_idx + 1);
    365          must_free_frame_name = true;
    366        } else {
    367          temp_frame_name = frame_name.data();
    368        }
    369        double fduration = frame_duration * 1000.f * tps_denom / tps_numerator;
    370        layer_name = g_strdup_printf("%s (%.15gms)%s", temp_frame_name,
    371                                     fduration, blend);
    372        if (must_free_frame_name) free(temp_frame_name);
    373      }
    374      layer = gimp_layer_new(*image_id, layer_name, xsize, ysize, layer_type,
    375                             /*opacity=*/100,
    376                             gimp_image_get_default_new_layer_mode(*image_id));
    377 
    378      gimp_image_insert_layer(*image_id, layer, /*parent_id=*/-1,
    379                              /*position=*/0);
    380 
    381      pixels_buffer_2 = g_malloc(buffer_size);
    382      GeglBuffer *buffer = gimp_drawable_get_buffer(layer);
    383      const Babl *destination_format = gegl_buffer_set_format(buffer, nullptr);
    384 
    385      std::string babl_format_str = "";
    386      if (is_gray) {
    387        babl_format_str += "Y'";
    388      } else {
    389        babl_format_str += "R'G'B'";
    390      }
    391      if (info.alpha_bits > 0) {
    392        babl_format_str += "A";
    393      }
    394      babl_format_str += " float";
    395 
    396      const Babl *source_format = babl_format(babl_format_str.c_str());
    397 
    398      babl_process(babl_fish(source_format, destination_format),
    399                   pixels_buffer_1, pixels_buffer_2, xsize * ysize);
    400 
    401      gegl_buffer_set(buffer, GEGL_RECTANGLE(0, 0, xsize, ysize), 0, nullptr,
    402                      pixels_buffer_2, GEGL_AUTO_ROWSTRIDE);
    403      gimp_item_transform_translate(layer, crop_x0, crop_y0);
    404 
    405      g_clear_object(&buffer);
    406      g_free(pixels_buffer_1);
    407      g_free(pixels_buffer_2);
    408      if (stop_processing) status = JXL_DEC_SUCCESS;
    409      g_free(layer_name);
    410      layer_idx++;
    411    } else if (status == JXL_DEC_FRAME) {
    412      JxlFrameHeader frame_header;
    413      if (JxlDecoderGetFrameHeader(dec.get(), &frame_header) !=
    414          JXL_DEC_SUCCESS) {
    415        g_printerr(LOAD_PROC " Error: JxlDecoderSetImageOutBuffer failed\n");
    416        return false;
    417      }
    418      xsize = frame_header.layer_info.xsize;
    419      ysize = frame_header.layer_info.ysize;
    420      crop_x0 = frame_header.layer_info.crop_x0;
    421      crop_y0 = frame_header.layer_info.crop_y0;
    422      frame_duration = frame_header.duration;
    423      blend_mode = frame_header.layer_info.blend_info.blendmode;
    424      if (blend_mode != JXL_BLEND_BLEND && blend_mode != JXL_BLEND_REPLACE) {
    425        g_printerr(
    426            LOAD_PROC
    427            " Warning: JxlDecoderGetFrameHeader: Unhandled blend mode: %d\n",
    428            blend_mode);
    429      }
    430      if (frame_header.name_length > 0) {
    431        frame_name.resize(frame_header.name_length + 1);
    432        if (JXL_DEC_SUCCESS != JxlDecoderGetFrameName(dec.get(),
    433                                                      frame_name.data(),
    434                                                      frame_name.size())) {
    435          g_printerr(LOAD_PROC "Error: JxlDecoderGetFrameName failed");
    436          return false;
    437        }
    438      } else {
    439        frame_name.resize(0);
    440      }
    441    } else if (status == JXL_DEC_SUCCESS) {
    442      // All decoding successfully finished.
    443      // It's not required to call JxlDecoderReleaseInput(dec.get())
    444      // since the decoder will be destroyed.
    445      break;
    446    } else if (status == JXL_DEC_NEED_MORE_INPUT ||
    447               status == JXL_DEC_FRAME_PROGRESSION) {
    448      stop_processing = status != JXL_DEC_FRAME_PROGRESSION;
    449      if (JxlDecoderFlushImage(dec.get()) == JXL_DEC_SUCCESS) {
    450        status = JXL_DEC_FULL_IMAGE;
    451        continue;
    452      }
    453      g_printerr(LOAD_PROC " Error: Already provided all input\n");
    454      return false;
    455    } else if (status == JXL_DEC_ERROR) {
    456      g_printerr(LOAD_PROC " Error: Decoder error\n");
    457      return false;
    458    } else {
    459      g_printerr(LOAD_PROC " Error: Unknown decoder status\n");
    460      return false;
    461    }
    462  }  // end grand decode loop
    463 
    464  gimp_load_progress.update();
    465 
    466  if (profile_icc) {
    467    gimp_image_set_color_profile(*image_id, profile_icc);
    468  }
    469 
    470  gimp_load_progress.update();
    471 
    472  // TODO(xiota): Add option to keep image as float
    473  if (info.bits_per_sample < 32) {
    474    gimp_image_convert_precision(*image_id, precision);
    475  }
    476 
    477  gimp_image_set_filename(*image_id, filename);
    478 
    479  gimp_load_progress.finished();
    480  return true;
    481 }
    482 
    483 }  // namespace jxl