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