libjxl

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

file-jxl-load.cc (17873B)


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